跳到主要内容

创建自定义访问令牌脚本

要添加自定义声明到访问令牌 (Access token),你需要提供一个返回包含这些声明的对象的脚本。该脚本应编写为一个返回自定义声明对象的 JavaScript 函数。

  1. 进入 控制台 > 自定义 JWT。

  2. 你可以为两种不同类型的访问令牌自定义访问令牌声明:

    • 用户访问令牌:为终端用户签发的访问令牌。例如,用于 Web 应用或移动应用。
    • 机器对机器访问令牌:为服务或应用程序签发的访问令牌。例如用于机器对机器应用。

    不同类型的访问令牌可能具有不同的令牌负载上下文。你可以分别为每种类型的访问令牌自定义令牌声明。

    选择你想自定义令牌声明的访问令牌类型,并点击 添加自定义声明 按钮来创建新脚本。

备注:

自定义令牌声明功能仅对以下用户开放:

实现 getCustomJwtClaims() 函数​

在 自定义 JWT 详情页,你可以找到脚本编辑器来编写你的自定义令牌声明脚本。该脚本应为一个返回自定义声明对象的 JavaScript 函数。

自定义令牌声明详情页

步骤 1:编辑脚本​

使用左侧的代码编辑器修改脚本。系统为你提供了一个默认返回空对象的 getCustomJwtClaims,你可以在此基础上修改函数以返回你自己的自定义声明对象。

const getCustomJwtClaims = async ({ token, context, environmentVariables }) => {
return {};
};

该编辑器使用 JavaScript 语言服务器,提供基础语法高亮、代码补全和错误检查。输入参数类型良好,并采用 jsDoc 风格文档。你可以利用编辑器的 IntelliSense 正确访问输入对象的属性。详细的参数定义可在页面右侧查看。

备注:

该函数将作为模块导出。请确保函数名保持为 getCustomJwtClaims,以便模块能正确导出该函数。

在用户访问令牌中,认证上下文声明 acr、amr 和 auth_time 被 Logto 保留(参见增强认证 (Step-up authentication))。如果你的脚本返回这些键,Logto 会忽略它们并保留其派生的认证上下文。

步骤 2:输入参数​

getCustomJwtClaims 函数接收一个对象作为输入参数。该输入对象包含以下属性:

token​

令牌负载对象。该对象包含原始令牌声明和你可能需要在脚本中访问的元数据。

你可以在页面右侧找到令牌负载对象和用户数据对象的详细类型定义。编辑器的 IntelliSense 也会帮助你正确访问输入对象的这些属性。

  • 用户访问令牌数据对象
    属性描述类型
    jti唯一 JWT idstring
    aud令牌的受众 (Audience)string
    scope令牌的权限 (Scopes)string
    clientId令牌的客户端 idstring
    accountId令牌的用户 idstring
    expiresWithSession令牌是否随会话过期boolean
    grantId令牌当前认证授权 idstring
    gty令牌的授权类型string
    kind令牌类型AccessToken
  • 机器对机器访问令牌数据对象
    属性描述类型
    jti唯一 JWT idstring
    aud令牌的受众 (Audience)string
    scope令牌的权限 (Scopes)string
    clientId令牌的客户端 idstring
    kind令牌类型ClientCredentials

context(仅用户访问令牌可用)​

context 对象包含与当前授权 (Authorization) 过程相关的用户数据和授权数据。

  • 用户数据对象 对于用户访问令牌,Logto 提供了额外的用户数据上下文供你访问。用户数据对象包含你设置自定义声明可能需要的所有用户资料数据和组织成员数据。请参阅 用户 和 组织 (Organizations) 获取更多信息。

  • 组织数据对象 对于组织访问令牌(为特定组织请求的访问令牌),Logto 通过 context.organization 提供目标组织,因此你可以附加每个组织的声明——例如,将 Logto 组织 ID 映射到存储在组织自定义数据中的内部 ID。该数据对象仅在为特定组织签发令牌时存在,包含:

    属性描述类型
    id唯一组织 idstring
    name组织名称string
    description组织描述string | null
    customData组织自定义数据Record<string, unknown>

    详情请参阅 组织 (Organizations)。

  • 授权数据对象 对于通过模拟令牌交换授权的用户访问令牌,Logto 提供了额外的授权数据上下文供你访问。授权数据对象包含来自主体令牌的自定义上下文。详情请参阅 用户模拟。

  • 用户交互数据对象 对于给定的用户访问令牌,有时你需要访问用户在当前授权 (Authorization) 会话中的交互详情。例如,你可能需要获取用户用于登录的企业单点登录 (Enterprise SSO) 身份。该用户交互数据对象包含最近用户提交的交互数据,包括:

    属性描述类型
    interactionEvent当前用户交互的事件SignIn 或 Register
    userId当前用户交互的用户 idstring
    verificationRecords用户在交互过程中提交的用于识别和验证身份的验证记录列表。VerificationRecord[]

    验证记录类型:

    // VerificationType.Password
    {
    id: string;
    type: 'Password';
    identifier: {
    type: 'username' | 'email' | 'phone' | 'userId';
    value: string;
    }
    verified: boolean;
    }
    // VerificationType.EmailVerificationCode
    {
    id: string;
    templateType: 'SignIn' | 'Register' | 'ForgotPassword' | 'Generic';
    verified: boolean;
    type: 'EmailVerificationCode';
    identifier: {
    type: 'email';
    value: string;
    }
    }
    // VerificationType.PhoneVerificationCode
    {
    id: string;
    templateType: 'SignIn' | 'Register' | 'ForgotPassword' | 'Generic';
    verified: boolean;
    type: 'PhoneVerificationCode';
    identifier: {
    type: 'phone';
    value: string;
    }
    }
    // VerificationType.Social
    {
    id: string;
    type: 'Social';
    connectorId: string;
    socialUserInfo?: {
    id: string;
    email?: string | undefined;
    phone?: string | undefined;
    name?: string | undefined;
    avatar?: string | undefined;
    rawData?: Record<string, unknown> | undefined;
    } | undefined;
    }
    // VerificationType.EnterpriseSso
    {
    id: string;
    type: 'EnterpriseSso';
    connectorId: string;
    enterpriseUserInfo?: {
    id: string;
    email?: string | undefined;
    phone?: string | undefined;
    name?: string | undefined;
    avatar?: string | undefined;
    [key: string]?: unknown;
    } | undefined;
    issuer?: string | undefined;
    }
    // VerificationType.Totp (MFA)
    {
    id: string;
    type: 'Totp';
    userId: string;
    verified: boolean;
    }
    // VerificationType.WebAuthn (MFA)
    {
    id: string;
    type: 'WebAuthn';
    userId: string;
    verified: boolean;
    }
    // VerificationType.BackupCode (MFA)
    {
    id: string;
    type: "BackupCode";
    userId: string;
    code?: string | undefined;
    }
    // VerificationType.OneTimeToken
    {
    id: string;
    type: "OneTimeToken";
    verified: boolean;
    identifier: {
    type: "email";
    value: string;
    };
    oneTimeTokenContext?: {
    jitOrganizationIds?: string[] | undefined;
    } | undefined;
    }
    备注:

    用户交互数据对象中可能存在多条验证记录,尤其是在用户经历了多次登录或注册流程时。

    例如,用户先通过 Social 验证记录登录,然后通过 EmailVerificationCode 验证记录绑定新邮箱,再通过 Totp 验证记录验证 MFA 状态。在这种情况下,你可能需要在脚本中分别处理所有验证记录。

    每种类型的验证记录在用户交互数据对象中只会出现一次。

environmentVariables​

使用右侧的 设置环境变量 区域为你的脚本设置环境变量。你可以用这些变量存储敏感信息或配置数据,避免在脚本中硬编码。例如 API 密钥、密钥或 URL。

你在此处设置的所有环境变量都可在脚本中使用。通过输入参数中的 environmentVariables 对象访问这些变量。

api​

api 对象为你的脚本提供了一组实用函数,用于对令牌签发过程进行额外访问控制。api 对象包含以下函数:

api.denyAccess(message?: string): void

api.denyAccess() 函数允许你通过自定义消息拒绝令牌签发过程。你可以用此函数对令牌签发过程施加额外访问校验。

运行时 API​

自定义访问令牌脚本在 Logto Cloud 和自托管 Logto 中均可使用以下标准 Web API:

  • fetch、Request、Response 和 Headers
  • 通过 crypto 和 crypto.subtle 使用 Web Crypto
  • TextEncoder 和 TextDecoder
  • URL 和 URLSearchParams

例如,你可以用 Web Crypto 计算 SHA-256 和 HMAC-SHA-256 值:

const toHex = (buffer) =>
[...new Uint8Array(buffer)].map((byte) => byte.toString(16).padStart(2, '0')).join('');

const sha256 = async (input) =>
toHex(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(input)));

const hmacSha256 = async (key, input) => {
const encoder = new TextEncoder();
const cryptoKey = await crypto.subtle.importKey(
'raw',
encoder.encode(key),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);

return toHex(await crypto.subtle.sign('HMAC', cryptoKey, encoder.encode(input)));
};

脚本不能导入包。请避免使用 Node.js 特有的全局变量和模块,因为它们在自托管 Logto 和 Logto Cloud 之间不可移植,也不属于支持的脚本约定。

步骤 3:获取外部数据​

你可以在脚本中使用标准的 fetch 函数获取外部数据。fetch 是一个基于 Promise 的函数,允许你向外部 API 发起 HTTP 请求。

const getCustomJwtClaims = async ({ environmentVariables }) => {
const response = await fetch('https://api.example.com/data', {
headers: {
Authorization: `Bearer ${environmentVariables.API_KEY}`,
},
});

const data = await response.json();

return {
data,
};
};
备注:

请注意,任何外部数据获取都可能为令牌签发过程引入延迟。确保外部 API 足够可靠且响应速度能满足你的需求。

此外:

  • 在脚本中妥善处理错误和超时,避免令牌签发过程被阻塞。
  • 使用合适的授权头保护你的外部 API,防止未授权访问。

步骤 4:测试脚本​

请务必在保存前测试你的脚本。点击页面右侧的 测试上下文 标签,修改用于测试的模拟令牌负载和用户数据上下文。

点击编辑器右上角的 运行测试,用模拟数据运行脚本。脚本的输出将在 测试结果 抽屉中显示。

测试自定义 JWT 脚本
备注:

测试结果是你设置的模拟数据下 getCustomJwtClaims 函数的输出(即在时序图第 3 步获得的“额外令牌声明”)。实际令牌负载和用户数据上下文在令牌签发过程中会有所不同。

点击 创建 按钮保存脚本。自定义令牌声明脚本将被保存并应用于访问令牌签发流程。