reference:
本篇文章我们主要介绍如何借助authkit快速搭建一个登录鉴权系统,提高开发效率
what?
在开发个人项目时,我们通常需要搭建一个用户账户系统,用于鉴权后进行一系列后续操作,这一部分的工作通常复用性较高且较为繁琐,那么我们有没有可能借助目前一些现有服务商提供的服务快速完成这部分的工作?
Authkit被提出解决这个问题,Authkit通过提供托管,与构建,可自定义的身份验证UI并自动处理以下内容使得搭建一个账户系统变得简单
- 注册、登录、密码重置和电子邮件验证流程
- 企业 SSO 路由和 MFA 注册
- 自动机器人检测
- 可定制的域名和品牌
通俗的来说,Authkit提供的服务与SSO的流程类似,不同的是用户账号注册登录&找回密码,用户数据存储等行为都由跳转到的外部平台处理,这意味着开发者可以不用写这一部分相关逻辑的后端
now what?
在这一部分,我们介绍如何快速接入Authkit
首先安装SDKnpm install @workos-inc/node
之后登录workOS官网获取一个账户(dashboard.workos.com/get-started)
在dashboard中获取当前账户对应的API_KEY与CLIENT_ID,并设置为项目的环境变量
// .env.local
WORKOS_API_KEY='sk_example_123456789'
WORKOS_CLIENT_ID='client_123456789'
之后我们需要在dashboard中设置redirect URI,当用户通过Authkit完成身份验证后,它将被重定向到这个redirect URI
在完成了基础设置后,我们现在将Authkit添加到我们的项目中
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS(process.env.WORKOS_API_KEY);
const clientId = process.env.WORKOS_CLIENT_IDas string;
export default function App() {
const authorizationUrl = workos.userManagement.getAuthorizationUrl({
provider: 'authkit',
redirectUri: 'your own redirect uri',
clientId,
});
return (
<>
<a href={authorizationUrl}>
click to sign in
</a>
</>
)
}
📌 官方文档采用的是api重定向的方式来完成重定向,也就是用户点击按钮→前端向一个自定义api发送请求→自定义api重定向到authorizationUrl,但在实际操作中,我发现这样会触发跨域问题(issue: https://github.com/workos/authkit/issues/13)
在用户点击sign in后,他会被重定向到authorizationUrl ,这个界面的样式可以在dashboard中自定义
接下来我们需要设置redirectUri对应的接口,在我们的示例中,redirectUri设置为/api/authCallback
由于我们在这一步用到了jwt,我们需要先生成一个secret key用于HS256加密node -e "console.log(require('crypto').randomBytes(64).toString('base64'));”
接下来使用如下代码完成回调api的构建
// src/app/api/authCallback/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { WorkOS } from '@workos-inc/node';
import { SignJWT } from 'jose';
const secret = new Uint8Array(
Buffer.from(process.env.JWT_SECRET_KEY || '', 'base64'),
);
const workos = new WorkOS(process.env.WORKOS_API_KEY);
const clientId = process.env.WORKOS_CLIENT_ID as string;
export async function GET(req: NextRequest) {
// The authorization code returned by AuthKit
const code = req.nextUrl.searchParams.get('code') as string;
const { user } = await workos.userManagement.authenticateWithCode({
code,
clientId,
});
// Cleanup params and redirect to homepage
const url = req.nextUrl.clone();
url.searchParams.delete('code');
url.pathname = '/';
const response = NextResponse.redirect(url);
// Create a JWT with the user's information
const token = await new SignJWT({
// Here you might lookup and retrieve user details from your database
user,
})
.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
.setIssuedAt()
.setExpirationTime('11h')
.sign(secret);
// Store in a cookie
response.cookies.set({
name: 'token',
value: token,
path: '/',
httpOnly: true,
secure: true,
sameSite: 'lax',
});
return response;
};
我们采用jwt对传回的用户数据进行加密并写回到客户端的cookie中,以达到持久化登录状态保存的目的
接下来一个问题是如何获取加密后的用户信息?
我们可以新增一个bff接口,用于解密校验当前用户登录态是否过期
// src/app/api/user/route.ts
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { jwtVerify } from 'jose';
// Get secret
const secret = new Uint8Array(
Buffer.from(process.env.JWT_SECRET_KEY || '', 'base64'),
);
export async function GET() {
const token = cookies().get('token')?.value || '';
// Verify the JWT signature
let verifiedToken;
try {
verifiedToken = await jwtVerify(token, secret);
} catch {
return NextResponse.json({ isAuthenticated: false }, { status: 401 });
}
// Return the User object if the token is valid
return NextResponse.json(
{
isAuthenticated: true,
user: verifiedToken.payload.user,
},
{ status: 200 },
);
}