Published on

Auth.js 中的 access token 和 refresh token 实现

Authors
  • avatar
    Name
    Shelton Ma
    Twitter

NextAuth.js 中的 access token 和 refresh token 实现

NextAuth.js 提供了对 OAuth 认证流程的支持,包括通过 access token 和 refresh token 处理外部身份验证系统的授权和认证.

基本概念

  1. Access Token:用于授权访问受保护资源,通常有效期较短.每当用户需要访问需要身份验证的 API 时,都会使用 access token.
  2. Refresh Token:用于获取新的 access token,通常有效期较长.当 access token 过期时,客户端可以使用 refresh token 获取新的 access token.

如何在 NextAuth.js 中配置 access token 和 refresh token

配置 NextAuth.js 并指定 access token 和 refresh token 的回调.

// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  callbacks: {
    // 获取授权后的 `access token` 和 `refresh token`
    async jwt({ token, account }) {
      if (account) {
        // 保存 `access_token` 和 `refresh_token` 到 JWT 中
        token.accessToken = account.access_token;
        token.refreshToken = account.refresh_token;
        token.expiresAt = Date.now() + account.expires_in * 1000; // 存储过期时间
      }
      
      // 检查 token 是否过期,如果过期则使用 refresh token 更新 access token
      if (token.expiresAt && Date.now() > token.expiresAt) {
        try {
          const response = await fetch('https://oauth2.googleapis.com/token', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams({
              client_id: process.env.GOOGLE_CLIENT_ID,
              client_secret: process.env.GOOGLE_CLIENT_SECRET,
              refresh_token: token.refreshToken,
              grant_type: 'refresh_token',
            }),
          });

          const refreshedTokens = await response.json();

          // 更新 JWT 中的 access_token 和过期时间
          if (refreshedTokens.access_token) {
            token.accessToken = refreshedTokens.access_token;
            token.expiresAt = Date.now() + refreshedTokens.expires_in * 1000;
          }
        } catch (error) {
          console.error('Error refreshing access token:', error);
        }
      }
      return token;
    },
    
    // 会话回调:每次访问会话时都可以访问 JWT
    async session({ session, token }) {
      session.accessToken = token.accessToken;
      session.refreshToken = token.refreshToken;
      session.expiresAt = token.expiresAt;
      return session;
    },
  },
  session: {
    strategy: 'jwt', // 使用 JWT 存储会话信息
  },
  secret: process.env.JWT_SECRET,
});