@nihplod/sso-sdk 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -9
- package/dist/index.d.mts +136 -13
- package/dist/index.d.ts +136 -13
- package/dist/index.js +475 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +467 -77
- package/dist/index.mjs.map +1 -1
- package/dist/next/index.d.mts +54 -6
- package/dist/next/index.d.ts +54 -6
- package/dist/next/index.js +355 -85
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +351 -84
- package/dist/next/index.mjs.map +1 -1
- package/dist/react/index.d.mts +175 -14
- package/dist/react/index.d.ts +175 -14
- package/dist/react/index.js +627 -151
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +630 -155
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ const sso = new SsoClient({
|
|
|
23
23
|
await sso.login();
|
|
24
24
|
|
|
25
25
|
// 2. Handle callback (inside the callback page)
|
|
26
|
+
// Token is persisted to sessionStorage by default, so it survives
|
|
27
|
+
// the full-page redirect back from the SSO center.
|
|
26
28
|
const token = await sso.handleCallback(window.location.href);
|
|
27
29
|
|
|
28
30
|
// 3. Get user info
|
|
@@ -60,6 +62,8 @@ Initiate SSO login. Generates PKCE parameters and redirects to the SSO login pag
|
|
|
60
62
|
|
|
61
63
|
Build the login URL string without redirecting. Returns `Promise<string>`.
|
|
62
64
|
|
|
65
|
+
> ⚠️ Do NOT mix `getLoginUrl()` with `login()` for the same login attempt: both regenerate and overwrite the `state` / PKCE verifier in sessionStorage, so the flow started first will fail with a state mismatch. Use only one entry point per login.
|
|
66
|
+
|
|
63
67
|
### `sso.handleCallback(callbackUrl)`
|
|
64
68
|
|
|
65
69
|
Handle the OAuth callback. Parses `code` and `state` from the URL, validates state, and exchanges the code for tokens.
|
|
@@ -113,13 +117,26 @@ Clear local token data and attempt to revoke the server-side refresh_token.
|
|
|
113
117
|
|------|------|------|------|
|
|
114
118
|
| `redirectToSso` | `boolean` | `false` | Whether to redirect to the SSO logout page (OIDC RP-Initiated Logout) |
|
|
115
119
|
|
|
116
|
-
When `redirectToSso=true`, the user is redirected to `/
|
|
120
|
+
When `redirectToSso=true`, the user is redirected to the `end_session_endpoint` from OIDC Discovery (fallback: `/api/oauth/end-session`) with `client_id`, `post_logout_redirect_uri`, `id_token_hint` and `state` parameters. The generated `state` is saved to sessionStorage; the main site clears the session and then returns to the sub-project callback address. Validate the `state` on the return page with `sso.validateLogoutState()` to prevent logout CSRF.
|
|
121
|
+
|
|
122
|
+
### `sso.validateLogoutState(url)`
|
|
123
|
+
|
|
124
|
+
Validate the `state` parameter of an RP-Initiated Logout redirect-back (logout CSRF protection). Call this on the page that `post_logout_redirect_uri` points to. Returns `true` only when the URL carries a `state` matching the one saved by `sso.logout(true)`; on success the saved state is cleared (one-time).
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// On the post_logout_redirect_uri page:
|
|
128
|
+
if (sso.validateLogoutState(window.location.href)) {
|
|
129
|
+
// Trusted redirect-back from the SSO logout page
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Returns `boolean`.
|
|
117
134
|
|
|
118
135
|
### `sso.getDiscovery()`
|
|
119
136
|
|
|
120
137
|
Fetch the OIDC Discovery document.
|
|
121
138
|
|
|
122
|
-
Returns `Promise<OidcDiscovery
|
|
139
|
+
Returns `Promise<OidcDiscovery | null>` — may be `null` when the discovery endpoint is unreachable and no cached document exists.
|
|
123
140
|
|
|
124
141
|
---
|
|
125
142
|
|
|
@@ -204,6 +221,7 @@ const {
|
|
|
204
221
|
user, // SsoUser | null
|
|
205
222
|
isAuthenticated, // boolean
|
|
206
223
|
isLoading, // boolean
|
|
224
|
+
error, // SsoError | null — set when loading user info fails (e.g. session revoked)
|
|
207
225
|
login, // (returnUrl?: string) => Promise<void>
|
|
208
226
|
logout, // (redirectToSso?: boolean) => Promise<void>
|
|
209
227
|
refreshUser, // () => Promise<void>
|
|
@@ -212,6 +230,8 @@ const {
|
|
|
212
230
|
} = useSso();
|
|
213
231
|
```
|
|
214
232
|
|
|
233
|
+
Authentication state is three-valued: `isLoading` (initializing/refreshing) → `error` (load failed, e.g. session expired) → `user` (authenticated). Render your UI accordingly.
|
|
234
|
+
|
|
215
235
|
### `<RequireAuth>`
|
|
216
236
|
|
|
217
237
|
```tsx
|
|
@@ -222,6 +242,21 @@ import { RequireAuth } from "@nihplod/sso-sdk/react";
|
|
|
222
242
|
</RequireAuth>
|
|
223
243
|
```
|
|
224
244
|
|
|
245
|
+
If starting the login flow fails (e.g. the user closes the popup, `popup_closed`), the component resets its internal trigger flag and shows a retry entry point instead of getting stuck on "please log in". Use `onError` to observe the failure and `renderLoginError(error, retry)` to customize the UI.
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
<RequireAuth
|
|
249
|
+
autoLogin
|
|
250
|
+
usePopup
|
|
251
|
+
onError={(err) => console.warn("login failed", err)}
|
|
252
|
+
renderLoginError={(err, retry) => (
|
|
253
|
+
<button onClick={retry}>登录未完成,点击重试</button>
|
|
254
|
+
)}
|
|
255
|
+
>
|
|
256
|
+
<ProtectedContent />
|
|
257
|
+
</RequireAuth>
|
|
258
|
+
```
|
|
259
|
+
|
|
225
260
|
### `withAuth(Component)`
|
|
226
261
|
|
|
227
262
|
```tsx
|
|
@@ -242,6 +277,23 @@ export default function AuthCallback() {
|
|
|
242
277
|
}
|
|
243
278
|
```
|
|
244
279
|
|
|
280
|
+
By default the component performs a full-page redirect (`window.location.href`) to the saved `returnUrl` (or `/`) after the token exchange — safe because tokens are persisted to sessionStorage by default. For SPAs that prefer router navigation (no page reload, app state preserved), pass `onSuccess` to take over the redirect; `onError` / `renderError` customize failure handling:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
function CallbackRoute() {
|
|
284
|
+
const navigate = useNavigate();
|
|
285
|
+
return (
|
|
286
|
+
<CallbackPage
|
|
287
|
+
onSuccess={(tokenData) => navigate("/dashboard", { replace: true })}
|
|
288
|
+
onError={(err) => console.warn("sso callback failed", err)}
|
|
289
|
+
renderError={(message) => <MyErrorPage message={message} />}
|
|
290
|
+
/>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The default error UI is also exported as `DefaultCallbackError` if you want to reuse it.
|
|
296
|
+
|
|
245
297
|
---
|
|
246
298
|
|
|
247
299
|
## Next.js Bindings
|
|
@@ -257,9 +309,20 @@ export const middleware = createSsoMiddleware({
|
|
|
257
309
|
ssoBaseUrl: "https://nihplod.cn",
|
|
258
310
|
redirectUri: "https://yourapp.com/api/auth/callback",
|
|
259
311
|
scopes: "openid profile",
|
|
260
|
-
publicPaths: ["/", "/public"],
|
|
312
|
+
publicPaths: ["/", "/public", "/api/auth/logout"],
|
|
261
313
|
// Confidential Client (BFF) can pass clientSecret
|
|
262
314
|
// clientSecret: process.env.SSO_CLIENT_SECRET,
|
|
315
|
+
// validateSsoCookie defaults to true: the middleware calls the introspection
|
|
316
|
+
// endpoint to verify the SSO session cookie. Set to false only if you accept
|
|
317
|
+
// "cookie exists = logged in" semantics (lowest latency, but may pass revoked
|
|
318
|
+
// sessions). Either way, the middleware is only a UX gate — always re-verify
|
|
319
|
+
// tokens in Route Handlers / Server Components before serving sensitive data.
|
|
320
|
+
// insecureLocalDev: false by default; set true ONLY for http://localhost
|
|
321
|
+
// development (disables the Secure cookie attribute and strips __Host-/__Secure-
|
|
322
|
+
// prefixes, which browsers refuse to write over HTTP). Must be set consistently
|
|
323
|
+
// on the middleware, callback and logout handlers. Never enable in production —
|
|
324
|
+
// as a safety guard, the option is force-ignored (with a warning) when
|
|
325
|
+
// NODE_ENV=production and ssoBaseUrl uses https, keeping cookies secure.
|
|
263
326
|
});
|
|
264
327
|
|
|
265
328
|
export const config = {
|
|
@@ -267,6 +330,13 @@ export const config = {
|
|
|
267
330
|
};
|
|
268
331
|
```
|
|
269
332
|
|
|
333
|
+
> **Token expiry behavior (expected):** the middleware does NOT refresh tokens. When the
|
|
334
|
+
> `access_token` cookie expires or fails introspection, the middleware clears it and
|
|
335
|
+
> redirects to `/api/oauth/authorize`. Because the user still holds an SSO session on the
|
|
336
|
+
> main site, the authorize endpoint immediately redirects back with a fresh authorization
|
|
337
|
+
> code (silent re-auth), and the callback handler issues new cookies — the user typically
|
|
338
|
+
> only sees a quick redirect loop through the SSO center. No client-side action is needed.
|
|
339
|
+
|
|
270
340
|
```typescript
|
|
271
341
|
// src/app/api/auth/callback/route.ts
|
|
272
342
|
import { createCallbackRouteHandler } from "@nihplod/sso-sdk/next";
|
|
@@ -284,21 +354,30 @@ export const GET = createCallbackRouteHandler({
|
|
|
284
354
|
// src/app/api/auth/logout/route.ts
|
|
285
355
|
import { createLogoutRouteHandler } from "@nihplod/sso-sdk/next";
|
|
286
356
|
|
|
287
|
-
|
|
357
|
+
const handler = createLogoutRouteHandler({
|
|
288
358
|
clientId: "...",
|
|
289
359
|
ssoBaseUrl: "https://nihplod.cn",
|
|
290
360
|
redirectUri: "https://yourapp.com/api/auth/callback",
|
|
291
361
|
postLogoutRedirectUri: "https://yourapp.com/",
|
|
292
362
|
redirectToSso: true,
|
|
293
363
|
});
|
|
364
|
+
|
|
365
|
+
// Prefer POST to trigger logout (prevents logout CSRF via cross-site GET);
|
|
366
|
+
// GET is kept for compatibility with plain <a> navigations.
|
|
367
|
+
export const GET = handler;
|
|
368
|
+
export const POST = handler;
|
|
294
369
|
```
|
|
295
370
|
|
|
296
|
-
|
|
371
|
+
Trigger the logout endpoint with a POST request (recommended):
|
|
297
372
|
|
|
298
373
|
```tsx
|
|
299
|
-
<
|
|
374
|
+
<button onClick={() => fetch("/api/auth/logout", { method: "POST" }).then(() => location.assign("/"))}>
|
|
375
|
+
Logout
|
|
376
|
+
</button>
|
|
300
377
|
```
|
|
301
378
|
|
|
379
|
+
A plain `<a href="/api/auth/logout">` still works via GET, but note it can be triggered cross-site (logout CSRF).
|
|
380
|
+
|
|
302
381
|
### Cookie Configuration
|
|
303
382
|
|
|
304
383
|
Default cookie names:
|
|
@@ -311,13 +390,17 @@ Default cookie names:
|
|
|
311
390
|
| return_url | `__Host-nihplod_sso_return` | Requires Secure + Path=/ + no Domain |
|
|
312
391
|
| verifier | `__Secure-nihplod_sso_verifier` | Requires Secure + no Domain; Path is the callback path, therefore uses `__Secure-` prefix |
|
|
313
392
|
|
|
314
|
-
> For local development with `http://localhost`,
|
|
393
|
+
> For local development with `http://localhost`, browsers reject `Secure` cookies — and cookies named with `__Host-`/`__Secure-` prefixes are refused outright when `Secure` is missing (Chrome, Edge and Firefox all enforce this; behavior on `localhost` varies by browser, some treat it as a secure context for `Secure` cookies, none accept prefixed names without `Secure`). The visible symptom: the login callback appears to succeed but the cookies are never written, so the middleware keeps judging you as logged out and redirects to the SSO authorize page in an infinite loop. Fix: set `insecureLocalDev: true` on `createSsoMiddleware`, `createCallbackRouteHandler` and `createLogoutRouteHandler` (it disables `Secure` and strips the prefixes, with a startup warning), or serve local dev over HTTPS. HTTPS is mandatory in production — and as a production guard, all three helpers force-ignore `insecureLocalDev` (keeping `Secure` and the `__Host-`/`__Secure-` prefixes, with a warning) when `NODE_ENV=production` and `ssoBaseUrl` uses HTTPS.
|
|
315
394
|
|
|
316
395
|
---
|
|
317
396
|
|
|
318
397
|
## Security Recommendations and Token Storage
|
|
319
398
|
|
|
320
|
-
By default, the SDK stores tokens in **
|
|
399
|
+
By default, the SDK stores tokens in **sessionStorage** (tab-scoped persistence). This keeps the login state across page reloads and the full-page redirect that `CallbackPage` performs after the token exchange, while the data is cleared automatically when the tab closes and is never shared with other tabs. In SSR environments or privacy modes where `sessionStorage` is unavailable/unwritable, it falls back to an in-memory map (login state is lost on reload in that case).
|
|
400
|
+
|
|
401
|
+
> ⚠️ XSS note: any token readable by JavaScript can be stolen by XSS. sessionStorage narrows the exposure compared with `localStorage` (tab-scoped, auto-cleared), but a `refresh_token` readable by JS is still exfiltratable while the tab is open. If you need stronger guarantees, use the Next.js BFF pattern below (tokens in `httpOnly` cookies) or keep the refresh token inside a Service Worker.
|
|
402
|
+
|
|
403
|
+
Transient OAuth data (PKCE `code_verifier`, `state`, `returnUrl`, popup nonce) is stored separately in **sessionStorage**, because it must survive the full-page redirect to the SSO center and back; it is cleared automatically when the tab closes. In SSR environments without `sessionStorage`, it falls back to an in-memory map.
|
|
321
404
|
|
|
322
405
|
If the sub-project is a **Next.js BFF / Confidential Client**, you can store tokens in `localStorage` for multi-tab sharing:
|
|
323
406
|
|
|
@@ -353,6 +436,6 @@ const challenge = await generateCodeChallenge(verifier);
|
|
|
353
436
|
// State
|
|
354
437
|
const state = generateState();
|
|
355
438
|
|
|
356
|
-
// Custom token storage (default is
|
|
439
|
+
// Custom token storage (default is sessionStorage; persist: true → localStorage)
|
|
357
440
|
setTokenStorage(createSecureStorage({ persist: false }));
|
|
358
441
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token 存储抽象层
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
4
|
+
* 分两类存储:
|
|
5
|
+
* - Token 数据:默认使用 sessionStorage(标签页级持久化)。登录回调后 CallbackPage
|
|
6
|
+
* 默认整页跳转,内存存储会丢失登录态,因此默认改为 sessionStorage:
|
|
7
|
+
* 整页跳转与刷新后登录态保留,关闭标签页自动清除;SSR / 隐私模式写入失败时
|
|
8
|
+
* 降级为内存 Map。对需要多 Tab 共享 token 或 BFF/Confidential Client 场景,
|
|
9
|
+
* 可通过 setTokenStorage() 注入 localStorage 实现(如 createSecureStorage({ persist: true }))。
|
|
10
|
+
* - 临时数据(PKCE verifier / state / returnUrl / popup nonce):必须跨整页重定向存活
|
|
11
|
+
* (login() 会 302 跳转到 SSO 中心再回来),因此默认写入 sessionStorage;
|
|
12
|
+
* SSR 等无 sessionStorage 环境自动降级为内存 Map。
|
|
7
13
|
*
|
|
8
14
|
* 多 client 隔离:
|
|
9
15
|
* - token / state / return_url 均支持按 clientId 隔离 key
|
|
@@ -27,6 +33,20 @@ interface TokenStorage {
|
|
|
27
33
|
set(key: string, value: string): void;
|
|
28
34
|
remove(key: string): void;
|
|
29
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 创建存储实现
|
|
38
|
+
*
|
|
39
|
+
* @param options.persist 是否持久化到 localStorage。默认 false(sessionStorage)。
|
|
40
|
+
* - false(默认):sessionStorage,标签页级持久化(刷新/整页跳转后登录态保留,
|
|
41
|
+
* 关闭标签页即清除),多 Tab 间不共享;隐私模式等写入失败时降级为内存。
|
|
42
|
+
* - true:localStorage,多 Tab 共享、关闭浏览器后仍保留。
|
|
43
|
+
* ⚠️ 安全警告:persist=true 会将 refresh_token 明文写入 localStorage,
|
|
44
|
+
* 任何 XSS 均可在不被检测的情况下读取。仅在 BFF/Confidential Client
|
|
45
|
+
* 且 refresh_token 不直接暴露给浏览器的场景下使用。
|
|
46
|
+
*/
|
|
47
|
+
declare function createSecureStorage(options?: {
|
|
48
|
+
persist?: boolean;
|
|
49
|
+
}): TokenStorage;
|
|
30
50
|
/**
|
|
31
51
|
* 设置自定义存储实现
|
|
32
52
|
*/
|
|
@@ -44,6 +64,9 @@ declare function removePkceVerifier(clientId: string): void;
|
|
|
44
64
|
declare function saveOAuthState(state: string, clientId?: string): void;
|
|
45
65
|
declare function getOAuthState(clientId?: string): string | null;
|
|
46
66
|
declare function removeOAuthState(clientId?: string): void;
|
|
67
|
+
declare function saveLogoutState(state: string, clientId?: string): void;
|
|
68
|
+
declare function getLogoutState(clientId?: string): string | null;
|
|
69
|
+
declare function removeLogoutState(clientId?: string): void;
|
|
47
70
|
declare function saveReturnUrl(url: string, clientId?: string): void;
|
|
48
71
|
declare function getReturnUrl(clientId?: string): string | null;
|
|
49
72
|
declare function removeReturnUrl(clientId?: string): void;
|
|
@@ -85,6 +108,8 @@ interface SsoClientConfig {
|
|
|
85
108
|
ssoBaseUrl: string;
|
|
86
109
|
/** 请求的 scope(空格分隔),如 "openid profile phone" */
|
|
87
110
|
scopes?: string;
|
|
111
|
+
/** RP-Initiated Logout 返回地址(可选)。不传时回退到 redirectUri */
|
|
112
|
+
postLogoutRedirectUri?: string;
|
|
88
113
|
}
|
|
89
114
|
/** 用户信息 */
|
|
90
115
|
interface SsoUser {
|
|
@@ -141,26 +166,78 @@ declare class SsoClient {
|
|
|
141
166
|
private _getTokenEndpoint;
|
|
142
167
|
/** 获取 userinfo 端点 URL(优先 Discovery,回退默认) */
|
|
143
168
|
private _getUserinfoEndpoint;
|
|
169
|
+
/**
|
|
170
|
+
* 开放重定向防护:仅保存相对路径或与当前页面同源的 returnUrl,
|
|
171
|
+
* 其余(如 https://evil.com)忽略并告警,防止回调后跳转到钓鱼站点。
|
|
172
|
+
*/
|
|
173
|
+
private _saveReturnUrlIfTrusted;
|
|
144
174
|
/**
|
|
145
175
|
* 发起 SSO 登录
|
|
146
176
|
*
|
|
147
177
|
* 生成 PKCE code_verifier/code_challenge 和 state 参数,
|
|
148
178
|
* 构建 authorize URL,通过 302 跳转到 SSO 登录页。
|
|
149
179
|
*
|
|
150
|
-
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage
|
|
180
|
+
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage;
|
|
181
|
+
* 仅允许相对路径或同源绝对 URL,否则忽略并告警)
|
|
182
|
+
*
|
|
183
|
+
* ⚠️ 不要与 getLoginUrl() 混用:两者都会重新生成并覆盖 sessionStorage 中的
|
|
184
|
+
* state / PKCE verifier,先调用的那次授权流程将因 state 不匹配而失败。
|
|
185
|
+
* 同一次登录只使用其中一个入口。
|
|
151
186
|
*/
|
|
152
187
|
login(returnUrl?: string): Promise<void>;
|
|
153
188
|
/**
|
|
154
189
|
* 构建登录 URL(不跳转,返回 URL 字符串)
|
|
155
190
|
*
|
|
156
191
|
* 适用于需要手动处理跳转的场景。
|
|
192
|
+
*
|
|
193
|
+
* ⚠️ 不要与 login() 混用:两者都会重新生成并覆盖 sessionStorage 中的
|
|
194
|
+
* state / PKCE verifier,先调用的那次授权流程将因 state 不匹配而失败。
|
|
195
|
+
* 同一次登录只使用其中一个入口。
|
|
157
196
|
*/
|
|
158
197
|
getLoginUrl(returnUrl?: string): Promise<string>;
|
|
198
|
+
/**
|
|
199
|
+
* 弹窗模式 SSO 登录
|
|
200
|
+
*
|
|
201
|
+
* 打开一个小窗口进行登录,认证完成后窗口自动关闭,
|
|
202
|
+
* 主页面不丢失状态(适用于 SPA 中需要保持表单/浏览上下文的场景)。
|
|
203
|
+
*
|
|
204
|
+
* 流程:
|
|
205
|
+
* 1. window.open() 打开授权 URL 到弹窗
|
|
206
|
+
* 2. 用户在弹窗中完成登录
|
|
207
|
+
* 3. 弹窗加载 CallbackPage 时检测到 window.opener,通过 postMessage 回传回调 URL
|
|
208
|
+
* 4. 主页面收到消息后调用 handleCallback() 交换 token
|
|
209
|
+
* 5. 弹窗自动关闭
|
|
210
|
+
*
|
|
211
|
+
* @param options - 弹窗配置
|
|
212
|
+
* @param options.returnUrl - 登录成功后的返回地址
|
|
213
|
+
* @param options.width - 弹窗宽度(默认 480)
|
|
214
|
+
* @param options.height - 弹窗高度(默认 640)
|
|
215
|
+
* @returns TokenData
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* const client = new SsoClient({ clientId: "xxx", redirectUri: "https://myapp.com/callback", ssoBaseUrl: "https://nihplod.cn" });
|
|
220
|
+
* try {
|
|
221
|
+
* const tokenData = await client.loginPopup({ returnUrl: "/dashboard" });
|
|
222
|
+
* console.log("登录成功", tokenData);
|
|
223
|
+
* } catch (err) {
|
|
224
|
+
* if (err instanceof SsoError && err.code === "popup_blocked") {
|
|
225
|
+
* // 弹窗被拦截,回退到同页重定向
|
|
226
|
+
* await client.login("/dashboard");
|
|
227
|
+
* }
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
loginPopup(options?: {
|
|
232
|
+
returnUrl?: string;
|
|
233
|
+
width?: number;
|
|
234
|
+
height?: number;
|
|
235
|
+
}): Promise<TokenData>;
|
|
159
236
|
/**
|
|
160
237
|
* 处理 OAuth 回调
|
|
161
238
|
*
|
|
162
239
|
* 解析回调 URL,校验 state 参数,用授权码交换 token。
|
|
163
|
-
* 成功后 token 自动保存到 sessionStorage
|
|
240
|
+
* 成功后 token 自动保存到 token 存储(默认 sessionStorage,可通过 setTokenStorage 定制)。
|
|
164
241
|
*
|
|
165
242
|
* @param callbackUrl - 完整的回调 URL(window.location.href)
|
|
166
243
|
* @returns TokenData 或 null
|
|
@@ -198,9 +275,28 @@ declare class SsoClient {
|
|
|
198
275
|
* 登出
|
|
199
276
|
*
|
|
200
277
|
* 清除本地所有 token 和临时数据,并尝试撤销服务端 refresh_token。
|
|
201
|
-
* @param redirectToSso - 是否重定向到 SSO 登出页(默认 false
|
|
278
|
+
* @param redirectToSso - 是否重定向到 SSO 登出页(默认 false)。
|
|
279
|
+
* 为 true 时携带 state 参数(已保存到 sessionStorage),
|
|
280
|
+
* 回跳页面应调用 validateLogoutState() 校验防登出 CSRF。
|
|
202
281
|
*/
|
|
203
282
|
logout(redirectToSso?: boolean): Promise<void>;
|
|
283
|
+
/**
|
|
284
|
+
* 校验 RP-Initiated Logout 回跳的 state 参数(登出 CSRF 防护)
|
|
285
|
+
*
|
|
286
|
+
* 在 post_logout_redirect_uri 指向的页面加载时调用;
|
|
287
|
+
* 仅在 URL 携带 state 且与 logout(redirectToSso=true) 保存的值一致时返回 true,
|
|
288
|
+
* 校验后清除已保存的 logout state(一次性)。
|
|
289
|
+
*
|
|
290
|
+
* @param url - 当前页面完整 URL(window.location.href)
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* if (sso.validateLogoutState(window.location.href)) {
|
|
295
|
+
* // 来自 SSO 登出的可信回跳
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
validateLogoutState(url: string): boolean;
|
|
204
300
|
/**
|
|
205
301
|
* 获取 OIDC Discovery 文档
|
|
206
302
|
*
|
|
@@ -247,9 +343,16 @@ declare function generateState(): string;
|
|
|
247
343
|
* SSO SDK 错误类型
|
|
248
344
|
*/
|
|
249
345
|
/** SSO 错误码 */
|
|
250
|
-
type SsoErrorCode = "invalid_config" | "state_mismatch" | "pkce_required" | "token_request_failed" | "session_expired" | "no_refresh_token" | "userinfo_failed" | "not_authenticated" | "sso_server_error" | "network_error" | "id_token_invalid" | "id_token_unsupported_alg" | "id_token_missing_secret" | "id_token_invalid_signature" | "id_token_issuer_mismatch" | "id_token_audience_mismatch" | "id_token_expired" | "id_token_missing_sub" | "id_token_at_hash_mismatch";
|
|
251
|
-
/**
|
|
252
|
-
|
|
346
|
+
type SsoErrorCode = "invalid_config" | "state_mismatch" | "pkce_required" | "token_request_failed" | "session_expired" | "no_refresh_token" | "userinfo_failed" | "not_authenticated" | "authorization_code_expired" | "authorization_code_used" | "client_disabled" | "user_denied_authorization" | "account_disabled" | "sso_server_error" | "rate_limited" | "network_error" | "popup_blocked" | "popup_closed" | "id_token_invalid" | "id_token_unsupported_alg" | "id_token_hs256_unsupported" | "id_token_missing_secret" | "id_token_invalid_signature" | "id_token_issuer_mismatch" | "id_token_audience_mismatch" | "id_token_expired" | "id_token_missing_sub" | "id_token_at_hash_mismatch";
|
|
347
|
+
/**
|
|
348
|
+
* 将 OAuth 2.0 服务端 error 字段映射到 SsoErrorCode
|
|
349
|
+
*
|
|
350
|
+
* @param oauthError 服务端返回的 error 字段
|
|
351
|
+
* @param context 调用上下文:"token_exchange"(授权码换 token)或 "refresh"(刷新)。
|
|
352
|
+
* invalid_grant 在换 token 场景多为授权码过期/已用,在刷新场景多为会话失效,
|
|
353
|
+
* 需按上下文细化映射。
|
|
354
|
+
*/
|
|
355
|
+
declare function mapOAuthErrorToSsoCode(oauthError: string, context?: "token_exchange" | "refresh"): SsoErrorCode;
|
|
253
356
|
/**
|
|
254
357
|
* SSO SDK 自定义错误
|
|
255
358
|
*/
|
|
@@ -260,13 +363,33 @@ declare class SsoError extends Error {
|
|
|
260
363
|
constructor(code: SsoErrorCode, description: string, cause?: unknown);
|
|
261
364
|
}
|
|
262
365
|
/**
|
|
263
|
-
* OAuth 2.0
|
|
366
|
+
* OAuth 2.0 协议层错误(RFC 6749 §5.2)
|
|
367
|
+
*
|
|
368
|
+
* 保留用于表示服务端原样返回的 OAuth 错误(error / error_description / error_uri),
|
|
369
|
+
* 与 SDK 语义的 SsoError 区分。当前 SDK 内部不抛出此类型(统一使用 SsoError +
|
|
370
|
+
* mapOAuthErrorToSsoCode 映射),作为公共类型导出供子项目自行解析 OAuth 响应时使用。
|
|
264
371
|
*/
|
|
265
372
|
declare class OAuthError extends Error {
|
|
266
|
-
readonly code:
|
|
373
|
+
readonly code: string;
|
|
267
374
|
readonly description: string;
|
|
268
375
|
readonly uri?: string;
|
|
269
|
-
constructor(code:
|
|
376
|
+
constructor(code: string, description: string, uri?: string);
|
|
270
377
|
}
|
|
271
378
|
|
|
272
|
-
|
|
379
|
+
/**
|
|
380
|
+
* 安全相关小工具:returnUrl 开放重定向校验、常量时间字符串比较
|
|
381
|
+
*/
|
|
382
|
+
/**
|
|
383
|
+
* 校验 returnUrl 是否可信(防开放重定向)。
|
|
384
|
+
* 仅允许:相对路径(且不以 // 开头)或与 currentOrigin 完全同源的绝对 URL。
|
|
385
|
+
* 拒绝一切含反斜杠 \ 的值:浏览器会把 "/\evil.com" 归一化为 "//evil.com",
|
|
386
|
+
* 形成协议相对 URL 开放重定向。
|
|
387
|
+
*/
|
|
388
|
+
declare function isTrustedReturnUrl(url: string, currentOrigin: string): boolean;
|
|
389
|
+
/**
|
|
390
|
+
* 常量时间字符串比较(避免 state / at_hash / nonce 等机密值的时序侧信道)。
|
|
391
|
+
* 长度不同也执行完整循环,不提前返回。
|
|
392
|
+
*/
|
|
393
|
+
declare function timingSafeEqualString(a: string, b: string): boolean;
|
|
394
|
+
|
|
395
|
+
export { OAuthError, type OidcDiscovery, SsoClient, type SsoClientConfig, SsoError, type SsoErrorCode, type SsoUser, type TokenData, type TokenResponse, type TokenStorage, clearAllSsoData, clearVerifiersForClients, createSecureStorage, generateCodeChallenge, generateCodeVerifier, generateState, getLogoutState, getOAuthState, getPkceVerifier, getReturnUrl, getTokenData, getTokenStorage, isTrustedReturnUrl, mapOAuthErrorToSsoCode, removeLogoutState, removeOAuthState, removePkceVerifier, removeReturnUrl, removeTokenData, saveLogoutState, saveOAuthState, savePkceVerifier, saveReturnUrl, saveTokenData, setTokenStorage, timingSafeEqualString };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token 存储抽象层
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
4
|
+
* 分两类存储:
|
|
5
|
+
* - Token 数据:默认使用 sessionStorage(标签页级持久化)。登录回调后 CallbackPage
|
|
6
|
+
* 默认整页跳转,内存存储会丢失登录态,因此默认改为 sessionStorage:
|
|
7
|
+
* 整页跳转与刷新后登录态保留,关闭标签页自动清除;SSR / 隐私模式写入失败时
|
|
8
|
+
* 降级为内存 Map。对需要多 Tab 共享 token 或 BFF/Confidential Client 场景,
|
|
9
|
+
* 可通过 setTokenStorage() 注入 localStorage 实现(如 createSecureStorage({ persist: true }))。
|
|
10
|
+
* - 临时数据(PKCE verifier / state / returnUrl / popup nonce):必须跨整页重定向存活
|
|
11
|
+
* (login() 会 302 跳转到 SSO 中心再回来),因此默认写入 sessionStorage;
|
|
12
|
+
* SSR 等无 sessionStorage 环境自动降级为内存 Map。
|
|
7
13
|
*
|
|
8
14
|
* 多 client 隔离:
|
|
9
15
|
* - token / state / return_url 均支持按 clientId 隔离 key
|
|
@@ -27,6 +33,20 @@ interface TokenStorage {
|
|
|
27
33
|
set(key: string, value: string): void;
|
|
28
34
|
remove(key: string): void;
|
|
29
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 创建存储实现
|
|
38
|
+
*
|
|
39
|
+
* @param options.persist 是否持久化到 localStorage。默认 false(sessionStorage)。
|
|
40
|
+
* - false(默认):sessionStorage,标签页级持久化(刷新/整页跳转后登录态保留,
|
|
41
|
+
* 关闭标签页即清除),多 Tab 间不共享;隐私模式等写入失败时降级为内存。
|
|
42
|
+
* - true:localStorage,多 Tab 共享、关闭浏览器后仍保留。
|
|
43
|
+
* ⚠️ 安全警告:persist=true 会将 refresh_token 明文写入 localStorage,
|
|
44
|
+
* 任何 XSS 均可在不被检测的情况下读取。仅在 BFF/Confidential Client
|
|
45
|
+
* 且 refresh_token 不直接暴露给浏览器的场景下使用。
|
|
46
|
+
*/
|
|
47
|
+
declare function createSecureStorage(options?: {
|
|
48
|
+
persist?: boolean;
|
|
49
|
+
}): TokenStorage;
|
|
30
50
|
/**
|
|
31
51
|
* 设置自定义存储实现
|
|
32
52
|
*/
|
|
@@ -44,6 +64,9 @@ declare function removePkceVerifier(clientId: string): void;
|
|
|
44
64
|
declare function saveOAuthState(state: string, clientId?: string): void;
|
|
45
65
|
declare function getOAuthState(clientId?: string): string | null;
|
|
46
66
|
declare function removeOAuthState(clientId?: string): void;
|
|
67
|
+
declare function saveLogoutState(state: string, clientId?: string): void;
|
|
68
|
+
declare function getLogoutState(clientId?: string): string | null;
|
|
69
|
+
declare function removeLogoutState(clientId?: string): void;
|
|
47
70
|
declare function saveReturnUrl(url: string, clientId?: string): void;
|
|
48
71
|
declare function getReturnUrl(clientId?: string): string | null;
|
|
49
72
|
declare function removeReturnUrl(clientId?: string): void;
|
|
@@ -85,6 +108,8 @@ interface SsoClientConfig {
|
|
|
85
108
|
ssoBaseUrl: string;
|
|
86
109
|
/** 请求的 scope(空格分隔),如 "openid profile phone" */
|
|
87
110
|
scopes?: string;
|
|
111
|
+
/** RP-Initiated Logout 返回地址(可选)。不传时回退到 redirectUri */
|
|
112
|
+
postLogoutRedirectUri?: string;
|
|
88
113
|
}
|
|
89
114
|
/** 用户信息 */
|
|
90
115
|
interface SsoUser {
|
|
@@ -141,26 +166,78 @@ declare class SsoClient {
|
|
|
141
166
|
private _getTokenEndpoint;
|
|
142
167
|
/** 获取 userinfo 端点 URL(优先 Discovery,回退默认) */
|
|
143
168
|
private _getUserinfoEndpoint;
|
|
169
|
+
/**
|
|
170
|
+
* 开放重定向防护:仅保存相对路径或与当前页面同源的 returnUrl,
|
|
171
|
+
* 其余(如 https://evil.com)忽略并告警,防止回调后跳转到钓鱼站点。
|
|
172
|
+
*/
|
|
173
|
+
private _saveReturnUrlIfTrusted;
|
|
144
174
|
/**
|
|
145
175
|
* 发起 SSO 登录
|
|
146
176
|
*
|
|
147
177
|
* 生成 PKCE code_verifier/code_challenge 和 state 参数,
|
|
148
178
|
* 构建 authorize URL,通过 302 跳转到 SSO 登录页。
|
|
149
179
|
*
|
|
150
|
-
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage
|
|
180
|
+
* @param returnUrl - 登录成功后的返回地址(可选,保存到 sessionStorage;
|
|
181
|
+
* 仅允许相对路径或同源绝对 URL,否则忽略并告警)
|
|
182
|
+
*
|
|
183
|
+
* ⚠️ 不要与 getLoginUrl() 混用:两者都会重新生成并覆盖 sessionStorage 中的
|
|
184
|
+
* state / PKCE verifier,先调用的那次授权流程将因 state 不匹配而失败。
|
|
185
|
+
* 同一次登录只使用其中一个入口。
|
|
151
186
|
*/
|
|
152
187
|
login(returnUrl?: string): Promise<void>;
|
|
153
188
|
/**
|
|
154
189
|
* 构建登录 URL(不跳转,返回 URL 字符串)
|
|
155
190
|
*
|
|
156
191
|
* 适用于需要手动处理跳转的场景。
|
|
192
|
+
*
|
|
193
|
+
* ⚠️ 不要与 login() 混用:两者都会重新生成并覆盖 sessionStorage 中的
|
|
194
|
+
* state / PKCE verifier,先调用的那次授权流程将因 state 不匹配而失败。
|
|
195
|
+
* 同一次登录只使用其中一个入口。
|
|
157
196
|
*/
|
|
158
197
|
getLoginUrl(returnUrl?: string): Promise<string>;
|
|
198
|
+
/**
|
|
199
|
+
* 弹窗模式 SSO 登录
|
|
200
|
+
*
|
|
201
|
+
* 打开一个小窗口进行登录,认证完成后窗口自动关闭,
|
|
202
|
+
* 主页面不丢失状态(适用于 SPA 中需要保持表单/浏览上下文的场景)。
|
|
203
|
+
*
|
|
204
|
+
* 流程:
|
|
205
|
+
* 1. window.open() 打开授权 URL 到弹窗
|
|
206
|
+
* 2. 用户在弹窗中完成登录
|
|
207
|
+
* 3. 弹窗加载 CallbackPage 时检测到 window.opener,通过 postMessage 回传回调 URL
|
|
208
|
+
* 4. 主页面收到消息后调用 handleCallback() 交换 token
|
|
209
|
+
* 5. 弹窗自动关闭
|
|
210
|
+
*
|
|
211
|
+
* @param options - 弹窗配置
|
|
212
|
+
* @param options.returnUrl - 登录成功后的返回地址
|
|
213
|
+
* @param options.width - 弹窗宽度(默认 480)
|
|
214
|
+
* @param options.height - 弹窗高度(默认 640)
|
|
215
|
+
* @returns TokenData
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* const client = new SsoClient({ clientId: "xxx", redirectUri: "https://myapp.com/callback", ssoBaseUrl: "https://nihplod.cn" });
|
|
220
|
+
* try {
|
|
221
|
+
* const tokenData = await client.loginPopup({ returnUrl: "/dashboard" });
|
|
222
|
+
* console.log("登录成功", tokenData);
|
|
223
|
+
* } catch (err) {
|
|
224
|
+
* if (err instanceof SsoError && err.code === "popup_blocked") {
|
|
225
|
+
* // 弹窗被拦截,回退到同页重定向
|
|
226
|
+
* await client.login("/dashboard");
|
|
227
|
+
* }
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
loginPopup(options?: {
|
|
232
|
+
returnUrl?: string;
|
|
233
|
+
width?: number;
|
|
234
|
+
height?: number;
|
|
235
|
+
}): Promise<TokenData>;
|
|
159
236
|
/**
|
|
160
237
|
* 处理 OAuth 回调
|
|
161
238
|
*
|
|
162
239
|
* 解析回调 URL,校验 state 参数,用授权码交换 token。
|
|
163
|
-
* 成功后 token 自动保存到 sessionStorage
|
|
240
|
+
* 成功后 token 自动保存到 token 存储(默认 sessionStorage,可通过 setTokenStorage 定制)。
|
|
164
241
|
*
|
|
165
242
|
* @param callbackUrl - 完整的回调 URL(window.location.href)
|
|
166
243
|
* @returns TokenData 或 null
|
|
@@ -198,9 +275,28 @@ declare class SsoClient {
|
|
|
198
275
|
* 登出
|
|
199
276
|
*
|
|
200
277
|
* 清除本地所有 token 和临时数据,并尝试撤销服务端 refresh_token。
|
|
201
|
-
* @param redirectToSso - 是否重定向到 SSO 登出页(默认 false
|
|
278
|
+
* @param redirectToSso - 是否重定向到 SSO 登出页(默认 false)。
|
|
279
|
+
* 为 true 时携带 state 参数(已保存到 sessionStorage),
|
|
280
|
+
* 回跳页面应调用 validateLogoutState() 校验防登出 CSRF。
|
|
202
281
|
*/
|
|
203
282
|
logout(redirectToSso?: boolean): Promise<void>;
|
|
283
|
+
/**
|
|
284
|
+
* 校验 RP-Initiated Logout 回跳的 state 参数(登出 CSRF 防护)
|
|
285
|
+
*
|
|
286
|
+
* 在 post_logout_redirect_uri 指向的页面加载时调用;
|
|
287
|
+
* 仅在 URL 携带 state 且与 logout(redirectToSso=true) 保存的值一致时返回 true,
|
|
288
|
+
* 校验后清除已保存的 logout state(一次性)。
|
|
289
|
+
*
|
|
290
|
+
* @param url - 当前页面完整 URL(window.location.href)
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* if (sso.validateLogoutState(window.location.href)) {
|
|
295
|
+
* // 来自 SSO 登出的可信回跳
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
validateLogoutState(url: string): boolean;
|
|
204
300
|
/**
|
|
205
301
|
* 获取 OIDC Discovery 文档
|
|
206
302
|
*
|
|
@@ -247,9 +343,16 @@ declare function generateState(): string;
|
|
|
247
343
|
* SSO SDK 错误类型
|
|
248
344
|
*/
|
|
249
345
|
/** SSO 错误码 */
|
|
250
|
-
type SsoErrorCode = "invalid_config" | "state_mismatch" | "pkce_required" | "token_request_failed" | "session_expired" | "no_refresh_token" | "userinfo_failed" | "not_authenticated" | "sso_server_error" | "network_error" | "id_token_invalid" | "id_token_unsupported_alg" | "id_token_missing_secret" | "id_token_invalid_signature" | "id_token_issuer_mismatch" | "id_token_audience_mismatch" | "id_token_expired" | "id_token_missing_sub" | "id_token_at_hash_mismatch";
|
|
251
|
-
/**
|
|
252
|
-
|
|
346
|
+
type SsoErrorCode = "invalid_config" | "state_mismatch" | "pkce_required" | "token_request_failed" | "session_expired" | "no_refresh_token" | "userinfo_failed" | "not_authenticated" | "authorization_code_expired" | "authorization_code_used" | "client_disabled" | "user_denied_authorization" | "account_disabled" | "sso_server_error" | "rate_limited" | "network_error" | "popup_blocked" | "popup_closed" | "id_token_invalid" | "id_token_unsupported_alg" | "id_token_hs256_unsupported" | "id_token_missing_secret" | "id_token_invalid_signature" | "id_token_issuer_mismatch" | "id_token_audience_mismatch" | "id_token_expired" | "id_token_missing_sub" | "id_token_at_hash_mismatch";
|
|
347
|
+
/**
|
|
348
|
+
* 将 OAuth 2.0 服务端 error 字段映射到 SsoErrorCode
|
|
349
|
+
*
|
|
350
|
+
* @param oauthError 服务端返回的 error 字段
|
|
351
|
+
* @param context 调用上下文:"token_exchange"(授权码换 token)或 "refresh"(刷新)。
|
|
352
|
+
* invalid_grant 在换 token 场景多为授权码过期/已用,在刷新场景多为会话失效,
|
|
353
|
+
* 需按上下文细化映射。
|
|
354
|
+
*/
|
|
355
|
+
declare function mapOAuthErrorToSsoCode(oauthError: string, context?: "token_exchange" | "refresh"): SsoErrorCode;
|
|
253
356
|
/**
|
|
254
357
|
* SSO SDK 自定义错误
|
|
255
358
|
*/
|
|
@@ -260,13 +363,33 @@ declare class SsoError extends Error {
|
|
|
260
363
|
constructor(code: SsoErrorCode, description: string, cause?: unknown);
|
|
261
364
|
}
|
|
262
365
|
/**
|
|
263
|
-
* OAuth 2.0
|
|
366
|
+
* OAuth 2.0 协议层错误(RFC 6749 §5.2)
|
|
367
|
+
*
|
|
368
|
+
* 保留用于表示服务端原样返回的 OAuth 错误(error / error_description / error_uri),
|
|
369
|
+
* 与 SDK 语义的 SsoError 区分。当前 SDK 内部不抛出此类型(统一使用 SsoError +
|
|
370
|
+
* mapOAuthErrorToSsoCode 映射),作为公共类型导出供子项目自行解析 OAuth 响应时使用。
|
|
264
371
|
*/
|
|
265
372
|
declare class OAuthError extends Error {
|
|
266
|
-
readonly code:
|
|
373
|
+
readonly code: string;
|
|
267
374
|
readonly description: string;
|
|
268
375
|
readonly uri?: string;
|
|
269
|
-
constructor(code:
|
|
376
|
+
constructor(code: string, description: string, uri?: string);
|
|
270
377
|
}
|
|
271
378
|
|
|
272
|
-
|
|
379
|
+
/**
|
|
380
|
+
* 安全相关小工具:returnUrl 开放重定向校验、常量时间字符串比较
|
|
381
|
+
*/
|
|
382
|
+
/**
|
|
383
|
+
* 校验 returnUrl 是否可信(防开放重定向)。
|
|
384
|
+
* 仅允许:相对路径(且不以 // 开头)或与 currentOrigin 完全同源的绝对 URL。
|
|
385
|
+
* 拒绝一切含反斜杠 \ 的值:浏览器会把 "/\evil.com" 归一化为 "//evil.com",
|
|
386
|
+
* 形成协议相对 URL 开放重定向。
|
|
387
|
+
*/
|
|
388
|
+
declare function isTrustedReturnUrl(url: string, currentOrigin: string): boolean;
|
|
389
|
+
/**
|
|
390
|
+
* 常量时间字符串比较(避免 state / at_hash / nonce 等机密值的时序侧信道)。
|
|
391
|
+
* 长度不同也执行完整循环,不提前返回。
|
|
392
|
+
*/
|
|
393
|
+
declare function timingSafeEqualString(a: string, b: string): boolean;
|
|
394
|
+
|
|
395
|
+
export { OAuthError, type OidcDiscovery, SsoClient, type SsoClientConfig, SsoError, type SsoErrorCode, type SsoUser, type TokenData, type TokenResponse, type TokenStorage, clearAllSsoData, clearVerifiersForClients, createSecureStorage, generateCodeChallenge, generateCodeVerifier, generateState, getLogoutState, getOAuthState, getPkceVerifier, getReturnUrl, getTokenData, getTokenStorage, isTrustedReturnUrl, mapOAuthErrorToSsoCode, removeLogoutState, removeOAuthState, removePkceVerifier, removeReturnUrl, removeTokenData, saveLogoutState, saveOAuthState, savePkceVerifier, saveReturnUrl, saveTokenData, setTokenStorage, timingSafeEqualString };
|