JWT

JWT 插件提供用于获取 JWT 令牌的端点以及用于验证令牌的 JWKS 端点。

【The JWT plugin provides endpoints to retrieve a JWT token and a JWKS endpoint to verify the token.】

此插件并不是用来替代会话的。它旨在用于需要 JWT 令牌的服务。如果你想使用 JWT 令牌进行身份验证,请查看 Bearer 插件

安装

【Installation】

将插件添加到你的 auth 配置中

【Add the plugin to your auth config】

auth.ts
import { betterAuth } from "better-auth"
import { jwt } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [ 
        jwt(), 
    ] 
})

迁移数据库

运行迁移或生成架构,以向数据库添加必要的字段和表。

npx @better-auth/cli migrate
npx @better-auth/cli generate

See the Schema section to add the fields manually.

用法

【Usage】

安装插件后,你可以开始使用 JWT 和 JWKS 插件,通过各自的端点获取令牌和 JWKS。

【Once you've installed the plugin, you can start using the JWT & JWKS plugin to get the token and the JWKS through their respective endpoints.】

JWT

获取令牌

【Retrieve the token】

有多种方法可以获取 JWT 令牌:

【There are multiple ways to retrieve JWT tokens:】

  1. 使用客户端插件(推荐)

jwtClient 插件添加到你的身份验证客户端配置中:

【Add the jwtClient plugin to your auth client configuration:】

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { jwtClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
  plugins: [
    jwtClient() 
  ]
})

然后使用客户端获取 JWT 令牌:

【Then use the client to get JWT tokens:】

const { data, error } = await authClient.token()
if (error) {
  // handle error
}
if (data) {
  const jwtToken = data.token
  // Use this token for authenticated requests to external services
}

这是针对需要 JWT 令牌以进行外部 API 认证的客户端应用推荐的方法。

【This is the recommended approach for client applications that need JWT tokens for external API authentication.】

  1. 使用你的会话令牌

要获取令牌,请调用 /token 端点。这将返回以下内容:

【To get the token, call the /token endpoint. This will return the following:】

  { 
    "token": "ey..."
  }

如果在你的身份验证配置中添加了 bearer 插件,请确保在请求的 Authorization 头中包含令牌。

【Make sure to include the token in the Authorization header of your requests if the bearer plugin is added in your auth configuration.】

await fetch("/api/auth/token", {
  headers: {
    "Authorization": `Bearer ${token}`
  },
})
  1. 来自 set-auth-jwt

当你调用 getSession 方法时,会在 set-auth-jwt 头中返回一个 JWT,你可以直接将其发送到你的服务。

【When you call getSession method, a JWT is returned in the set-auth-jwt header, which you can use to send to your services directly.】

await authClient.getSession({
  fetchOptions: {
    onSuccess: (ctx)=>{
      const jwt = ctx.response.headers.get("set-auth-jwt")
    }
  }
})

验证令牌

【Verifying the token】

该令牌可以在你自己的服务中进行验证,无需额外的验证调用或数据库检查。为此使用 JWKS。可以从 /api/auth/jwks 端点获取公钥。

【The token can be verified in your own service, without the need for an additional verify call or database check. For this JWKS is used. The public key can be fetched from the /api/auth/jwks endpoint.】

由于此密钥不经常更改,因此可以无限期缓存。用于签署 JWT 的密钥 ID(kid)包含在令牌的头部。如果收到具有不同 kid 的 JWT,建议重新获取 JWKS。

【Since this key is not subject to frequent changes, it can be cached indefinitely. The key ID (kid) that was used to sign a JWT is included in the header of the token. In case a JWT with a different kid is received, it is recommended to fetch the JWKS again.】

  {
    "keys": [
        {
            "crv": "Ed25519",
            "x": "bDHiLTt7u-VIU7rfmcltcFhaHKLVvWFy-_csKZARUEU",
            "kty": "OKP",
            "kid": "c5c7995d-0037-4553-8aee-b5b620b89b23"
        }
    ]
  }

使用 jose 与远程 JWKS 的示例

【Example using jose with remote JWKS】

import { jwtVerify, createRemoteJWKSet } from 'jose'

async function validateToken(token: string) {
  try {
    const JWKS = createRemoteJWKSet(
      new URL('http://localhost:3000/api/auth/jwks')
    )
    const { payload } = await jwtVerify(token, JWKS, {
      issuer: 'http://localhost:3000', // Should match your JWT issuer, which is the BASE_URL
      audience: 'http://localhost:3000', // Should match your JWT audience, which is the BASE_URL by default
    })
    return payload
  } catch (error) {
    console.error('Token validation failed:', error)
    throw error
  }
}

// Usage example
const token = 'your.jwt.token' // this is the token you get from the /api/auth/token endpoint
const payload = await validateToken(token)

使用本地 JWKS 的示例

【Example with local JWKS】

import { jwtVerify, createLocalJWKSet } from 'jose'


async function validateToken(token: string) {
  try {
    /**
     * This is the JWKS that you get from the /api/auth/
     * jwks endpoint
     */
    const storedJWKS = {
      keys: [{
        //...
      }]
    };
    const JWKS = createLocalJWKSet({
      keys: storedJWKS.data?.keys!,
    })
    const { payload } = await jwtVerify(token, JWKS, {
      issuer: 'http://localhost:3000', // Should match your JWT issuer, which is the BASE_URL
      audience: 'http://localhost:3000', // Should match your JWT audience, which is the BASE_URL by default
    })
    return payload
  } catch (error) {
    console.error('Token validation failed:', error)
    throw error
  }
}

// Usage example
const token = 'your.jwt.token' // this is the token you get from the /api/auth/token endpoint
const payload = await validateToken(token)

OAuth 提供者模式

【OAuth Provider Mode】

如果你正在使你的系统符合 oAuth 标准(例如在使用 OIDC 或 MCP 插件时),你必须禁用/token端点(oAuth 等效端点为/oauth2/token)并禁用设置 jwt 头(oAuth 等效端点为/oauth2/userinfo)。

【If you are making your system oAuth compliant (such as when utilizing the OIDC or MCP plugins), you MUST disable the /token endpoint (oAuth equivalent /oauth2/token) and disable setting the jwt header (oAuth equivalent /oauth2/userinfo).】

auth.ts
betterAuth({
  disabledPaths: [
    "/token",
  ],
  plugins: [jwt({
    disableSettingJwtHeader: true,
  })]
})

远程 JWKS 链接

【Remote JWKS Url】

禁用 /jwks 端点,并在任何发现过程中(例如 OIDC)使用此端点。

【Disables the /jwks endpoint and uses this endpoint in any discovery such as OIDC.】

如果你的 JWKS 不是在 /jwks 管理,或者你的 JWKS 使用证书签名并放在 CDN 上,这会很有用。

【Useful if your JWKS are not managed at /jwks or if your jwks are signed with a certificate and placed on your CDN.】

注意:你必须指定用于签名的非对称算法。

【NOTE: you MUST specify which asymmetric algorithm is used for signing.】

auth.ts
jwt({
  jwks: {
    remoteUrl: "https://example.com/.well-known/jwks.json",
    keyPairConfig: {
      alg: 'ES256',
    },
  }
})

自定义 JWKS 路径

【Custom JWKS Path】

默认情况下,JWKS 端点位于 /jwks。你可以使用 jwksPath 选项自定义此路径。

【By default, the JWKS endpoint is available at /jwks. You can customize this path using the jwksPath option.】

当你需要时,这很有用:

  • 遵循 OAuth 2.0/OIDC 规范(例如,/.well-known/jwks.json
  • 与应用中现有的 API 规范保持一致
  • 避免与其他端点的路径冲突

【This is useful when you need to:

  • Follow OAuth 2.0/OIDC conventions (e.g., /.well-known/jwks.json)
  • Match existing API conventions in your application
  • Avoid path conflicts with other endpoints】

服务器配置:

auth.ts
jwt({
  jwks: {
    jwksPath: "/.well-known/jwks.json"
  }
})

客户端配置:

在服务器上使用自定义 jwksPath 时,你必须在客户端配置相同的路径:

【When using a custom jwksPath on the server, you MUST configure the client with the same path:】

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { jwtClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
  plugins: [
    jwtClient({
      jwks: {
        jwksPath: "/.well-known/jwks.json" // Must match server configuration
      }
    })
  ]
})

然后你可以像平常一样使用 jwks() 方法:

【Then you can use the jwks() method as usual:】

const { data, error } = await authClient.jwks()
if (data) {
  // Use data.keys to verify JWT tokens
}

客户端配置的 jwksPath 必须 与服务器配置相匹配。如果不匹配,客户端将无法获取 JWKS。

自定义签名

【Custom Signing】

这是一个高级功能。必须提供此插件之外的配置。

【This is an advanced feature. Configuration outside of this plugin MUST be provided.】

执行者:

  • 如果使用 sign 函数,必须定义 remoteUrl。它应存储所有活动密钥,而不仅仅是当前密钥。
  • 如果使用本地化方法,请确保服务器在密钥轮换后使用最新的私钥。根据部署情况,可能需要重启服务器。
  • 使用远程方法时,确认传输后的负载未被更改。如果可用,使用 CRC32 或 SHA256 校验等完整性验证。

【Implementers:

  • remoteUrl must be defined if using the sign function. This shall store all active keys, not just the current one.
  • If using localized approach, ensure server uses the latest private key when rotated. Depending on deployment, the server may need to be restarted.
  • When using remote approach, verify the payload is unchanged after transit. Use integrity validation like CRC32 or SHA256 checks if available.】

本地化签名

【Localized Signing】

auth.ts
jwt({
  jwks: {
    remoteUrl: "https://example.com/.well-known/jwks.json",
    keyPairConfig: {
      alg: 'EdDSA',
    },
  },
  jwt: {
    sign: async (jwtPayload: JWTPayload) => {
      // this is pseudocode
      return await new SignJWT(jwtPayload)
        .setProtectedHeader({
          alg: "EdDSA",
          kid: process.env.currentKid,
          typ: "JWT",
        })
        .sign(process.env.clientPrivateKey);
    },
  },
})

远程签署

【Remote Signing】

如果你正在使用远程密钥管理服务,例如 Google KMSAmazon KMSAzure Key Vault,这将非常有用。

【Useful if you are using a remote Key Management Service such as Google KMS, Amazon KMS, or Azure Key Vault.】

auth.ts
jwt({
  jwks: {
    remoteUrl: "https://example.com/.well-known/jwks.json",
    keyPairConfig: {
      alg: 'ES256',
    },
  },
  jwt: {
    sign: async (jwtPayload: JWTPayload) => {
      // this is pseudocode
      const headers = JSON.stringify({ kid: '123', alg: 'ES256', typ: 'JWT' })
      const payload = JSON.stringify(jwtPayload)
      const encodedHeaders = Buffer.from(headers).toString('base64url')
      const encodedPayload = Buffer.from(payload).toString('base64url')
      const hash = createHash('sha256')
      const data = `${encodedHeaders}.${encodedPayload}`
      hash.update(Buffer.from(data))
      const digest = hash.digest()
      const sig = await remoteSign(digest)
      // integrityCheck(sig)
      const jwt = `${data}.${sig}`
      // verifyJwt(jwt)
      return jwt
    },
  },
})

架构

【Schema】

JWT 插件会向数据库添加以下表格:

【The JWT plugin adds the following tables to the database:】

JWKS

表名:jwks

【Table Name: jwks

Field NameTypeKeyDescription
idstringUnique identifier for each web key
publicKeystring-The public part of the web key
privateKeystring-The private part of the web key
createdAtDate-Timestamp of when the web key was created
expiresAtDateTimestamp of when the web key expires

你可以自定义 jwks 表的表名和字段。有关如何自定义插件架构的更多信息,请参见数据库概念文档

选项

【Options】

密钥对算法

【Algorithm of the Key Pair】

用于生成密钥对的算法。默认是 EdDSA,使用 Ed25519 曲线。以下是可用的选项:

【The algorithm used for the generation of the key pair. The default is EdDSA with the Ed25519 curve. Below are the available options:】

auth.ts
jwt({
  jwks: {
    keyPairConfig: {
      alg: "EdDSA",
      crv: "Ed25519"
    }
  }
})

EdDSA

  • 默认曲线Ed25519
  • 可选属性crv
    • 可用选项:Ed25519Ed448
    • 默认:Ed25519

ES256

  • 无额外属性

RSA256

  • 可选属性modulusLength
    • 期望的是一个数字
    • 默认值:2048

PS256

  • 可选属性modulusLength
    • 期望的是一个数字
    • 默认值:2048

ECDH-ES

  • 可选属性crv
    • 可用选项:P-256P-384P-521
    • 默认:P-256

ES512

  • 无额外属性

禁用私钥加密

【Disable private key encryption】

默认情况下,私钥使用 AES256 GCM 加密。你可以通过将 disablePrivateKeyEncryption 选项设置为 true 来禁用此功能。

【By default, the private key is encrypted using AES256 GCM. You can disable this by setting the disablePrivateKeyEncryption option to true.】

出于安全原因,建议将私钥保持加密状态。

【For security reasons, it's recommended to keep the private key encrypted.】

auth.ts
jwt({
  jwks: {
    disablePrivateKeyEncryption: true
  }
})

密钥轮换

【Key Rotation】

你可以通过设置 rotationInterval 选项来启用密钥轮换。这将按指定的间隔自动轮换密钥对。

【You can enable key rotation by setting the rotationInterval option. This will automatically rotate the key pair at the specified interval.】

默认值是 undefined(禁用)。

【The default value is undefined (disabled).】

auth.ts
jwt({
  jwks: {
    rotationInterval: 60 * 60 * 24 * 30, // 30 days
    gracePeriod: 60 * 60 * 24 * 30 // 30 days
  }
})
  • rotationInterval:轮换密钥对的时间间隔(以秒为单位)。
  • gracePeriod:密钥轮换后保持旧密钥对有效的时间(以秒为单位)。这对于允许客户端验证由旧密钥对签名的令牌非常有用。默认值为30天。

修改 JWT 负载

【Modify JWT payload】

默认情况下,整个用户对象会被添加到 JWT 负载中。你可以通过向 definePayload 选项提供一个函数来修改负载。

【By default the entire user object is added to the JWT payload. You can modify the payload by providing a function to the definePayload option.】

auth.ts
jwt({
  jwt: {
    definePayload: ({user}) => {
      return {
        id: user.id,
        email: user.email,
        role: user.role
      }
    }
  }
})

修改发行者、受众、主题或过期时间

【Modify Issuer, Audience, Subject or Expiration time】

如果未提供,则使用 BASE_URL 作为发行者,并将受众设置为 BASE_URL。过期时间设置为 15 分钟。

【If none is given, the BASE_URL is used as the issuer and the audience is set to the BASE_URL. The expiration time is set to 15 minutes.】

auth.ts
jwt({
  jwt: {
    issuer: "https://example.com",
    audience: "https://example.com",
    expirationTime: "1h",
    getSubject: (session) => {
      // by default the subject is the user id
      return session.user.email
    }
  }
})

自定义适配器

【Custom Adapter】

默认情况下,JWT 插件会从你的数据库中存储和检索 JWKS。你可以提供自定义适配器来覆盖此行为,从而允许你将 JWKS 存储在 Redis、外部服务或内存存储等其他位置。

【By default, the JWT plugin stores and retrieves JWKS from your database. You can provide a custom adapter to override this behavior, allowing you to store JWKS in alternative locations such as Redis, external services, or in-memory storage.】

auth.ts
jwt({
  adapter: {
    getJwks: async (ctx) => {
      // Custom implementation to get all JWKS
      // This overrides the default database query
      return await yourCustomStorage.getAllKeys()
    },
    createJwk: async (ctx, webKey) => {
      // Custom implementation to create a new key
      // This overrides the default database insert
      return await yourCustomStorage.createKey(webKey)
    }
  }
})