@auth-link/web 0.0.5 → 1.1.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/docs/README.ja.md CHANGED
@@ -1,282 +1,62 @@
1
- # AuthLink Web
1
+ # AuthLink Web 1.1
2
2
 
3
- [English](../README.md) | [简体中文](./README.zh-CN.md) | **日本語**
3
+ [English](README.en.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md)
4
4
 
5
- スマートフォン登録とアプリ内確認のフロントエンド用ダイアログです。Vue 3、React 18/19、通常の JavaScript に対応します。インストール、session 取得、client 初期化、表示、イベントと後処理の順に接続します。
5
+ ## 認証コードを直接検証
6
6
 
7
- ## 1. インストールと 2 つの API
7
+ アプリに6桁の認証コードを直接表示します。通常の検証では、毎回の承認、認証用requestId、businessId、ポーリングは不要です。初回はQRで端末を確認し、サイトで現在のコードを検証して連携を完了します。
8
+
9
+ 画面を開く前にCMSサーバーでセッションの唯一の保留中操作を原子的に確保します。別タブから上書きさせません。ユーザーはサーバーのセッションから取得し、CSRF、業務権限、元のフォームを確認します。コードを検証し、verificationIdと元の操作を原子的に確定してから成功を返します。onVerifiedは表示更新専用です。
8
10
 
9
11
  ```sh
10
- npm install @auth-link/web@latest
11
- # 利用する vue、または react / react-dom もインストールします。
12
+ npm install @auth-link/web@^1.1.0
12
13
  ```
13
14
 
14
- | origin | フロントエンド設定 | 用途 |
15
- | --- | --- | --- |
16
- | 自サイトの API(例 https://cms-api.example.com) | baseUrl、allowedBackendOrigins | session、要求作成、結果 |
17
- | AuthLink 本番 API https://api.getauthlink.com | authLinkOrigin | ticket 内のサービス origin の照合 |
18
-
19
- baseUrl は自サイトの業務 API であり、AuthLink API や公式サイトではありません。既存のログイン/session と下記要求 API を利用します。SDK 自体はログイン/session API を提供しません。テナントキーはブラウザーに渡しません。
20
-
21
- ルート import は client と型、/vue と /react はコンポーネントと useAuthLink、/styles.css は明示 import です。ESM、TypeScript、ツール環境 Node 22.13+、React 18.2+/19 または Vue 3.3+。利用するフレームワークのみ導入します。
22
-
23
- ## 2. 現在の session と client
24
-
25
15
  ```ts
26
- import { createAuthLinkClient } from '@auth-link/web';
16
+ import { openPasscode, createPasscodeClient } from '@auth-link/web';
27
17
  import '@auth-link/web/styles.css';
28
-
29
- // getCsrf reads your website's CURRENT session token on every request.
30
- export function createWebsiteClient(getCsrf: () => string, backendOrigin?: string) {
31
- return createAuthLinkClient({
32
- baseUrl: backendOrigin ? `${backendOrigin}/api/authlink` : '/api/authlink',
33
- allowedBackendOrigins: backendOrigin ? [backendOrigin] : [],
34
- credentials: backendOrigin ? 'include' : 'same-origin',
35
- csrfToken: getCsrf,
36
- authLinkOrigin: 'https://api.getauthlink.com',
37
- });
38
- }
39
- ```
40
-
41
- サイトのログイン後に既存 session API を読みます。例は CMS の authenticated/csrf です。フィールド名が csrfToken ならその値を SDK の csrfToken コールバックへ渡します。
42
-
43
- ```ts
44
- // Run in your website's existing authenticated browser flow.
45
- const backendOrigin = 'https://cms-api.example.com'; // YOUR application backend
46
- const response = await fetch(`${backendOrigin}/api/session`, {
47
- credentials: 'include', cache: 'no-store', redirect: 'error',
18
+ const client = createPasscodeClient({ baseUrl: '/api', csrfToken: () => csrfToken });
19
+ const modal = openPasscode({
20
+ client, expiresAt: pending.expiresAt, locale: 'zh-CN', variant: 'B',
21
+ // First-time enrollment only:
22
+ // bindingQr: pending.bindingQr, authLinkOrigin: 'https://api.getauthlink.com',
23
+ onVerified: result => updateUI(result.verificationId),
48
24
  });
49
- if (!response.ok) throw new Error('Session unavailable');
50
- let session = await response.json();
51
- if (session.authenticated !== true || typeof session.csrf !== 'string' || !session.csrf) {
52
- throw new Error('Log in to your website first');
53
- }
54
- const client = createWebsiteClient(() => session.csrf, backendOrigin);
55
- // Same origin: createWebsiteClient(() => session.csrf)
56
- // After token refresh, replace session or update session.csrf.
57
- // On logout/account/session change, close/unmount old dialogs and recreate client state.
25
+ // modal.setLocale('en'); modal.setVariant('C'); modal.destroy();
58
26
  ```
59
27
 
60
- GET /api/session はサイト側の既存ルートの例です。同 origin は相対 URL と same-origin Cookie、別 origin の Cookie は include を使い、業務 API が現在のフロントエンド origin を許可している必要があります。Cookie 制限も適用されます。HttpOnly Cookie を読んだり、CSRF をブラウザーで生成したり、照合数字で代用したりしません。
28
+ 標準クライアントは現在の認証情報とCSRFを付け、CMSのPOST /api/authへ{passcode}を送信します。フォームも送る場合はclient.verifyで{form,passcode}をCMSへ送信し、サーバーで保存済み内容と照合します。テナントキーやコード生成用秘密はブラウザーへ渡しません。
61
29
 
62
- Bearer/JWT を使う場合、auth は既存ログイン状態です。更新された accessToken を毎回読みます。これはテナントキーではありません。credentials=omit の Bearer のみ CSRF を省略でき、Cookie 併用なら csrfToken が必要です。
30
+ ## オプションとコールバック
63
31
 
64
- ```ts
65
- const backendOrigin = 'https://cms-api.example.com'; // YOUR application backend
66
- const client = createAuthLinkClient({
67
- baseUrl: `${backendOrigin}/api/authlink`, allowedBackendOrigins: [backendOrigin],
68
- credentials: 'omit', accessToken: () => auth.accessToken,
69
- authLinkOrigin: 'https://api.getauthlink.com',
70
- });
71
- ```
32
+ 必須はclient.verify(passcode, signal)とexpiresAt(CMS操作の期限)です。任意でlocale、variant A/B/C、初回用bindingQrとauthLinkOrigin、onVerified、onError、onClose、onExpiredを指定します。成功結果はstatus、verificationId、authenticationPolicy、expiresAtを含み、requestIdやbusinessIdは含みません。
72
33
 
73
- ## 3. Vue:クリックで作成、表示、後処理
34
+ Cookie認証ではCSRFが必須です。Bearer認証はaccessTokenとcredentials=omitを使います。別オリジンのCMSはallowedBackendOriginsを明示し、ループバック開発以外ではHTTPSを使います。
74
35
 
75
- `examples/vue-client.vue` を AuthLinkPanel.vue としてコピーできます。ログイン後に現在の session.csrf を渡し、別 origin なら backendOrigin も渡します。ログアウト・アカウント/session 変更では旧コンポーネントをアンマウントして再作成します。token 更新時は csrf prop を更新します。
36
+ ## ReactとVue
76
37
 
77
- ```vue
78
- <script setup lang="ts">
79
- import { markRaw, onUnmounted, ref, shallowRef } from 'vue';
80
- import { createAuthLinkClient, requestKey } from '@auth-link/web';
81
- import type { Confirmation, Mode, Ticket } from '@auth-link/web';
82
- import { AuthLinkBind, AuthLinkConfirm } from '@auth-link/web/vue';
83
- import '@auth-link/web/styles.css';
38
+ Reactは同じオプションを使い、Vueはverified、error、close、expiredイベントを発行します。clientの参照を安定させてください。13言語に対応し、既定は簡体字中国語です。Aはシンプル、Bは分割、Cはコンパクト表示です。
84
39
 
85
- // csrf comes from your website's current authenticated session.
86
- const props = defineProps<{ csrf: string; backendOrigin?: string }>();
87
- const ticket = shallowRef<Ticket | null>(null);
88
- const busy = ref(false), message = ref('');
89
- const lifetime = new AbortController();
90
- let creationKey: string | null = null;
91
- let creationMode: Mode | null = null;
92
- const client = markRaw(createAuthLinkClient({
93
- baseUrl: props.backendOrigin ? `${props.backendOrigin}/api/authlink` : '/api/authlink',
94
- allowedBackendOrigins: props.backendOrigin ? [props.backendOrigin] : [],
95
- credentials: props.backendOrigin ? 'include' : 'same-origin',
96
- csrfToken: () => props.csrf,
97
- authLinkOrigin: 'https://api.getauthlink.com',
98
- }));
99
- function finish(text: string) { ticket.value = null; message.value = text; }
100
- function confirmed(result: Confirmation) {
101
- finish(`${result.purpose}: ${result.requestId}`);
102
- // Reload your website's account/status API if the page needs fresh state.
103
- }
104
- function showError(error: Error) { message.value = error.message; }
105
- async function start(mode: Mode) {
106
- if (busy.value || ticket.value) return;
107
- busy.value = true; message.value = '';
108
- try {
109
- if (creationMode !== mode) { creationKey = null; creationMode = mode; }
110
- creationKey ??= requestKey();
111
- const value = await client.create(mode, { idempotencyKey: creationKey, signal: lifetime.signal });
112
- if (lifetime.signal.aborted) return;
113
- ticket.value = value; creationKey = null;
114
- } catch (error) {
115
- if (!lifetime.signal.aborted) showError(error as Error);
116
- // Keep the same key when retrying an uncertain creation of the same mode.
117
- } finally { busy.value = false; }
118
- }
119
- onUnmounted(() => { lifetime.abort(); ticket.value = null; creationKey = null; });
40
+ ```tsx
41
+ import { AuthLinkPasscode } from '@auth-link/web/react';
42
+ // Keep client stable (for example with useMemo).
43
+ <AuthLinkPasscode client={client} expiresAt={pending.expiresAt}
44
+ locale="zh-CN" variant="B" onVerified={updateUI} />
45
+ ```
46
+ ```vue
47
+ <script setup>
48
+ import { AuthLinkPasscode } from '@auth-link/web/vue';
120
49
  </script>
121
-
122
50
  <template>
123
- <button :disabled="busy || !!ticket" @click="start('bind')">Bind phone</button>
124
- <button :disabled="busy || !!ticket" @click="start('confirm')">Confirm</button>
125
- <AuthLinkBind v-if="ticket?.purpose === 'bind'" :ticket="ticket" :client="client" variant="B"
126
- @confirmed="confirmed" @close="finish('Closed')" @rejected="finish('Rejected')"
127
- @expired="finish('Expired; start again')" @error="showError" />
128
- <AuthLinkConfirm v-else-if="ticket?.purpose === 'confirm'" :ticket="ticket" :client="client" variant="B"
129
- @confirmed="confirmed" @close="finish('Closed')" @rejected="finish('Rejected')"
130
- @expired="finish('Expired; start again')" @error="showError" />
131
- <p role="status">{{ message }}</p>
51
+ <AuthLinkPasscode :client="client" :expires-at="pending.expiresAt"
52
+ locale="zh-CN" variant="B" @verified="updateUI" />
132
53
  </template>
133
54
  ```
134
55
 
135
- ticket は shallowRef、client は markRaw で保持します。同じ用途の作成失敗ではキーを保持し、成功後に消去します。mounted/watch で要求を作りません。確認・拒否・期限切れ・閉じる操作で ticket を消去します。通信エラーでは表示を維持して同じ要求を再試行します。必要なアカウント状態は自サイトの API から再取得します。
136
-
137
- ## 4. React
138
-
139
- 上記で初期化した安定した client と mode=bind/confirm を渡します。ログアウトやアカウント/session 変更ではアンマウントします。
140
-
141
- ```tsx
142
- "use client";
143
- import { useEffect, useRef, useState } from 'react';
144
- import { requestKey } from '@auth-link/web';
145
- import type { AuthLinkClient, Mode, Ticket } from '@auth-link/web';
146
- import { AuthLinkBind, AuthLinkConfirm } from '@auth-link/web/react';
147
- // Import '@auth-link/web/styles.css' once in your application entry.
148
-
149
- export function AuthLinkPanel({ client, mode }: { client: AuthLinkClient; mode: Mode }) {
150
- const [ticket, setTicket] = useState<Ticket | null>(null);
151
- const [busy, setBusy] = useState(false);
152
- const [message, setMessage] = useState('');
153
- const creation = useRef<{ mode: Mode; key: string } | null>(null);
154
- const inFlight = useRef(false);
155
- const controller = useRef<AbortController | null>(null);
156
- useEffect(() => () => { controller.current?.abort(); }, []);
157
- function finish(text: string) { setTicket(null); setMessage(text); }
158
- async function start() {
159
- if (inFlight.current || ticket) return;
160
- inFlight.current = true; setBusy(true); setMessage('');
161
- const attempt = new AbortController(); controller.current = attempt;
162
- try {
163
- if (creation.current?.mode !== mode) creation.current = { mode, key: requestKey() };
164
- const value = await client.create(mode, { idempotencyKey: creation.current.key, signal: attempt.signal });
165
- if (attempt.signal.aborted) return;
166
- setTicket(value); creation.current = null;
167
- } catch (error) { if (!attempt.signal.aborted) setMessage((error as Error).message); }
168
- finally {
169
- inFlight.current = false;
170
- if (!attempt.signal.aborted) setBusy(false);
171
- }
172
- }
173
- const Component = mode === 'bind' ? AuthLinkBind : AuthLinkConfirm;
174
- return <>
175
- {!ticket && <button disabled={busy} onClick={() => void start()}>Start verification</button>}
176
- {ticket && <Component ticket={ticket} client={client} variant="B"
177
- onConfirmed={result => finish(`${result.purpose}: ${result.requestId}`)}
178
- onClose={() => finish('Closed')} onRejected={() => finish('Rejected')}
179
- onExpired={() => finish('Expired; start again')}
180
- onError={error => setMessage(error.message)} />}
181
- <p role="status">{message}</p>
182
- </>;
183
- }
184
- ```
185
-
186
- ## 5. 通常の JavaScript / 直接表示
187
-
188
- ```ts
189
- import { openAuthLinkConfirm, requestKey } from '@auth-link/web';
190
- import '@auth-link/web/styles.css';
191
-
192
- // Inside a click handler; client is the stable instance created above.
193
- const ticket = await client.create('confirm', { idempotencyKey: requestKey() });
194
- const modal = openAuthLinkConfirm({
195
- ticket, client, variant: 'B', locale: 'zh-CN',
196
- onConfirmed: result => console.log(result.requestId),
197
- onRejected: result => console.log(result.code),
198
- onExpired: () => console.log('Expired'),
199
- onError: error => console.log(error.message),
200
- onClose: () => console.log('Closed'),
201
- });
202
- // Route/component cleanup: modal.destroy();
203
- // Optional: modal.close(); modal.retry(); modal.setLocale('ja'); modal.setVariant('C');
204
- ```
205
-
206
- 登録時は client.create('bind', ...) と openAuthLinkBind(...) を使います。確認・拒否は自動で閉じてから通知し、onClose は発火しません。閉じる操作は監視を停止します。画面を離れる際は destroy を呼びます。同じ要求の成功はインスタンス内で一度だけ通知します。イベントは表示更新用で、保護操作は自サイトの業務 API を呼びます。
207
-
208
- ## 6. フロントエンドが使う API の形式
209
-
210
- | 自サイトのルート | 送信 | 受信 |
211
- | --- | --- | --- |
212
- | POST /api/authlink/requests | purpose=bind/confirm、Idempotency-Key | Ticket |
213
- | POST /api/authlink/requests/{requestId}/result | 空オブジェクト、現在のログイン情報 | Result(自動監視) |
214
-
215
- Cookie モードでは両方に現在の CSRF ヘッダーを送ります。Ticket は requestId、purpose、qr、pairingCode、expiresAt、confirm 時は displayCode も必要です。Ticket/Result/Confirmation 型を import し、QR や数字を自作しません。confirmed のみ成功イベントを発火します。new/pending/approved/consumed は待機、rejected/expired は対応イベントです。
216
-
217
- create() は purpose のみ送ります。業務固有項目が必要なら独自 fetch で作成し、validateTicket で検証して同じコンポーネントへ渡します。post/session/backendOrigin は既存のフロントエンド状態で、同じ入力の再試行では同じキーを保持します。
218
-
219
- ```ts
220
- import { requestKey, validateTicket } from '@auth-link/web';
221
-
222
- // Keep this key across retries of the SAME operation and payload.
223
- const idempotencyKey = requestKey();
224
- const response = await fetch(`${backendOrigin}/api/authlink/requests`, {
225
- method: 'POST', credentials: 'include', cache: 'no-store', redirect: 'error',
226
- headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': session.csrf,
227
- 'Idempotency-Key': idempotencyKey },
228
- body: JSON.stringify({ purpose: 'confirm', postId: post.id, version: post.version }),
229
- });
230
- if (!response.ok) throw new Error('Creation failed');
231
- const ticket = validateTicket(await response.json(), 'confirm', 'https://api.getauthlink.com');
232
- // Pass ticket and the same client to AuthLinkConfirm.
233
- ```
234
-
235
- ## 7. 設定・イベント・表示
236
-
237
- | prop | 型 | 用途 |
238
- | --- | --- | --- |
239
- | `ticket` | `Ticket` | Required; the current request |
240
- | `client` | `AuthLinkClient` | Required; keep the instance stable |
241
- | `locale` | `Locale` | Default `zh-CN` |
242
- | `variant` | `'A' / 'B' / 'C'` | Default `A` |
243
- | `showQr` | `boolean` | Optional QR for confirm; bind always shows QR |
244
- | `intervalMs` | `number` | Default 1500; range 250–10000 ms |
245
- | `onRefresh` | `() => void` | Optional user action after expiry; create a new ticket |
246
- | `className` | `string` | Custom dialog class; Vue also accepts `class` |
247
-
248
- | React コールバック / Vue イベント | 値 | 動作 |
249
- | --- | --- | --- |
250
- | `onConfirmed` / `confirmed` | `Confirmation` | Success; dialog closes automatically |
251
- | `onRejected` / `rejected` | `Rejection` | `{status:'rejected',code:'REJECTED',requestId,purpose}`; dialog closes |
252
- | `onExpired` / `expired` | — | Request expired |
253
- | `onClose` / `close` | — | User closes the dialog; clear ticket |
254
- | `onError` / `error` | `Error` | Read `error.message`; retain the dialog for retry |
255
-
256
- A は中央カード(既定)、B はデスクトップ分割・狭い画面で縦配置、C は小型・モバイルで下部シートです。locale/variant の変更では要求を作り直しません。ネイティブ dialog.showModal() 対応ブラウザーが必要です。
257
-
258
- CSS を明示 import します。ダイアログは document.body 配下なので :root または .authlink-dialog で --authlink-foreground、--authlink-background、--authlink-accent、--authlink-muted を指定します。Vue class は文字列・配列・オブジェクトを className と併用できます。SSR はダイアログや要求を作りません。
259
-
260
- 言語:`zh-CN` (default), `zh-TW`, `en`, `ja`, `bn`, `es`, `fil`, `hi`, `id`, `ms`, `pt`, `th`, `vi`。未指定・未対応値は簡体字中国語へフォールバックします。supportedLocales で一覧を取得できます。
261
-
262
- ## 8. 通信設定とエラー確認
263
-
264
- | 設定 | 用途 |
265
- | --- | --- |
266
- | `baseUrl` | Your application API prefix; default `/api/authlink` |
267
- | `allowedBackendOrigins` | Exact extra application API origins; no paths/wildcards |
268
- | `credentials` | Default `same-origin`; cross-origin cookies need `include` |
269
- | `csrfToken` | Sync/async getter for the current website CSRF token |
270
- | `csrfHeaderName` | Default `X-CSRF-Token` |
271
- | `accessToken` | Sync/async website Bearer token getter, without `Bearer ` |
272
- | `headers` | Extra headers or sync/async getter |
273
- | `authLinkOrigin` | `https://api.getauthlink.com` for production AuthLink |
274
- | `fetch` | Optional fetch implementation |
275
-
276
- headers は Accept、Content-Type、Authorization、Idempotency-Key、設定済み CSRF ヘッダーを上書きできません。error.message を確認します。CSRF_REQUIRED は session token、ACCESS_TOKEN_REQUIRED はログイン状態、BACKEND_ORIGIN_NOT_ALLOWED は正確な許可 origin、NETWORK_ERROR は開発者ツールの Network・通信・CORS を確認します。タイムアウトは 10 秒、閉じる/アンマウントで監視を中止します。
56
+ ## 失敗と終了処理
277
57
 
278
- 期限切れの数字は隠れます。数字をフロントエンドで生成・キャッシュしません。QR はローカル生成で、CSP 使用時は data: 画像を許可します。イベントや localStorage、ブラウザーからの状態値は業務上の許可証ではありません。
58
+ 使用済みコードは別操作や別セッションに使えません。ログインや初回連携直後は次のコードを待ちます。PASSCODE_INVALID、PASSCODE_USED、PASSCODE_LOCKEDを成功として扱わないでください。通信結果不明時は元の冪等キーとコードを保持して再試行し、業務を二重実行しません。
279
59
 
280
- ## ライセンス
60
+ destroy()はブラウザー要求を中止してタイマーを解除し、終了コールバックを呼びません。close()はonCloseも呼びます。画面を閉じてもサーバー処理は元に戻らず、キャンセルと保留中操作はCMS側で管理します。待機やローカル期限判定はポーリングしません。
281
61
 
282
- MIT。LICENSE と THIRD_PARTY_NOTICES.md を参照してください。
62
+ 旧openAuthLink / Ticket / Consumeは以前のプロトコル向けに残ります。新アプリはopenPasscode / AuthLinkPasscodeとサーバーのAuthenticate / authenticateを使います。旧連携の移行機能は提供しません。