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
1
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"
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
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
1
这将安装 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],
});
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
将自动获取环境变量
ts
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
1
2
3
4
2
3
4
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
1
2
2
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:
- GET
/api/auth/signin
- POST
/api/auth/signin/:provider
- GET/POST
/api/auth/callback/:provider
- GET
/api/auth/signout
- POST
/api/auth/signout
- GET
/api/auth/session
- GET
/api/auth/csrf
- GET
/api/auth/providers
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
2
3
2
3
- AUTH_SECRET
bash
openssl rand -base64 32
1
https://generate-secret.vercel.app/32
- 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).*)"],
};
1
2
3
4
5
6
2
3
4
5
6
允许访问首页:
^/$
ts
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico|^/$).*)"],
};
1
2
3
2
3
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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>
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
调用方式:
例如,在首页
tsx
import { SignIn, SignOut } from "@/app/ui/auth/auth-components";
return (
<>
...
<SignIn />
<SignOut />
...
</>
);
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
其中, 将导向登录页:
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 />
</>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
可接着参考如下页面:
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
暂略;
1
auth
ts
export const config = {
...
pages: {
signIn: '/login' // overrides the next-auth default signin page https://authjs.dev/guides/basics/pages
}
}
1
2
3
4
5
6
2
3
4
5
6
安装各种组件
json
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"tailwind-merge": "^2.2.1"
1
2
3
4
2
3
4
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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
参考资料: 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
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
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
]
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
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)提供程序搭配来验证用户的身份。