前言
前段时间有位兄弟看到我前面写的一篇文章《基于Next14+Auth5实现Github、Google、Gitee平台授权登录和邮箱密码登录》,在实现飞书登录时遇到了个问题,私信我让我帮忙解决一下。
我简单到飞书开发者平台看了一下,它也是基于OAuth2协议,以为和gitee登录差不多,自定义provider就行了。当时比较忙,就让他自己先研究了。
他研究了一阵,发现飞书登录流程和gitee登录流程不太一样,获取token这一步需要自定义request,他按照官方文档也自定义了request,但是一直不生效,不知道怎么回事,想让我有偿帮忙。
我试了一下也不行,然后看了next-auth库的源码后发现v5版本获取token不支持自定义request,v4是支持的,这位兄弟思路是对的,没想到官方文档写了支持,但是实际不支持,他一直认为是自己的问题,所以有时候遇得解决不了的bug,可以去看看源码,或许有收获。
v4版本的文档里写的token是支持request参数的
源码这个文件里的handleOAuth方法,压根没有调用request方法,只用到了url属性。
既然找到了问题,那有没有办法解决呢,他还是想让我帮忙解决一下。我问了一下他这个项目是个人项目还是公司项目,如果是是个人盈利项目或者接的私活,我可能会让他请我喝杯咖啡,因为当时我也很忙,抽时间帮他解决一个我自己也不确定能不能解决的问题,可能会花很多时间。他回答说是公司项目,那我就不打算收他钱了,想着大家都是打工人,都不容易。
没等到周末,当天晚上下班后,我研究了一下,然后写了个demo给他,最后成功把问题解决了,下面给大家分享一下我的解决方案,希望能帮助更多的人。
建议大家先看一下前面文档
飞书登录流程
获取用户信息
可以通过https://open.feishu.cn/open-apis/authen/v1/user_info
接口获取,但是需要在请求头上带上access_token。
文档地址:open.feishu.cn/document/se…
获取access_token
可以通过https://open.feishu.cn/open-apis/authen/v1/oidc/access_token
接口获取用户token,但是需要先获取app_access_token
和授权码。
文档地址:open.feishu.cn/document/uA…
获取app_access_token
可以通过https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal
接口获取app_access_token
,参数是应用的App_ID和App_Secret,这个可以在应用管理界面查看到。
文档地址:open.feishu.cn/document/se…
获取授权码
可以通过接口https://open.feishu.cn/open-apis/authen/v1/authorize?app_id={APPID}&redirect_uri={REDIRECT_URI}&scope={SCOPE}&state={STATE}
获取,获取成功后,会重定向你设置的地址,并把授权码放到url参数上。
文档地址:open.feishu.cn/document/co…
小结
上面是通过获取用户信息反推需要哪些条件,正向流程是:
- 获取授权码
- 获取app_access_token
- 获取user_access_token
- 获取用户信息
现在知道飞书获取token为什么要自定义request了吧,因为飞书获取token操作比别的平台多了一个获取app_access_token的操作,需要先调用一个接口获取app_access_token,才能获取到user_access_token。
那现在token不支持自定义request,只支持自定义接口地址,我就想了个方法,我自己写一个接口,然后把token地址指向新写的接口,然后在接口里做这些操作就行了。
具体实现
创建飞书应用
先注册一个飞书账号,然后在这个页面创建应用,地址为:open.feishu.cn/app/
输入名称和应用描述后,就创建成功了。进入应用管理界面,App ID和App Secret后面会用到。
到安全设置这里把本地回调地址加进去,不然跳转的时候会报错,如果以后发到线上了,这里可以把线上的地址加进来。
回调地址:http://localhost:3000/api/auth/callback/feishu
自定义飞书Provider
把gitee的provider复制过来,根据飞书的参数要求改造一下。
/**
* @module providers/feishu
*/
export default function Feishu(config: any): any {
const apiUserUrl = 'https://open.feishu.cn/open-apis/authen/v1/user_info';
const apiAUthUrl = 'https://open.feishu.cn/open-apis/authen/v1/authorize';
// 开发环境
const devBaseUrl = 'http://localhost:3000';
// 生产环境
const prodBaseUrl = '';
const baseUrl = process.env.NODE_ENV === 'development' ? devBaseUrl : prodBaseUrl;
return {
id: 'feishu',
name: 'feishu',
type: 'oauth',
authorization: {
url: apiAUthUrl,
params: {
scope: '',
app_id: config.clientId,
redirect_uri: encodeURI(
`${baseUrl}/api/auth/callback/feishu`
),
state: 'RANDOMSTATE',
},
},
token: {
url: `${baseUrl}/api/feishu/token`,
},
userinfo: {
url: apiUserUrl,
async request({ tokens, provider }: any) {
// 拿到上一步获取到的token,调用飞书获取用户信息的接口,获取用户信息
const profile = await fetch(provider.userinfo?.url as URL, {
headers: {
Authorization: `Bearer ${tokens.access_token}`,
'User-Agent': 'authjs',
},
}).then(async (res) => await res.json());
return profile.data;
},
},
profile(profile: any) {
// 选择想要的参数设置的session里
return {
id: profile.open_id.toString(),
name: profile.name ?? profile.login,
image: profile.avatar_thumb,
};
},
options: config,
};
}
正常token配置的url是飞书的获取token地址,gitee就是这样的,但是前面说过飞书需要先获取到app_access_token,才能获取user_access_token,所以获取token这里需要自定义。
自定义获取飞书token接口
在next里对外暴露一个接口是非常简单的,app router模式下,只需要在按照下面这种格式定义文件就行了。
举个例子:
比如我们想对外暴露一个/api/user/login接口,那我们在项目api文件夹下创建/user/login/route.ts文件就行了。
如果想对外暴露GET请求方式,只需要在文件里导出GET方法。
如果对外暴露POST请求方式,只需要在文件里导出POST方法。
下面来实现一下这个接口
// src/app/api/feishu/token/route.ts
export async function POST(request: Request) {
// 获取body请求参数
const formData = await request.formData();
// 获取授权码
const code = formData.get('code');
// 构造获取app_access_token的请求body
const body = {
app_id: process.env.FEISHU_APP_ID,
app_secret: process.env.FEISHU_APP_SECRET,
};
// 获取app_access_token的请求url
const app_access_token_url = 'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal';
// 获取app_access_token
const result = await fetch(app_access_token_url,
{
body: JSON.stringify(body),
method: 'POST'
}
).then((res) => res.json());
// 从结果中取出app_access_token
const app_access_token = result['app_access_token'];
// 构造获取access_token的请求body
const access_token_body = {
grant_type: 'authorization_code',
code,
};
// 请求获取access_token的url
const access_token_url = 'https://open.feishu.cn/open-apis/authen/v1/oidc/access_token';
const access_token_result = await fetch(access_token_url, {
headers: {
Authorization: `Bearer ${app_access_token}`,
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify(access_token_body),
method: 'POST',
})
.then((res) => res.json())
.catch((error) => {
console.log(error, 'error');
});
// 返回access_token
return Response.json(access_token_result.data);
}
在auth的providers中添加刚才写的飞书provider
在.env文件中配置飞书APP_ID和APP_SECRET
效果展示
邮箱密码登录跨服务验证
前言
还有一个场景,有些兄弟私信我问我方案。他们邮箱密码登录的时候,验证不是在next中验证的,使用的是三方接口,这里也给大家分享一下我的方案。
模拟三方接口
登录
直接在next项目里模拟登录接口,这个接口可以是任何后端语言写的接口,比如java、go、node。
// src/app/api/user/login/route.ts
// 模拟登录
export async function POST(request: Request) {
// 获取body请求参数
const data = await request.json();
console.log(data);
// 这里可以写登录验证
return Response.json({ access_token: Date.now() });
}
获取当前用户信息
export async function GET(request: Request) {
const token = request.headers.get('Authorization');
console.log(token, 'token');
return Response.json({ name: '前端小付' });
}
模拟需要token才能调用的接口
// src/app/api/user/list/route.ts
export const GET = (request: Request) => {
const token = request.headers.get('Authorization');
if (!token) {
return new Response('Unauthorized', {
status: 401,
});
}
return Response.json([{
name: '前端小付',
age: 18,
}]);
}
改造凭证登录验证方法
以前是在next项目中直接验证的,现在我们改造成调用远程接口验证。
没改造前的代码
改造后的代码,调用刚才写的登录接口,获取到token返回。
然后在jwt回调里调用获取用户信息的接口,通过token获取用户信息。这个jwt回调是用户每次使用auth()
方法获取session信息的时候会调用。
使用auth()方法获取session信息
在组件里调用远程接口,需要从session里拿到token传给接口。
每次都要写一遍这个,不太优雅,我们简单封装一个request方法,自动注入token。
'use server'
import { auth } from '@/auth';
export const request = async (input: RequestInfo | URL, init?: RequestInit) => {
const user: any = await auth();
return fetch(input, {
...init,
headers: {
...init?.headers,
Authorization: user?.user?.token || '',
}
});
}
刷新token
也有不少人问我登录后怎么刷新token,可以看下官方给的方案。
最后
这个故事告诉我们,在使用三方库遇到问题的时候,还是需要看一看源码的,不然被文档坑了都不知道。