Logto OAuth 标准连接器接入指南:对接任意 OAuth 2.0 身份提供商的完整配置与实现解析
【免费下载链接】logto🧑🚀 Authentication and authorization infrastructure for SaaS and AI apps, built on OIDC and OAuth 2.1 with multi-tenancy, SSO, and RBAC.项目地址: https://gitcode.com/GitHub_Trending/lo/logto
导读
本文围绕 Logto 的 OAuth 标准连接器(connector-oauth2)展开,它让 Logto 能够对接任意遵循 OAuth 2.0 协议的社会化身份提供商(IdP),为你的应用添加社交登录按钮、绑定社交身份、同步用户资料,并通过安全的令牌存储访问第三方 API。读完本文,你将掌握该连接器的全部配置项(含profileMap嵌套属性映射与customConfig自定义参数)、三种令牌端点客户端认证方式的选择,以及其背后 Authorization Code 授权码流程的源码级实现原理。
OAuth 连接器是什么:一个可复用的"通用协议连接器"
OAuth 标准连接器是 Logto 官方提供的一类特殊连接器,其定位不同于按厂商定制(如 Google、GitHub)的专属连接器:它只要求身份提供商支持 OAuth 2.0 协议,而不要求它出现在 Logto 的预置列表中。也就是说,你可以在一个租户内同时添加多个基于 OAuth 协议的自定义连接器,分别对接不同的 IdP。
借助它,你的应用可以:
- 在登录页添加对应 IdP 的社交登录按钮;
- 将用户账号与社交身份绑定(link/unlink);
- 从社交提供商同步用户资料(如昵称、头像、邮箱);
- 通过 Logto 将访问令牌安全存入 Secret Vault,用于后台自动化任务(例如代替用户编辑 Google Docs、管理日历事件)——前提是你在授权时向 IdP 请求了对应的 API scope。
在 连接器常量定义 中可以看到该连接器的元数据特征:platform为Universal(全平台通用)、isStandard: true(标准协议连接器)、isTokenStorageSupported: true(支持令牌存储),这正是它与定制连接器的关键区别。其包名为@logto/connector-oauth,实现位于 packages/connectors/connector-oauth2。
前置准备:创建你的 OAuth 应用
在配置连接器之前,你需要先确认目标身份提供商支持 OAuth 2.0 协议——这是配置有效连接器的前提。随后,按照该 IdP 的官方文档注册并创建一个用于 OAuth 授权的应用,通常会得到clientId与clientSecret两项凭据。
注意:注册应用时通常需要填写回调(redirect URI)地址。Logto 会在授权流程启动时将会话中的
redirectUri传给授权端点(见下文源码解析),请在 IdP 侧将此回调地址加入白名单。
配置你的连接器:核心参数逐一拆解
支持的授权类型:仅 Authorization Code
出于安全考虑,该连接器只支持 Authorization Code(授权码)授权类型,它可以完美契合 Logto 的认证场景。从 oauth2 配置 Guard 的源码可以看到,responseType被限定为字面量'code'(默认值'code'),grantType被限定为字面量'authorization_code'(默认值'authorization_code'),二者在配置校验时即被锁定,无法改成隐式授权(implicit)等其他模式。
客户端凭据:clientId 与 clientSecret
clientId与clientSecret可在你的 OAuth 应用详情页找到:
- clientId:客户端在授权服务器注册时获得的唯一标识。授权服务器用它验证客户端身份,并把后续签发的访问令牌关联到该客户端应用。
- clientSecret:注册时由授权服务器签发给客户端的机密密钥。客户端在请求访问令牌时用它向授权服务器证明自己的身份。它属于机密信息,必须始终安全保管,绝不可泄露到前端代码或公开仓库。
在表单定义(form-items.ts)中,二者均为必填的文本输入项。
令牌端点认证方式:tokenEndpointAuthMethod
客户端在令牌端点请求访问令牌时,需要先向授权服务器证明自己的身份,tokenEndpointAuthMethod即用于指定这一认证方式。其支持的可选值来自源码中的TokenEndpointAuthMethod枚举(见 oauth2/types.ts):
| 取值 | 含义 | 默认值 |
|---|---|---|
client_secret_basic | 将clientId:clientSecret以 Basic Auth 形式放入Authorization请求头 | 否 |
client_secret_post | 将clientId与clientSecret直接放入令牌请求的表单体 | 是 |
client_secret_jwt | 用 clientSecret 作为 HMAC 密钥签名一个 JWT 断言(client_assertion)进行认证 | 否 |
要确定你的 IdP 支持哪些方式,可以查询其 OpenID Connect 发现端点返回的token_endpoint_auth_methods_supported字段,或直接查阅该 IdP 的官方文档。三种方式在 requestTokenEndpoint 实现 中均有对应分支,细节见后文"源码深度解析"。
JWT 签名算法:clientSecretJwtSigningAlgorithm(可选)
该参数仅在tokenEndpointAuthMethod为client_secret_jwt时需要。它指定客户端在令牌请求中签署 JWT 所使用的算法。源码中的ClientSecretJwtSigningAlgorithm枚举只提供三个 HMAC 系列算法:
HS256(默认值):HMAC 使用 SHA-256;HS384:HMAC 使用 SHA-384;HS512:HMAC 使用 SHA-512。
选择 HMAC 系列的原因在于:client_secret_jwt场景下 clientSecret 本身作为对称密钥即可完成签名与校验,无需像 RS256/ES256 那样管理公钥分发基础设施。在控制台表单中,该选项带有显示条件(showConditions),只有当tokenEndpointAuthMethod选择client_secret_jwt时才会出现。
请求权限范围:scope
scope用于声明客户端希望访问的资源与权限集合,通常是一个以空格分隔的字符串列表。例如read write表示请求对用户数据的读、写访问权。在表单中它是一个多行文本输入项(MultilineText),占位提示为"按空格分隔输入多个 scope"。
scope 的写法决定了你后续能同步到什么用户资料、能调用哪些 IdP API,因此请对照 IdP 文档准确填写。若还需要刷新令牌以持久访问,则必须包含offline_access(详见下文"存储令牌"小节)。
三个端点:authorizationEndpoint、tokenEndpoint、userInfoEndpoint
你需要在 IdP 的文档中找到以下三个端点并填入配置:
- authorizationEndpoint(认证端点,必填):用于发起认证流程。用户在此登录并授权客户端访问其资源。
- tokenEndpoint(令牌端点):客户端携带授权码向此端点换取访问令牌。注意:该字段在 README 的 Config types 表格中标记为
false(非必填),但在 oauth2ConfigGuard 中实际是必填字符串,且表单中默认即要求填写——配置时请务必提供,否则换取令牌会失败。 - userInfoEndpoint(用户信息端点,必填):客户端取得访问令牌后,用令牌从此端点获取用户的附加信息(如全名、邮箱、头像)。
用户资料映射:profileMap 与嵌套属性
OAuth 2.0 本身不规定用户资料的标准结构,不同 IdP 返回的 profile 字段千差万别。为此 Logto 提供了profileMap字段,允许你把 IdP 返回的字段名映射到 Logto 的标准用户资料字段上:键是 Logto 的标准字段名,值是对应 IdP profile 中的字段名。
当前阶段,Logto 只关注 IdP profile 中的 5 个字段:id、name、avatar、email、phone。其中只有id是必需的(它是社交身份的唯一标识,缺失会导致映射失败),其余均为可选字段。
profileMap还支持嵌套属性:可以用点号(.)路径把 IdP profile 中任意层级的嵌套属性取出来映射到 Logto 字段。例如,假设 IdP 返回如下嵌套结构:
{ "id": "123456", "contact": { "email": "octcat@github.com", "phone": "123-456-7890" }, "details": { "name": "Oct Cat", "avatar": { "url": "avatar.png" }, "groups": ["group1", "group2", "group3"] } }可以这样配置profileMap:
{ "id": "id", "name": "details.name", "avatar": "details.avatar.url", "email": "contact.email", "phone": "contact.phone" }其中details.name、details.avatar.url、contact.email、contact.phone都是对嵌套属性的点号路径引用。源码层面,这一机制由 userProfileMapping 实现:它通过getSafe(originUserProfile, source)按点号路径取值,再过滤掉空值后用userProfileGuard做类型校验(id允许字符串或数字,数字会被String()转为字符串)。对应的单元测试见 utils.test.ts,其中专门覆盖了嵌套属性映射的用例。
各字段的默认值如下(来自 profileMapGuard):
| ProfileMap 字段 | 类型 | 必填 | 默认值 |
|---|---|---|---|
| id | string | false | id |
| name | string | false | name |
| avatar | string | false | avatar |
| string | false | ||
| phone | string | false | phone |
自定义参数:customConfig(可选)
每个 IdP 都可能在标准 OAuth 协议之外有自己的变体(例如授权请求需要额外的固定参数、令牌请求需要携带渠道标识等)。为此连接器提供了一个可选的customConfig键(类型为Record<string, string>),用于放入你的自定义参数。
如果你的 IdP 严格遵循 OAuth 标准协议,就完全不需要关心customConfig。反之,你可以把 IdP 要求的附加参数写在这里——源码中,customConfig会被展开合并进授权 URL 查询参数(见 getAuthorizationUri 中的...customConfig)以及令牌请求表单体(见 getAccessToken 中的...customConfig)。
Config types 总表
综合 README 与 oauth2ConnectorConfigGuard 的定义,完整配置项如下:
| 名称 | 类型 | 必填 |
|---|---|---|
| authorizationEndpoint | string | true |
| userInfoEndpoint | string | true |
| clientId | string | true |
| clientSecret | string | true |
| tokenEndpointResponseType | enum | false |
| responseType | string | false |
| grantType | string | false |
| tokenEndpoint | string | false |
| scope | string | false |
| customConfig | Record<string, string> | false |
| profileMap | ProfileMap | false |
其中tokenEndpointResponseType的取值只有query-string(默认)与json,它决定了令牌端点的响应体格式如何解析(见后文源码解析)。一个最小可用的配置示例如下(参考 mock.ts):
{ "authorizationEndpoint": "https://provider.example.com/oauth/authorize", "tokenEndpoint": "https://provider.example.com/oauth/token", "userInfoEndpoint": "https://provider.example.com/userinfo", "clientId": "your-client-id", "clientSecret": "your-client-secret", "tokenEndpointResponseType": "json", "profileMap": { "id": "sub" } }通用设置:不阻塞连接,但影响用户体验
以下设置不会影响与 IdP 的连通性,但会显著影响终端用户的认证体验。
社交按钮名称与 Logo
如果你想在登录页展示一个社交登录按钮,可以为该连接器设置名称以及浅色/深色两套 Logo(在 constant.ts 中,logo与logoDark字段即为浅色与深色模式的图标资源)。这有助于用户一眼认出社交登录选项。
身份提供商名称(IdP Name)
每个社交连接器都有一个唯一的 IdP 名称(target)用于区分用户身份。常见的预置连接器使用固定的 IdP 名称,而自定义连接器必须使用唯一值——因为你要在一个租户里添加多个 OAuth 连接器,各自的target不能重复。这也是文档强调"可以添加多个 OAuth 协议连接器"时最容易踩的坑。
同步资料策略
在 OAuth 连接器中,你可以设置用户资料(如昵称、头像)的同步策略,二选一:
- Only sync at sign-up(仅在注册时同步):用户首次登录时拉取一次资料;
- Always sync at sign-in(每次登录都同步):用户每次登录都更新资料。
存储令牌以访问第三方 API(可选)
如果你想在用户授权的前提下访问 IdP 的 API 并代替用户执行操作(无论该身份是通过社交登录还是账号绑定时获得的),Logto 需要拿到特定的 API scope 并把令牌存储起来。步骤如下:
- 按前文说明,在scope字段中添加所需的权限范围;
- 在 Logto 控制台的该连接器上,开启Store tokens for persistent API access(存储令牌以持久访问 API)。Logto 会把访问令牌安全存入 Secret Vault;
- 对于标准OAuth/OIDC 身份提供商,scope 中必须包含
offline_access才能拿到刷新令牌,从而避免用户反复被要求授权。
连接器元数据中的isTokenStorageSupported: true即表明该连接器具备这一能力。
使用 OAuth 连接器
创建并配置好连接器后,你可以按需把它接入终端用户流程。
开启社交登录按钮
- 在 Logto 控制台进入Sign-in experience(登录体验)> Sign-up and sign-in(注册与登录)页面;
- 在Social sign-in(社交登录)区域添加该 OAuth 连接器,让用户可以使用你的 IdP 账号认证。
关于社交登录在端侧的实际交互流程,可进一步阅读仓库中的 end-user-flows/sign-in-flow.md 与 end-user-flows/register-flow.md。
绑定或解绑社交账号
可以使用 Account API 在你的应用内构建自定义账户中心,让已登录用户绑定或解绑社交账号。
提示:也可以只把 OAuth 连接器用于账号绑定与 API 访问,而不启用社交登录按钮——两者相互独立。
访问 IdP API 并执行操作
你的应用可以从 Secret Vault 取回已存储的访问令牌,调用 IdP 的 API 并自动化后端任务。具体能做什么,取决于 IdP 的能力以及你请求的 scope。
管理用户的社交身份
用户绑定社交账号后,管理员可以在 Logto 控制台管理该连接:
- 进入Logto 控制台 > 用户管理,打开目标用户的资料页;
- 在Social connections(社交连接)下找到对应 IdP 条目,点击Manage(管理);
- 在该页面中,管理员可以管理用户的社交连接、查看从社交账号授权并同步过来的全部资料,以及检查访问令牌的状态(如是否有效、是否过期)。
注意:少数 IdP 的访问令牌响应不包含 scope 信息,因此 Logto 无法直接展示用户授予的权限列表。但只要用户在授权时已同意所请求的 scope,你的应用在调用 OAuth API 时便拥有对应权限,不受此展示限制。
源码深度解析:授权码流程在 Logto 中如何落地
为帮助你更自信地排查问题,下面把连接器的关键实现链路串起来(入口见 src/index.ts,通用逻辑见 oauth2/utils.ts 与 src/utils.ts)。
第一步:构造授权 URL
getAuthorizationUri读取并校验配置后,把responseType、clientId、scope、redirectUri、state以及customConfig展开合并,交给 constructAuthorizationUri 拼装查询参数。值得注意的是两点:
- 若调用方(如上层应用)显式传入了
scope,它会覆盖配置里的 scope; - 所有参数会先经
snakecaseKeys转成 snake_case(如redirectUri→redirect_uri)、再剔除 undefined 值,与 OAuth 规范的标准参数名保持一致; - 初始化的
state由 Logto 生成用于防 CSRF,会话中的redirectUri会被暂存,供回调阶段取回。
第二步:授权码换令牌
用户完成授权后跳回 Logto 回调地址,getAccessToken先用oauth2AuthResponseGuard校验回调数据中的code(以及可选的state),然后调用requestTokenEndpoint请求令牌端点。这一步根据tokenEndpointAuthMethod有三种实现分支:
client_secret_post:把grant_type、code、redirect_uri、client_id、client_secret全部放进表单体提交;client_secret_basic:表单体只放业务参数,凭据以Basic base64(clientId:clientSecret)形式放入Authorization头;client_secret_jwt:用 clientSecret 作为 HMAC 密钥,通过jose库签署一个 JWT 断言——JWT 的iss/sub为 clientId,aud为令牌端点 URL,有效期 10 分钟,并附带jti;随后以client_assertion+client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer的形式提交(符合 RFC 7523 第 2.2 节)。
所有令牌请求参数同样会被自动转为 snake_case 并去除空值。若 IdP 返回 HTTP 错误,会被包装为ConnectorError抛出,便于上层统一处理。
第三步:解析令牌响应
accessTokenResponseHandler根据配置的tokenEndpointResponseType决定解析方式:json走parseJson,query-string则用query-string库按表单格式解析,两者结果都交给oauth2AccessTokenResponseGuard校验。校验通过后必须存在access_token,否则抛出SocialAuthCodeInvalid错误。令牌响应中的refresh_token、expires_in、scope均为可选字段,为后续刷新令牌流程保留。
第四步:获取并映射用户资料
_getUserInfo携带token_type + access_token作为Authorization头请求userInfoEndpoint,超时时间固定为 5 秒(defaultTimeout = 5000,见 constant.ts)。返回的 profile 经userProfileMapping按profileMap映射为标准字段,同时保留rawData原始数据供上层使用。
第五步:刷新令牌
getAccessTokenByRefreshToken以grant_type=refresh_token+refresh_token请求令牌端点,同样经过requestTokenEndpoint的三种认证分支与响应解析,用于在访问令牌过期后无感续期。这一能力正是"存储令牌以持久访问第三方 API"的底层支撑。
测试用例佐证
仓库为上述核心逻辑提供了完整的单测:userProfileMapping的字符串/数字 id 转换、空值与未映射字段的过滤、嵌套属性点号路径取值等行为,均可在 utils.test.ts 与 oauth2/utils.test.ts 中看到对应用例,可作为你配置profileMap时判断预期行为的参考。
参考
- RFC 6749《The OAuth 2.0 Authorization Framework》定义了本文涉及的授权码流程与客户端认证规范;
- RFC 7523 第 2.2 节规定了
client_secret_jwt中client_assertion_type的标准取值; - 连接器完整实现与测试:
packages/connectors/connector-oauth2; - 端侧交互流程文档:end-user-flows/sign-in-flow.md、end-user-flows/register-flow.md、end-user-flows/consent-flow.md。
【免费下载链接】logto🧑🚀 Authentication and authorization infrastructure for SaaS and AI apps, built on OIDC and OAuth 2.1 with multi-tenancy, SSO, and RBAC.项目地址: https://gitcode.com/GitHub_Trending/lo/logto
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考