@ axa-fr/oidc-客户端
一个用于浏览器应用程序的框架独立OpenID Connect(OIDC)客户端,由TypeScript编写. 它支持带有PKCE的授权代码流程,会话恢复,符号更新,多命名配置,可选服务-工作符号隔离,推出授权请求(PAR)和DPoP.
对于反应应用程序, 使用 @axa-fr/react-oidc。 。 。 .
快开始
npm install @axa-fr/oidc-client
登记a 公共浏览器客户端 与您的身份提供者:
- 启用 PKCE 授权代码流程 .
- 注册您的应用程序的精确回调 URL, 例如
https://app.example.com/authentication/callback,及其日志后 URL . - 允许您的应用程序来源通过 CORS 调用提供者的浏览器访问端点 .
- 选择应用程序所需的范围。 请求
offline_access只有在您的提供者使用它来发布刷新符号时.
永远不要在浏览器代码中设置客户端秘密. 将下面的示例发行者和客户端ID替换为您自己的值 。 配置您的网络服务器, 在调用路由中服务应用程序以及 /。 。 。 .
在您的页面中添加这些元素 :
<p id="session-status" role="status">Loading session…</p>
<button id="login" type="button" hidden>Sign in</button>
<button id="logout" type="button" hidden>Sign out</button>
当这些元素可用时运行此模块, 包括在召回路线上:
import { OidcClient } from '@axa-fr/oidc-client';
const configuration = {
client_id: 'your-public-client',
authority: 'https://issuer.example.com',
redirect_uri: `${window.location.origin}/authentication/callback`,
scope: 'openid profile',
};
const oidcClient = OidcClient.getOrCreate(() => fetch)(configuration);
const status = document.getElementById('session-status');
const loginButton = document.getElementById('login');
const logoutButton = document.getElementById('logout');
function showError() {
status.textContent = 'Authentication could not be completed. Please try again.';
}
loginButton.addEventListener('click', () => {
oidcClient.loginAsync('/').catch(showError);
});
logoutButton.addEventListener('click', () => {
oidcClient.logoutAsync('/').catch(showError);
});
async function start() {
if (window.location.pathname === new URL(configuration.redirect_uri).pathname) {
const { callbackPath } = await oidcClient.loginCallbackAsync();
window.history.replaceState(null, '', callbackPath || '/');
} else {
await oidcClient.tryKeepExistingSessionAsync();
}
const isAuthenticated = oidcClient.tokens != null;
status.textContent = isAuthenticated ? 'Signed in' : 'Not signed in';
loginButton.hidden = isAuthenticated;
logoutButton.hidden = !isAuthenticated;
}
start().catch(showError);
这个最小的例子使用浏览器存储,而不是服务员. 在选择符号存储策略进行生产之前,先阅读下一节.
调用受保护的 API
在认证后使用客户端的抓取包 :
const oidcFetch = oidcClient.fetchWithTokens(fetch);
const response = await oidcFetch('https://api.example.com/profile');
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
const profile = await response.json();
包装器附设了出入证,或者在服务人员隐藏时附设了信使占位符,并与信使更新相融合. 只用于您信任的 API URL: 包件本身不是目的允许列表 。 HTTP 错误响应仍然需要明确 response.ok 检查.
服务工作者
可选服务员可以在应用程序的JavaScript上下文之外保留访问并刷新令牌,并在配置的API请求中附加访问令牌. 没错 没有 防止 XSS: 注入的代码仍然可以通过您的应用程序行事并提出请求. 继续使用普通的XSS防御,小心地限制被信任的目的地,并避免渲染或记录生代币.
sequenceDiagram
participant App as Browser application
participant IdP as Identity provider
participant SW as OIDC service worker
participant API as Trusted API
App->>IdP: Authorization request with PKCE
IdP-->>App: Redirect with authorization code
App->>SW: Token request
SW->>IdP: Exchange code and verifier
IdP-->>SW: Tokens
SW-->>App: Token placeholders and session information
App->>SW: Protected API request
SW->>API: Request with access token
API-->>App: Response through service worker
图表显示工作模式, 启用了访问脚本隐藏 。 ID符和已解码的债权可能仍然可供应用代码使用.
安装和更新工人
node ./node_modules/@axa-fr/oidc-client/bin/copy-service-worker-files.mjs public
替换 public 使用您的应用程序的静态资产目录,并在运行命令前确保目录存在。 此创建或更新 OidcServiceWorker.js 创建 OidcTrustedDomains.js 如果它已经不存在。 使工人版本与客户端软件包保持一致;例如,将这个脚本合并到您的软件包中 package.json数字 :
{
"scripts": {
"postinstall": "node ./node_modules/@axa-fr/oidc-client/bin/copy-service-worker-files.mjs public"
}
}
编辑 public/OidcTrustedDomains.js,而不是产生的工人:
const trustedDomains = {
default: {
oidcDomains: [/^https:\/\/issuer\.example\.com(?:\/|$)/],
accessTokenDomains: [/^https:\/\/api\.example\.com\//],
},
};
包含提供者的端点起源, 如果它们与其发行者 URL 不同 。 尽量缩小接入目标;模式可以包括路径。 字符串条目被解释为正则表达式前缀,而不是精确的出处匹配. 该示例使用有已逃出点和主机名边界的锁定正则表达式来避免出乎意料的匹配. 这个 default 键必须匹配 OIDC 配置名称.
在客户端配置中添加这些选项 :
service_worker_relative_url: '/OidcServiceWorker.js',
service_worker_only: true,
使用 HTTPS( 或本地主机开发) 服务于 URL 的员工, 与 service_worker_only: true,登录需要可用的服务人员。 与 false (默认),当工人模式不可用时,客户端可以倒回浏览器存储. 故意选择后退.
信任域选项
这些选项属于每个命名条目 OidcTrustedDomains.js中,没有 OidcConfiguration。 。 。 .
| 选项 | 目的 |
|---|---|
oidcDomains | 供应商 URL 允许工人处理. |
accessTokenDomains | API URL,工人可以附加访问符. |
domains | 共享提供者/API目的列表的简称;单独列表给出更细的控制. |
showAccessToken | 默认为 false设置为 true 仅在集成需要应用程序 JavaScript 中的真实访问符;刷新符仍然被隐藏在工人模式中. |
allowMultiTabLogin | 孤立登录状态,非ce,和PKCE通过制表符验证. 需要 OIDC 为受保护的 API 请求获取包装; 见下文 . |
convertAllRequestsToCorsExceptNavigate | 默认为 false;可选地将非导航请求更改为 CORS 模式. |
setAccessTokenToNavigateRequests | 默认为 true; 控制匹配导航请求的符号附件. |
bypassAllNonOidcRequests | 默认为 false。当启用所有初始化的工人配置时,OIDC以外的请求和访问到的目标留给浏览器处理. |
demonstratingProofOfPossession | 启用此配置的工人侧 DPOP . |
demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent | 默认为 true 当工人 DPOP 启用时。 需要在工人方 DPOP 注射前使用 DPop 头; 设置为 false 以去除这个条件. |
demonstratingProofOfPossessionConfiguration | 覆盖工人的加密算法;见 DPOP 软件。 。 。 . |
与 allowMultiTabLogin: true, (中文(简体) ), 使用情况 oidcClient.fetchWithTokens(fetch) 保护的 API 请求。它的分页符号占位符告诉工人使用哪个会话。 平面 fetch 或默认的 Axios 请求没有提供该标记,因此自动令牌附件不能选择标签的令牌,请求可能会收到HTTP 401. 使用 OIDC 直接取取取,或者一个实际通过它路径请求的集成.
配置
公众 OidcConfiguration 类型 是完整的参考。 所需字段为 client_id, (中文(简体) ), authority, (中文(简体) ), redirect_uri,以及 scope。 。 。 .
| 选项 | 默认/ 行为 |
|---|---|
authority_configuration | 供应端点元数据而不是发现. 见 AuthorityConfiguration 在类型参考中,包括PAR元数据. |
storage | sessionStorage 在不使用工人存储时. localStorage 在标签和浏览器会话中持续,但应用程序JavaScript都可以访问. |
login_state_storage | 默认为 storage;将授权状态、核查者、非ce和登录参数与符号存储分开。 如果符号使用 localStorage,考虑 sessionStorage 在此避免交叉登录比赛 . |
silent_redirect_uri | 启用基于 Iframe 的静态日志流 。 必须不同于 redirect_uri; 向提供者登记. |
silent_login_uri | 开始无声登录的页面; 来源 silent_redirect_uri 如果省略,则删除. |
silent_login_timeout | 12000 毫秒长. |
refresh_time_before_tokens_expiration_in_second | 120 秒; 当此设置大于60 时, 客户端应用随机抖动 . |
token_renew_mode | 'access_token_or_id_token_invalid'; 替代品为 'access_token_invalid' 财务报告和审定财务报表 'id_token_invalid'。 。 。 . |
token_automatic_renew_mode | TokenAutomaticRenewMode.AutomaticBeforeTokenExpiration; 见 符号更新。 。 。 . |
token_request_timeout | 请求超时数以毫秒计. |
extras | 其他授权要求参数,例如: { prompt: 'consent' }。 。 。 . |
token_request_extras | 附加的代号请求参数 。 永远不要用它来嵌入浏览器客户端的秘密 . |
par, (中文(简体) ), par_request_timeout | 'disabled' 财务报告和审定财务报表 10000 毫秒;见 巴黎。 。 。 . |
authority_time_cache_wellknowurl_in_second | 发现-cache寿命;默认为1小时. |
authority_timeout_wellknowurl_in_millisecond | 发现超时; 默认为 10000 毫秒长. |
monitor_session | false;在提供者和浏览器的支持下,允许 OIDC 会话监测. |
logout_tokens_to_invalidate | ['access_token', 'refresh_token']; 标准登录时要撤销的符号类型. |
preload_user_info | false;在登录/会话恢复时获取用户信息,而不是等待消费者. |
demonstrating_proof_of_possession | false; 见 DPOP 软件。 。 。 . |
demonstrating_proof_of_possession_configuration | 用于客户端 DPOP 的加密设置 . |
service_worker_relative_url | 工人的 URL; 省略以禁用工人模式 . |
service_worker_only | false; 不允许浏览器存储在 true。 。 。 . |
service_worker_keep_alive_path | '/'; 工人保持活动请求所使用的路径. |
service_worker_activate | 用于推翻基于默认浏览器的激活决定的功能 . |
service_worker_register | 自定义 (url) => Promise<ServiceWorkerRegistration> 注册功能. |
loading_timeout_ms | 反应提供者的加载监视器使用;见 反应指南。 。 。 . |
静默登录
静态登录使用iframe和提供者的现有会话. 它依赖于提供者支持和浏览器cookie/privacy政策;它不能保证取代刷新符.
对于香草申请,应执行 silent_login_uri 页 次 silent_redirect_uri 调用页面。 登录页面开始 loginAsync 与 isSilentSignin: true (第三个参数); 回调页面调用 silentLoginCallbackAsync()。保持这些路线与交互式回调分离。 React提供者为您处理这些路由;其 无声日志路由执行 也是一种用于转发静态日志参数的参考.
按键更新
默认情况下,客户端在令牌到期前的续订计划. 它在可用时使用刷新符号,或者配置的静态日志流. 更新式发行和终身制仍然是提供者的决定.
仅在 API 请求需要时更新 :
import { TokenAutomaticRenewMode } from '@axa-fr/oidc-client';
const renewalOptions = {
token_automatic_renew_mode: TokenAutomaticRenewMode.AutomaticOnlyWhenFetchExecuted,
};
将此选项合并到您的配置和使用 fetchWithTokens(fetch) 或者说 getValidTokenAsync() 在受保护的请求之前。 普通获取不会触发此更新模式 .
renewTokensAsync(extras?, scope?)请求更新并保存终端 OIDC 失败时的非投掷行为.renewTokensOrThrowAsync(extras?, scope?)对需要明确故障处理的呼叫者,返回指针或拒绝.- 自动更新报告失败通过 事件。 。 。 .
推进的授权请求( PAR)
PAR(RFC 9126) 在浏览器导航前向提供者发送授权参数.
par 模式 | 行为 |
|---|---|
'disabled' (默认) | 使用普通前道请求并忽略PAR元数据. |
'auto' | 使用已发现或配置 pushed_authorization_request_endpoint。只有在没有终点且服务器不要求PAR时才退后. |
'required' | 需要 PAR 端点; 如果没有, 在导航前失败 . |
如果元数据说 require_pushed_authorization_requests: true, (中文(简体) ), 'auto' 当结束点缺失时也失败。 设定 par_request_timeout 以调整默认的10秒超时.
选择 PAR 后,客户端会发送授权参数(包括状态、非ce、PKCE和登录外加)为 application/x-www-form-urlencoded 数据。 随后的浏览器重定向带 client_id 返回时 request_uri。端点失败或无效响应 从来没有默默地降级 到前通道的流量.
失败使用 PushedAuthorizationRequestError代码 PushedAuthorizationRequestErrorCode数字 : ENDPOINT_UNAVAILABLE, (中文(简体) ), REQUEST_FAILED,或 INVALID_RESPONSE启用 isPushedAuthorizationRequestError(error) 检查 code附加时 status, (中文(简体) ), oauthError,以及 oauthErrorDescription 当可用时.
PAR 端点必须允许 CORS 从您的申请中获取,并接受没有客户秘密的公共客户端. 如果无法发现, 请配置 pushed_authorization_request_endpoint 在适用的情况下, require_pushed_authorization_requests 内 authority_configuration。 。 。 .
DPOP 软件
证明拥有的证据(RFC 9449) 将支持的令牌绑入密钥并使用签名的证明请求. 它需要授权服务器和资源服务器的支持. 它降低了无钥匙被窃取的令牌的用途;它并不能使应用程序免于XSS.
用于客户端的 DPOP, 设置 demonstrating_proof_of_possession: true 在 OIDC 配置中选择保护请求 :
const dpopFetch = oidcClient.fetchWithTokens(fetch, true);
const response = await dpopFetch('https://api.example.com/profile', { method: 'GET' });
同样,通过 true 作为第二个参数 userInfoAsync(false, true) 如果用户信息端点需要 DPOP。 对于工人方 DPOP ,配置 demonstratingProofOfPossession 在匹配的可信任域条目和审查中 demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent。 。 。 .
默认密码配置使用ECDSA P-256,SHA-256,以及 ES256 JWT算法. 覆盖算法通过 demonstrating_proof_of_possession_configuration (客户)或 demonstratingProofOfPossessionConfiguration (工作者). 见 DemonstratingProofOfPossessionConfiguration。 。 。 .
API 密码
见 OidcClient 用于完整类型脚本签名.
| API 密码 | 使用 |
|---|---|
OidcClient.getOrCreate(getFetch, location?)(configuration, name?) | 创建或重用一个命名客户端 。 过 () => fetch 用于浏览器获取执行;默认名称是 'default'。 。 。 . |
OidcClient.get(name?) | 获取已有客户端,或 null 如果没有初始化. |
OidcClient.getOrThrow(name?) | 获取已有客户端, 或丢弃缺失初始化 . |
tryKeepExistingSessionAsync() | 恢复已有会话; 返回是否保留 . |
loginAsync(callbackPath?, extras?, isSilentSignin?, scope?, silentLoginOnly?) | 开始认证 . callbackPath 是调用后的应用程序目的地,而不是注册的 redirect_uri。 。 。 . |
loginCallbackAsync() | 完成交互式认证、开始自动更新和返回 { callbackPath }。 。 。 . |
silentLoginCallbackAsync() | 完成 Iframe 无声记录回调 . |
logoutAsync(callbackPathOrUrl?, extras?) | 清除会话,撤销已配置的令牌,并在可用时使用提供者的登录结束点. |
clearSessionAsync() | 清除本地会话, 无需提供者登录或取消签名 . |
isLoggingOut | 显示登录正在进行;避免启动相竞的登录流量. |
renewTokensAsync(extras?, scope?) | 请求续约 . |
renewTokensOrThrowAsync(extras?, scope?) | 请求续约,如果失败则拒绝承诺. |
getValidTokenAsync(waitMs?, numberWait?) | 等待可用的符; 也支持按需更新 . |
fetchWithTokens(fetch, demonstratingProofOfPossession?) | 为受保护的 API 请求打包抓取 . |
userInfoAsync<T>(noCache?, demonstratingProofOfPossession?) | 装入用户信息,可选择绕过缓存. |
userInfo<T>() | 读取已缓存的用户信息 . |
tokens, (中文(简体) ), configuration | 查看当前会话数据及有效配置. 托肯斯可以包含工人占地符;不显示或登录. |
subscribeEvents(handler) | 订阅 (eventName, data) 并获得订阅身份证. |
removeEventSubscription(id) | 删除事件订阅 . |
OidcClient.eventNames | 发现支持的事件名称 . |
publishEvent(name, data) | 向客户端订阅者发布一个事件 . |
generateDemonstrationOfProofOfPossessionAsync(accessToken, url, method, extras?) | 低等校正生成; 偏好用于普通请求的抓取包 . |
signalServiceWorker(message, options?) | 发送打字的工人-程序信息 . |
您可以在第二个参数中插入自定义位置适配器 getOrCreate,使用导出 ILOidcLocation 接口和 OidcLocation 执行.
错误和事件
已知认证、召回、更新和网络故障的使用 OidcError。检查稳定 code 财务报告和审定财务报表 phase 字段而不是解析信件。 其他领域包括: retryable选项 status, (中文(简体) ), oauthError, (中文(简体) ), oauthErrorDescription,以及 cause。 。 。 .
import { OidcError, OidcErrorCode } from '@axa-fr/oidc-client';
try {
await oidcClient.renewTokensOrThrowAsync();
} catch (error) {
if (error instanceof OidcError && error.code === OidcErrorCode.LOGIN_REQUIRED) {
await oidcClient.loginAsync('/');
} else {
throw error;
}
}
retryable 指没有用户交互的重试可能成功,而不是总是自动进行. 网络故障、HTTP 408/429/5xx反应和DPop非ce挑战可以重试; LOGIN_REQUIRED 需要一个交互式登录.
非抛出流也通过事件暴露出出错误. 更新事件有效载荷包括: error 字段;用户信息、登录和保护的 API 请求有专门的出错事件。 只记录选定的诊断字段, 而非完整的事件有效载荷 :
const subscriptionId = oidcClient.subscribeEvents((name, data) => {
const error = data instanceof OidcError ? data : data?.error;
if (error instanceof OidcError) {
console.warn(name, error.code, error.phase, error.status);
}
});
// When the subscriber is no longer needed:
oidcClient.removeEventSubscription(subscriptionId);
切换同步事件
使用 OidcClient.eventNames 与 subscribeEvents 以观察同步,包括检查在不提出刷新请求的情况下重新使用有效符号。 自动更新检查大约每秒可以发出一次这些事件;浏览器调度和所花的时间同步可以延迟它们.
| 活动 | 有效载荷 | 含义 |
|---|---|---|
syncTokensAsync_begin | {} | 同步开始. |
syncTokensAsync_end | { status } | 同步已解决; 检查 status 确定结果. |
syncTokensAsync_error | { message, error, exception } | 同步启动 . message 实值 'sync tokens error'· ; exception 是原始抛出值,并且 error 同一个值是当它是 OidcError,否则 undefined。 。 。 . |
一个同步运行一个初始事件,然后是结束或出错事件。 内部刷新回放不会释放出额外的生命周期对. 当需要跨塔布锁但无法使用时, syncTokensAsync_lock_not_available 在同步开始前释放,在等待锁时可以重复。 生命周期事件既带有锁又没有锁.
结束活动报告决议,包括下列结果:
status | 含义 |
|---|---|
'LOGGED_IN' | 托肯是有效的, 被刷新过, 或从另一个标签同步 . |
'LOGGED' | 静默地登录提供了符号 . |
'SESSION_LOST' | 同步无法保留会话 。 现有 refreshTokensAsync_error 事件带有更新错误. |
'LOGGED_OUT' | 同步检测到登录或缺少存储的令牌 . |
'GIVE_UP' | 延期被推迟,例如,在隐藏文档中离线或等待API请求触发延期时. 这不能确定符号的有效性. |
null | 没有当前符号可以同步 . |
已解决的失败, 例如 'SESSION_LOST' 发出一个结束事件而不是同步出错事件. 出错事件后,原有的同步例外被重排;没有结束事件随之而来.
这些事件涵盖了同步本身. 初始工人设置和锁定获取发生在开始事件之前;随后的存储更新,会话销毁,以及定时器调度发生在结束事件之后. 这些步骤的失败都发生在这个事件边界之外,所以一个结束事件并不能保证续召成功.
保持订阅者轻量级且不丢出:用户例外发布并可以中断更新. 只记录选定的诊断字段 。 出错有效载荷包含原始例外,其性质或原因可能包括敏感数据;不记录完整的有效载荷、例外或符号.
import { isOidcError, OidcClient } from '@axa-fr/oidc-client';
// Use an already initialized client.
const oidcClient = OidcClient.getOrThrow();
const subscriptionId = oidcClient.subscribeEvents((name, data) => {
if (name === OidcClient.eventNames.syncTokensAsync_end) {
console.debug(name, data.status);
} else if (name === OidcClient.eventNames.syncTokensAsync_error) {
if (isOidcError(data.error)) {
console.warn(name, data.error.code, data.error.phase, data.error.status);
} else {
console.warn(name);
}
}
});
// When the subscriber is no longer needed:
oidcClient.removeEventSubscription(subscriptionId);
反应应用可以使用供应商的 onEvent(configurationName, eventName, data) 调用。 见 反应错误处理 用于会议损失反应.
缺少或错配的登录状态
OidcStateError 扩展 OidcError 并识别回调状态失败 :
OidcStateErrorCode.STATE_MISSING: 缺少存储的授权状态 .OidcStateErrorCode.STATE_MISMATCH: 返回状态不匹配存储状态 .OidcStateErrorCode.NONCE_MISSING: 缺少存储的非ce .
使用 isOidcStateError(error) 检查 error.code尤其对于通过无声记录处理错误而言 储存清理、驱逐和相互竞争的登录尝试都会导致这些失败。 不绕过状态或非ce检查; 提供新登录 。 在无声续展期间,通过会话丢失的流量报告缺失的非ce.
命名配置和路由
对不同范围的单独提供者或会话使用不同的名称:
const paymentsClient = OidcClient.getOrCreate(() => fetch)(paymentsConfiguration, 'payments');
const existingClient = OidcClient.get('payments');
每个名称都重新使用其初始配置. 给配置指定回调路径并匹配密钥 OidcTrustedDomains.js。 。 。 .
库保留了hash-route回调匹配的后遗症集成. 然而,OAuth重定向URI不能包含一个片段:使用基于路径的回调URL,就像快速起动一样,用于新的部署. 现有的散列召回设置取决于提供者的特定行为,需要匹配应用程序的路由. 交互和无声回调 URL 必须不同 .
服务人员协议
工人们的版本 postMessage 协议在 PROTOCOL.md。常数和帮助者,如 PROTOCOL_VERSION, (中文(简体) ), ServiceWorkerMessageType, (中文(简体) ), TOKEN_PLACEHOLDERS,以及 buildSecuredTokenPlaceholder 从 @axa-fr/oidc-client 财务报告和审定财务报表 @axa-fr/oidc-client-service-worker/protocol。 。 。 .
import { OidcClient, ServiceWorkerMessageType } from '@axa-fr/oidc-client';
const client = OidcClient.getOrThrow();
const result = await client.signalServiceWorker<{ state: string }>({
type: ServiceWorkerMessageType.GET_STATE,
configurationName: 'default',
data: null,
});
这需要一个活跃的工人。 见关于有效载荷、超时和兼容性保证的协议参考;避免包含会话状态的日志协议响应.
实例和进一步阅读
- 香草 JavaScript 演示 ——全面应用和地方发展指示.
- 反应指南 财务报告和审定财务报表 反应演示。 。 。 .
- 财务问题和部署指导。 。 。 .
- 服务提供者来源和协议。 。 。 .
- 成套服务人员指南。 。 。 .
演示属于pnpm单曲. 跟踪他们的README,而不是独立安装每个工作空间.
用于源开发, 使用根 贡献命令 和已死的锁文件。 保持本套件的Vitest和覆盖版本对齐.