前言

前段时间有位兄弟看到我前面写的一篇文章《基于Next14+Auth5实现Github、Google、Gitee平台授权登录和邮箱密码登录》,在实现飞书登录时遇到了个问题,私信我让我帮忙解决一下。

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

我简单到飞书开发者平台看了一下,它也是基于OAuth2协议,以为和gitee登录差不多,自定义provider就行了。当时比较忙,就让他自己先研究了。

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

他研究了一阵,发现飞书登录流程和gitee登录流程不太一样,获取token这一步需要自定义request,他按照官方文档也自定义了request,但是一直不生效,不知道怎么回事,想让我有偿帮忙。

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

我试了一下也不行,然后看了next-auth库的源码后发现v5版本获取token不支持自定义request,v4是支持的,这位兄弟思路是对的,没想到官方文档写了支持,但是实际不支持,他一直认为是自己的问题,所以有时候遇得解决不了的bug,可以去看看源码,或许有收获。

v4版本的文档里写的token是支持request参数的

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

源码这个文件里的handleOAuth方法,压根没有调用request方法,只用到了url属性。

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

既然找到了问题,那有没有办法解决呢,他还是想让我帮忙解决一下。我问了一下他这个项目是个人项目还是公司项目,如果是是个人盈利项目或者接的私活,我可能会让他请我喝杯咖啡,因为当时我也很忙,抽时间帮他解决一个我自己也不确定能不能解决的问题,可能会花很多时间。他回答说是公司项目,那我就不打算收他钱了,想着大家都是打工人,都不容易。

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

没等到周末,当天晚上下班后,我研究了一下,然后写了个demo给他,最后成功把问题解决了,下面给大家分享一下我的解决方案,希望能帮助更多的人。

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

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

飞书登录流程

获取用户信息

可以通过https://open.feishu.cn/open-apis/authen/v1/user_info接口获取,但是需要在请求头上带上access_token。

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

文档地址open.feishu.cn/document/se…

获取access_token

可以通过https://open.feishu.cn/open-apis/authen/v1/oidc/access_token接口获取用户token,但是需要先获取app_access_token和授权码。

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

文档地址: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,这个可以在应用管理界面查看到。

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

文档地址: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参数上。

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

文档地址: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/

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

输入名称和应用描述后,就创建成功了。进入应用管理界面,App ID和App Secret后面会用到。

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

到安全设置这里把本地回调地址加进去,不然跳转的时候会报错,如果以后发到线上了,这里可以把线上的地址加进来。

回调地址:http://localhost:3000/api/auth/callback/feishu

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

自定义飞书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

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

在.env文件中配置飞书APP_ID和APP_SECRET

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

效果展示

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

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

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

邮箱密码登录跨服务验证

前言

还有一个场景,有些兄弟私信我问我方案。他们邮箱密码登录的时候,验证不是在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项目中直接验证的,现在我们改造成调用远程接口验证。

没改造前的代码

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

改造后的代码,调用刚才写的登录接口,获取到token返回。

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

然后在jwt回调里调用获取用户信息的接口,通过token获取用户信息。这个jwt回调是用户每次使用auth()方法获取session信息的时候会调用。

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

使用auth()方法获取session信息

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

在组件里调用远程接口,需要从session里拿到token传给接口。

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

每次都要写一遍这个,不太优雅,我们简单封装一个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 || '',
    }
  });
}

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

刷新token

也有不少人问我登录后怎么刷新token,可以看下官方给的方案。

authjs.dev/guides/refr…

最后

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