Skip to content

Next.js + Next-auth

NextAuth.js example https://next-auth-example.vercel.app/

https://github.com/nextauthjs/next-auth/tree/main/apps/examples/nextjs

从头到尾完成 Github OAuth 登录

1. 创建一个 Next.js 项目

bash
npx create-next-app@latest galogin
selection:
    ✔ Would you like to use TypeScript? … No / Yes
    ✔ Would you like to use ESLint? … No / Yes
    ✔ Would you like to use Tailwind CSS? … No / Yes
    ✔ Would you like to use `src/` directory? … No / Yes
    ✔ Would you like to use App Router? (recommended) … No / Yes
    ✔ Would you like to customize the default import alias (@/*)? … No / Yes
    ✔ What import alias would you like configured? … @/*
version next.js 14/next-auth 5/typescript 5/node 21.6.2

node -v v21.6.2

json
"dependencies": {
  "next": "14.1.0",
  "next-auth": "^5.0.0-beta.13",
  "react": "^18",
},
"devDependencies": {
  "tailwindcss": "^3.3.0",
  "typescript": "^5"
}

2. next-auth 安装

https://authjs.dev/getting-started/providers/oauth-tutorial

Upgrade Guide (v5) https://authjs.dev/guides/upgrade-to-v5

Nextjs 说明: https://authjs.dev/reference/nextjs

bash
npm install next-auth@beta

这将安装 Auth.js v5

3. auth.ts

From Upgrade Guide (v5)

We worked hard to avoid having to save your config options in a separate file and then pass them around as authOptions throughout your application. To achieve this, we settled on moving the configuration file to the root of the repository and having it export an auth function you can use everywhere else.

An example of the new configuration file format is as follows, as you can see it looks very similar to the existing one.

./auth.ts

ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";

export const {
  handlers: { GET, POST },
  auth,
} = NextAuth({
  providers: [GitHub],
});

将自动获取环境变量

ts
GithubProvider({
  clientId: process.env.GITHUB_ID,
  clientSecret: process.env.GITHUB_SECRET,
}),

4. server api route

The old configuration file, contained in the API Route (pages/api/auth/[...nextauth].ts), now becomes a 1-line handler for GET and POST requests for those paths.

Create the following API route file. This route contains the necessary configuration for NextAuth.js, as well as the dynamic route handler:

app/api/auth/[...nextauth]/route.ts

ts
export { GET, POST } from "@/auth";
export const runtime = "edge"; // optional
eg /api/auth/callback/github

Behind the scenes, this creates all the relevant OAuth API routes within /api/auth/* so that auth API requests to:

can be handled by NextAuth.js. In this way, NextAuth.js stays in charge of the whole application's authentication request/response flow.

NextAuth.js is fully customizable - our guides section teaches you how to set it up to handle auth in different ways. All the possible configuration options are listed here.

5. .env 设置

.env.local

AUTH_GITHUB_ID=...
AUTH_GITHUB_SECRET=...
AUTH_SECRET=...
  1. AUTH_SECRET
bash
openssl rand -base64 32

https://generate-secret.vercel.app/32

  1. PROVIDERS ID/SECRET

6. 配置 OAuth Provider

(# Create a GitHub OAuth app here: https://github.com/settings/applications/new)

(略去)

7. NEXT.JS middleware.ts

ts
export { auth as middleware } from "@/auth";

// Read more: https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

允许访问首页:

^/$

ts
export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico|^/$).*)"],
};

in next auth config

ts
export const config = {
  theme: {
    logo: "https://next-auth.js.org/img/logo/logo-sm.png",
  },
  providers: [GitHub],
  callbacks: {
    authorized({ request, auth }) {
      //允许访问首页。TODO:图片为何被屏蔽
      const { pathname } = request.nextUrl;
      if (pathname === "/") return true;

      //  如果要进入 "/middleware-example",检查是否登录。
      //   const { pathname } = request.nextUrl
      //   if (pathname === "/middleware-example") return !!auth
      //   return true

      return !!auth;
    },
  },
} satisfies NextAuthConfig;

8. signIn/SignOUt button

/app/ui/auth/auth-components.ts

ts
import { signIn, signOut } from "@/auth"
// import { Button } from "./ui/button"

export function SignIn({
  provider
}: { provider?: string } ) {
  return (
    <form
      action={async () => {
        "use server"
        await signIn(provider,{ redirectTo: '/user' })
      }}
    >
      <button>Sign In</button>
    </form>
  )
}

export function SignOut() {
  return (
    <form
      action={async () => {
        "use server"
        await signOut({ redirectTo: '/' })
      }}
      className="w-full"
    >
      <button className="w-full p-0" >
        Sign Out
      </button>
    </form>
  )
}

调用方式:

例如,在首页

tsx
import { SignIn, SignOut } from "@/app/ui/auth/auth-components";

return (
  <>
    ...
    <SignIn />
    <SignOut />
    ...
  </>
);

其中, 将导向登录页:

http://localhost:3000/api/auth/signin

9. 在受保护页显示用户信息

tsx
import { SignOut, SignIn } from "@/app/ui/auth/auth-components";
import { auth } from "@/auth";

export default async function Page() {
  const session = await auth();
  if (!session?.user) return <SignIn />;
  return (
    <>
      protected page
      <div>Username: {session.user?.name}</div>
      <div className="w-20 h-20">
        {session.user.image && (
          <img src={session.user.image} alt={session.user.name ?? ""} />
        )}
      </div>
      <SignOut />
    </>
  );
}

可接着参考如下页面:

Exposing the session via SessionProvider:

https://authjs.dev/getting-started/providers/oauth-tutorial#exposing-the-session-via-sessionprovider

Consuming the session via hooks

https://authjs.dev/getting-started/providers/oauth-tutorial#consuming-the-session-via-hooks

10 定制 login 页面

增加页面 /app/login/page.tsx

tsx
暂略;

auth

ts
export const config = {
...
  pages: {
    signIn: '/login' // overrides the next-auth default signin page https://authjs.dev/guides/basics/pages
  }
}

安装各种组件

json
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"tailwind-merge": "^2.2.1"

in /app/login/page.tsx/app/ui/login-button.tsx

tsx
import { signIn } from 'next-auth/react'

<Button
  variant="outline"
  onClick={() => {
    setIsLoading(true)
    // next-auth signIn() function doesn't work yet at Edge Runtime
    // due to usage of BroadcastChannel
    signIn('github', { callbackUrl: `/` })
  }}
  disabled={isLoading}
  className={cn(className)}
  {...props}
>
……
</Botton>

参考资料: Next-auth react

https://authjs.dev/reference/nextjs/react#signin

https://authjs.dev/guides/upgrade-to-v5#authentication-methods

知识 : Callbacks

Callbacks

https://authjs.dev/guides/basics/callbacks

Callbacks are asynchronous functions you can use to control what happens when an action is performed.

Callbacks are extremely powerful, especially in scenarios involving JSON Web Tokens as they allow you to implement access controls without a database and to integrate with external databases or APIs.

tip

If you want to pass data such as an Access Token or User ID to the browser when using JSON Web Tokens, you can persist the data in the token when the jwt callback is called, then pass the data through to the browser in the session callback.

You can specify a handler for any of the callbacks below.

auth.js

ts
callbacks: {
  async signIn({ user, account, profile, email, credentials }) {
    return true
  },
  async redirect({ url, baseUrl }) {
    return baseUrl
  },
  async session({ session, user, token }) {
    return session
  },
  async jwt({ token, user, account, profile, isNewUser }) {
    return token
  }
}

JWT callback

This callback is called whenever a JSON Web Token is created (i.e. at sign in) or updated (i.e whenever a session is accessed in the client). The returned value will be encrypted, and it is stored in a cookie.

Requests to /api/auth/signin, /api/auth/session and calls to getSession(), getServerSession(), useSession() will invoke this function, but only if you are using a JWT session. This method is not invoked when you persist sessions in a database.

Custom Provider

知识 Use your own provider

https://authjs.dev/guides/providers/custom-provider

However, you can use any provider as long as they are compliant with the OAuth/OIDC specifications.

Auth.js uses the oauth4webapi package under the hood.

To use a custom OAuth provider with Auth.js, pass an object to the providers list.

It can implement either the OAuth2Config or the OIDCConfig interface, depending on if your provider is OAuth 2 or OpenID Connect compliant.

For example, if you have a fully OIDC-compliant provider, this is all you need:

ts
import type { OIDCConfig } from "@auth/core/providers"

...
providers: [
  {
    id: "my-oidc-provider",
    name: "My Provider",
    type: "oidc",
    issuer: "https://my.oidc-provider.com",
    clientId: process.env.CLIENT_ID,
    clientSecret: process.env.CLIENT_SECRET
  } satisfies OIDCConfig
]
...

authing 参考资料

https://github.com/nextauthjs/next-auth/issues/6198

I have to redirect to the sign out link or I can't sign out completely

Federated logout (OpenID Connect) #3938 https://github.com/nextauthjs/next-auth/discussions/3938

成为 OAuth 2.0 身份源 更新时间: 2022-04-20 11:18:51

https://docs.authing.cn/v2/guides/federation/oauth.html

什么是身份提供商 (IdP)? https://www.cloudflare.com/zh-cn/learning/access-management/what-is-an-identity-provider/

身份提供商(IdP)是一种存储和验证用户身份的服务。IdP 通常是云托管服务,常常与单点登录(SSO)提供程序搭配来验证用户的身份。

最新更新:

Alang.AI - Make Great AI Applications