使用next14和Auth5快速实现飞书授权登录

前言

前段时间有位兄弟看到我前面写的一篇文章《基于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给他,最后成功把问题解决了,下面给大家分享一下我的解决方案,希望能帮助更多的人。

建议大家先看一下前面文档

基于Next14+Auth5实现Github、Google、Gitee平台授权登录和邮箱密码登录

飞书登录流程

获取用户信息

可以通过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...

小结

上面是通过获取用户信息反推需要哪些条件,正向流程是:

  1. 获取授权码
  2. 获取app_access_token
  3. 获取user_access_token
  4. 获取用户信息

现在知道飞书获取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复制过来,根据飞书的参数要求改造一下。

ts 复制代码
/**
 * @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方法。

下面来实现一下这个接口

ts 复制代码
// 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。

ts 复制代码
// 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() });
}

获取当前用户信息

ts 复制代码
export async function GET(request: Request) {
  const token = request.headers.get('Authorization');

  console.log(token, 'token');

  return Response.json({ name: '前端小付' });
}

模拟需要token才能调用的接口

ts 复制代码
// 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。

ts 复制代码
'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,可以看下官方给的方案。

authjs.dev/guides/refr...

最后

这个故事告诉我们,在使用三方库遇到问题的时候,还是需要看一看源码的,不然被文档坑了都不知道。

相关推荐
RisunJan5 小时前
Linux命令-named-checkzone
linux·前端
小陈工5 小时前
Python Web开发入门(十):数据库迁移与版本管理——让数据库变更可控可回滚
前端·数据库·人工智能·python·sql·云原生·架构
吹晚风吧5 小时前
解决vite打包,base配置前缀,nginx的dist包找不到资源
服务器·前端·nginx
weixin199701080165 小时前
《施耐德商品详情页前端性能优化实战》
前端·性能优化
不想上班只想要钱5 小时前
模板里 item.xxx 报错 ,报 item的类型为未知
前端·vue
Irene19916 小时前
推荐 React 开发需要在 VS Code 中安装的插件
react.js
妖萌妹儿6 小时前
postman怎么做参数化批量测试,测试不同输入组合
开发语言·javascript·postman
阿琳a_6 小时前
在github上部署个人的vitepress文档网站
前端·vue.js·github·网站搭建·cesium
酉鬼女又兒6 小时前
零基础快速入门前端ES6 核心特性详解与蓝桥杯 Web 考点实践(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·职场和发展·蓝桥杯·es6·css3·html5
Zk.Sun6 小时前
【RK3588 Mali610 适配 Qt6 】
前端·javascript·vue.js