@auth-link/web 0.0.0-stage → 0.0.2
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/CHANGELOG.md +62 -0
- package/LICENSE +21 -0
- package/README.md +1264 -2
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/dist/index.js +2572 -0
- package/dist/index.js.map +7 -0
- package/dist/react.js +2519 -0
- package/dist/react.js.map +7 -0
- package/dist/styles.css +126 -0
- package/dist/types/core.d.ts +40 -0
- package/dist/types/i18n.d.ts +7 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/modal.d.ts +19 -0
- package/dist/types/react.d.ts +11 -0
- package/dist/types/types.d.ts +71 -0
- package/dist/types/view.d.ts +19 -0
- package/dist/types/vue.d.ts +12 -0
- package/dist/vue.js +2550 -0
- package/dist/vue.js.map +7 -0
- package/docs/README.en.md +397 -0
- package/docs/README.ja.md +397 -0
- package/docs/README.zh-CN.md +467 -0
- package/package.json +89 -4
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# AuthLink Web
|
|
2
|
+
|
|
3
|
+
[English](../README.md) | [简体中文](./README.zh-CN.md) | **日本語**
|
|
4
|
+
|
|
5
|
+
**AuthLink のスマートフォン登録とアプリでの確認**を Web サイトに組み込む React / Vue コンポーネントです。登録用 QR コード、同期する 6 桁の照合コード、カウントダウン、確認結果を表示します。特定のサイト基盤には依存しません。
|
|
6
|
+
|
|
7
|
+
## 主な機能
|
|
8
|
+
|
|
9
|
+
- ブラウザー内で生成するスマートフォン登録用 QR コード。
|
|
10
|
+
- アプリと同期し、30 秒ごとに更新される照合コード。
|
|
11
|
+
- 承認・拒否・期限切れ・エラーのイベント。
|
|
12
|
+
- React コンポーネントと hook、Vue コンポーネントと composable、フレームワーク非依存の API。
|
|
13
|
+
- **簡体字中国語を既定とする 13 言語**。コンポーネントごとに指定可能。
|
|
14
|
+
- TypeScript、独立した CSS、任意の React / Vue peer dependency。
|
|
15
|
+
|
|
16
|
+
本パッケージはブラウザー用の接続ライブラリです。サイトのバックエンド、AuthLink サービス、モバイルアプリと併用します。AuthLink はアプリ内の承認と元の端末のハードウェア証明を検証します。システムのパスキー、指紋認証、画面ロックは要求しません。
|
|
17
|
+
|
|
18
|
+
## インストール
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install @auth-link/web
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
対応環境:React 18.2+ / 19、Vue 3.3+、TypeScript、ESM。利用するフレームワークだけをインストールしてください。ツール環境には Node 22.13+ が必要です。
|
|
25
|
+
|
|
26
|
+
## 接続の流れ
|
|
27
|
+
|
|
28
|
+
1. サイトのバックエンドに AuthLink のテナント資格情報を設定します。
|
|
29
|
+
2. 下記の業務バックエンド(同一オリジンまたは明示的に許可した別オリジン)の作成・結果取得 API を実装します。
|
|
30
|
+
3. ユーザー操作でリクエストを作成し、取得した ticket をコンポーネントに渡します。
|
|
31
|
+
4. バックエンドが結果を検証・消費した後に確認完了を返します。
|
|
32
|
+
|
|
33
|
+
ブラウザーからこのライブラリを呼び出せますが、テナント API キーと結果の消費処理は必ずサーバー側に置きます。標準クライアントは既定の同一オリジンと、明示的に許可した別オリジンに対応します。独自の通信方式では `AuthLinkClient` を実装できますが、資格情報と検証をブラウザーに移さないでください。
|
|
34
|
+
|
|
35
|
+
## クライアント作成(既定の同一オリジン Cookie モード)
|
|
36
|
+
|
|
37
|
+
`readCsrfTokenFromYourCms` は SDK のエクスポートではなく、接続先サイトで実装する関数です。現在のログインセッションに対してバックエンドが発行した CSRF token を読み取ります。CMS は必須ではありません。ブラウザーで値を生成したり、AuthLink の API キーを返したりしないでください。
|
|
38
|
+
|
|
39
|
+
既存フレームワークの CSRF 機構を優先してください。独自に実装する場合は、サーバーで暗号学的乱数(Node の `randomBytes(32).toString('base64url')` など)を生成し、セッションに保存して HTML 属性をエスケープしたうえでページに埋め込みます。下記の `{{ csrfToken }}` はサーバーテンプレートの変数です。このページを公開キャッシュしないでください。
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<meta name="csrf-token" content="{{ csrfToken }}">
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
SDK は各リクエストで token を読み取り、`X-CSRF-Token` と同一オリジンのセッション cookie を送信します。作成 API と結果 API の両方で、バックエンドが同じセッションの token と照合し、欠落・不一致や不正なリクエスト元を拒否する必要があります。ログインやセッション変更後は最新 token を取得してください。SPA ではサイト独自の同一オリジン初期化 API から取得する方法も使えます。別のヘッダー名を使うフレームワークでは`csrfHeaderName` または独自 `AuthLinkClient` で適合します。
|
|
46
|
+
|
|
47
|
+
`authlink-client.ts` を作成します:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// authlink-client.ts
|
|
51
|
+
import { createAuthLinkClient } from '@auth-link/web';
|
|
52
|
+
import '@auth-link/web/styles.css';
|
|
53
|
+
|
|
54
|
+
// The application server renders this meta element using the current session's
|
|
55
|
+
// CSRF token. This function only reads that token; it does not generate one.
|
|
56
|
+
export function readCsrfTokenFromYourCms(): string {
|
|
57
|
+
const token = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content;
|
|
58
|
+
if (!token) throw new Error('Missing CSRF token; load the authenticated application page first.');
|
|
59
|
+
return token;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const client = createAuthLinkClient({
|
|
63
|
+
baseUrl: '/api/authlink',
|
|
64
|
+
csrfToken: readCsrfTokenFromYourCms,
|
|
65
|
+
// Replace this example with the configured AuthLink service origin.
|
|
66
|
+
authLinkOrigin: 'https://auth.example.com',
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`https://auth.example.com` は実際の AuthLink オリジンに置き換えてください。リクエストは下記の React 例のようにユーザー操作で作成します。
|
|
71
|
+
|
|
72
|
+
## フロントエンドとバックエンドの別オリジン対応(0.0.2 以降)
|
|
73
|
+
|
|
74
|
+
既定は同一オリジンの `/api/authlink` です。別オリジンの業務 API に直接接続するには完全な `baseUrl` と `allowedBackendOrigins` を指定します。例:フロントエンド `https://www.example.com`、バックエンド `https://api.example.com`。サブドメインやポートが異なる場合も別オリジンです。許可リストにはパス、ワイルドカード、ユーザー名・パスワードを含めず、信頼する固定設定を使います。HTTPS が必須で、開発用 HTTP loopback(localhost / 127.0.0.1 / [::1])のみ例外です。
|
|
75
|
+
|
|
76
|
+
SDK は認証情報を読む前に接続先を検証し、リダイレクトを拒否します。`authLinkOrigin` は QR 内の AuthLink サービスオリジンの検証用で、業務 API の許可リストとは別です。
|
|
77
|
+
|
|
78
|
+
### 別オリジンの Cookie / session
|
|
79
|
+
|
|
80
|
+
下記の `GET /api/session` はサイトで実装する例示 API で、SDK が提供する API ではありません。ログイン session の `{ "csrfToken": "現在のtoken" }` を返し、`Cache-Control: no-store` と CORS を設定します。既存サイトの API パスに適合してください。
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { createAuthLinkClient } from '@auth-link/web';
|
|
84
|
+
|
|
85
|
+
/** /api/session is an example endpoint implemented by your application backend. */
|
|
86
|
+
export async function createSessionBackendClient(backendOrigin: string) {
|
|
87
|
+
const response = await fetch(`${backendOrigin}/api/session`, {
|
|
88
|
+
credentials: 'include', cache: 'no-store', redirect: 'error',
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok) throw new Error('Session initialization failed');
|
|
91
|
+
const session: unknown = await response.json();
|
|
92
|
+
if (!session || typeof session !== 'object' || !('csrfToken' in session) ||
|
|
93
|
+
typeof session.csrfToken !== 'string' || !session.csrfToken) {
|
|
94
|
+
throw new Error('The session endpoint must return { csrfToken: string }');
|
|
95
|
+
}
|
|
96
|
+
let currentCsrfToken = session.csrfToken;
|
|
97
|
+
return {
|
|
98
|
+
client: createAuthLinkClient({
|
|
99
|
+
baseUrl: `${backendOrigin}/api/authlink`,
|
|
100
|
+
allowedBackendOrigins: [backendOrigin],
|
|
101
|
+
credentials: 'include',
|
|
102
|
+
csrfToken: () => currentCsrfToken,
|
|
103
|
+
}),
|
|
104
|
+
// Call after your application's session/token refresh; recreate on account changes.
|
|
105
|
+
updateCsrfToken(value: string) { currentCsrfToken = value; },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
ログイン後に `await createSessionBackendClient('https://api.example.com')` を呼び、返された `client` を既存コンポーネントに渡します。CSRF token 更新時は `updateCsrfToken(newToken)` を呼びます。ログアウトやアカウント切替時は古いダイアログを閉じ、状態を再初期化します。別オリジン Cookie には `credentials: 'include'` が必要ですが、SameSite、Secure、domain/path、ブラウザーの第三者 Cookie 制限も適用されます。別オリジンと cross-site は同じではありません。cross-site Cookie には通常 `SameSite=None; Secure` が必要で、ブラウザーによっては引き続き拒否されます。
|
|
111
|
+
|
|
112
|
+
### Bearer token / JWT
|
|
113
|
+
|
|
114
|
+
サイトの既存ログイン状態を渡してください。ログイン・更新処理で `auth.accessToken` を更新すると、SDK は作成・ポーリングのたびに最新値を読み取ります。
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { createAuthLinkClient } from '@auth-link/web';
|
|
118
|
+
|
|
119
|
+
/** auth is your website's existing login state, updated by its login/refresh flow. */
|
|
120
|
+
export function createBearerBackendClient(backendOrigin: string, auth: { accessToken: string }) {
|
|
121
|
+
return createAuthLinkClient({
|
|
122
|
+
baseUrl: `${backendOrigin}/api/authlink`,
|
|
123
|
+
allowedBackendOrigins: [backendOrigin],
|
|
124
|
+
credentials: 'omit',
|
|
125
|
+
accessToken: () => auth.accessToken,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
これはサイト利用者のログイン token であり、AuthLink のテナント API キーではありません。SDK が `Authorization: Bearer ...` を付加します。`accessToken` 指定時は既定で `credentials: 'omit'` となり、Cookie にフォールバックせず Bearer のみで認証するバックエンドでは CSRF を省略できます。`include` / `same-origin` を明示して Cookie を併用する場合は CSRF の提供・検証も必要です。
|
|
131
|
+
|
|
132
|
+
### バックエンドの CORS
|
|
133
|
+
|
|
134
|
+
`OPTIONS` プリフライトを処理し、実リクエスト・エラー応答・session 初期化 API にも必要な CORS ヘッダーを返します。フロントエンドが `https://www.example.com` の例:
|
|
135
|
+
|
|
136
|
+
```http
|
|
137
|
+
Access-Control-Allow-Origin: https://www.example.com
|
|
138
|
+
Access-Control-Allow-Credentials: true
|
|
139
|
+
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
|
140
|
+
Access-Control-Allow-Headers: Content-Type, X-CSRF-Token, Idempotency-Key, Authorization
|
|
141
|
+
Vary: Origin
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Cookie を使う場合 `Access-Control-Allow-Origin: *` は使用できません。バックエンドの許可リストと一致する Origin のみ返し、任意の値を反射しないでください。プリフライトには通常ログイン認証情報がないため、まず Origin・メソッド・ヘッダーを検証し、実リクエストで本人確認・CSRF(Cookie モード)・所有者を検証します。独自ヘッダーや CSRF ヘッダー名は `Access-Control-Allow-Headers` に追加します。SDK はサーバーの CORS 設定を代行できず、`no-cors` で結果を読めません。[MDN CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) を参照してください。
|
|
145
|
+
|
|
146
|
+
### クライアント設定
|
|
147
|
+
|
|
148
|
+
| 設定 | 説明 |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `baseUrl` | 業務 API の接頭辞、既定 `/api/authlink`;別オリジン時は完全 URL |
|
|
151
|
+
| `allowedBackendOrigins` | 追加許可する正確な業務 API オリジンの配列 |
|
|
152
|
+
| `credentials` | 既定 `same-origin`、`accessToken` 指定時 `omit`;別オリジン Cookie は `include` |
|
|
153
|
+
| `csrfToken` | リクエストごとの同期/非同期取得関数;Cookie モードは必須 |
|
|
154
|
+
| `csrfHeaderName` | 既定 `X-CSRF-Token`、フレームワークに合わせて変更可能 |
|
|
155
|
+
| `accessToken` | サイトのログイン token 取得関数;`Bearer ` 接頭辞は付けない |
|
|
156
|
+
| `headers` | 追加 `HeadersInit`、またはそれを返す同期/非同期関数 |
|
|
157
|
+
| `authLinkOrigin` | QR 内の信頼するサービスオリジン。業務 API オリジンとは別 |
|
|
158
|
+
| `fetch` | 任意の fetch 実装;接続先検証は維持 |
|
|
159
|
+
|
|
160
|
+
追加ヘッダーは SDK 管理の `Accept`、`Content-Type`、`Authorization`、`Idempotency-Key`、設定済み CSRF ヘッダーを上書きできません(`RESERVED_HEADER`)。`accessToken` と `csrfHeaderName` を使用してください。設定型 `ClientOptions` はルートエントリーからインポートできます。
|
|
161
|
+
|
|
162
|
+
未許可オリジンは `BACKEND_ORIGIN_NOT_ALLOWED`(旧 `CMS_ORIGIN_REQUIRED` に代わる)、不正な許可設定/URL は `INVALID_BACKEND_ORIGIN` / `INVALID_BASE_URL`、token 不足は `ACCESS_TOKEN_REQUIRED` / `CSRF_REQUIRED` となります。非同期の認証情報取得にも取消しと 10 秒タイムアウトが適用されます。CORS の詳細な失敗理由は開発者ツールで確認してください。SDK では通常 `NETWORK_ERROR` になります。
|
|
163
|
+
|
|
164
|
+
## 関数でダイアログを開く(React / Vue / JavaScript)
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { openAuthLinkBind, openAuthLinkConfirm, requestKey } from '@auth-link/web';
|
|
168
|
+
import { client } from './authlink-client';
|
|
169
|
+
import '@auth-link/web/styles.css';
|
|
170
|
+
|
|
171
|
+
const ticket = await client.create('confirm', { idempotencyKey: requestKey() });
|
|
172
|
+
const modal = openAuthLinkConfirm({
|
|
173
|
+
ticket, client, locale: 'ja',
|
|
174
|
+
onConfirmed: result => console.log(result.requestId),
|
|
175
|
+
onClose: () => console.log('Closed'),
|
|
176
|
+
});
|
|
177
|
+
// 登録: openAuthLinkBind({ ticket: bindingTicket, client })
|
|
178
|
+
// modal.close(); modal.setLocale('en');
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
React / Vue コンポーネントもマウント時にダイアログを開きます。React の onClose、Vue の @close で ticket を消去してアンマウントしてください。SSR はダイアログも通信も生成しません。スマートフォンの画面幅、スクロール、閉じるボタン、Escape に対応し、ネイティブ dialog.showModal() が必要です。body 直下に表示するため、テーマ変数は :root または .authlink-dialog に設定します。
|
|
182
|
+
|
|
183
|
+
閉じてもサーバーのリクエストを拒否・取消しません。元の有効期限は維持されます。承認・拒否時は自動で閉じ、onConfirmed または onRejected を通知します。ユーザー操作の onClose は通知しません。関数で開いた場合はページ破棄時に modal.destroy() を呼んで通信と UI を解除してください。destroy() は onClose を通知しません。modal.setLocale() はリクエストを作り直さずに言語を切り替えます。
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## React
|
|
187
|
+
|
|
188
|
+
`showBound(requestId)` はサイトの表示更新用関数です。メッセージを更新し、ticket を消去してダイアログをアンマウントします。下記には拒否・期限切れ・閉じる処理も含まれます。`<AuthLinkPanel mode="bind" />` または `<AuthLinkPanel mode="confirm" />` で使います。
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
"use client";
|
|
192
|
+
import { useRef, useState } from 'react';
|
|
193
|
+
import { requestKey } from '@auth-link/web';
|
|
194
|
+
import type { Mode, Ticket } from '@auth-link/web';
|
|
195
|
+
import { AuthLinkBind, AuthLinkConfirm } from '@auth-link/web/react';
|
|
196
|
+
import { client } from './authlink-client';
|
|
197
|
+
|
|
198
|
+
export function AuthLinkPanel({ mode }: { mode: Mode }) {
|
|
199
|
+
const [ticket, setTicket] = useState<Ticket | null>(null);
|
|
200
|
+
const [busy, setBusy] = useState(false);
|
|
201
|
+
const [message, setMessage] = useState('');
|
|
202
|
+
const key = useRef<string | null>(null);
|
|
203
|
+
const creating = useRef(false);
|
|
204
|
+
function finish(message: string) {
|
|
205
|
+
setTicket(null);
|
|
206
|
+
setMessage(message);
|
|
207
|
+
}
|
|
208
|
+
function showBound(requestId: string) {
|
|
209
|
+
// UI feedback only. The backend has already verified and saved the binding.
|
|
210
|
+
finish(`Phone bound (request ${requestId})`);
|
|
211
|
+
}
|
|
212
|
+
function showConfirmed(requestId: string) {
|
|
213
|
+
finish(`Confirmed (request ${requestId})`);
|
|
214
|
+
}
|
|
215
|
+
const start = async () => {
|
|
216
|
+
if (creating.current || ticket) return;
|
|
217
|
+
creating.current = true;
|
|
218
|
+
setBusy(true);
|
|
219
|
+
setMessage('');
|
|
220
|
+
try {
|
|
221
|
+
key.current ??= requestKey();
|
|
222
|
+
setTicket(await client.create(mode, { idempotencyKey: key.current }));
|
|
223
|
+
key.current = null;
|
|
224
|
+
} catch { setMessage('Request failed; retry the same creation.'); }
|
|
225
|
+
finally { creating.current = false; setBusy(false); }
|
|
226
|
+
};
|
|
227
|
+
const Component = mode === 'bind' ? AuthLinkBind : AuthLinkConfirm;
|
|
228
|
+
return <>
|
|
229
|
+
{!ticket && <button type="button" disabled={busy} onClick={() => void start()}>Start verification</button>}
|
|
230
|
+
{ticket && <Component ticket={ticket} client={client}
|
|
231
|
+
onConfirmed={result => result.purpose === 'bind' ? showBound(result.requestId) : showConfirmed(result.requestId)}
|
|
232
|
+
onClose={() => finish('Closed')}
|
|
233
|
+
onRejected={() => finish('Request rejected on the phone')}
|
|
234
|
+
onExpired={() => finish('Request expired; start again')}
|
|
235
|
+
onError={error => setMessage(error.message)} />}
|
|
236
|
+
<p role="status">{message}</p>
|
|
237
|
+
</>;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
バックエンドは結果を検証・消費・保存してから `confirmed` を返します。`showBound` は権限付与や紐付け保存を行いません。通知、サイトのアカウント状態 API の再取得、管理ページへの移動などに置き換えられます。ページ再読み込み後はサーバーから状態を取得し、コールバックや localStorage を認可に使わないでください。作成失敗時には同じ冪等キーを再利用します。
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
`locale` を省略すると簡体字中国語です。日本語は `locale="ja"`、簡体字中国語は `locale="zh-CN"` を指定します。client と ticket はレンダー間で安定したオブジェクトにしてください。React エントリーは `"use client"` を保持し、SSR ではリクエストを作成しません。
|
|
245
|
+
|
|
246
|
+
## Vue
|
|
247
|
+
|
|
248
|
+
```vue
|
|
249
|
+
<script setup lang="ts">
|
|
250
|
+
import { AuthLinkBind, AuthLinkConfirm } from '@auth-link/web/vue';
|
|
251
|
+
import type { Ticket, Confirmation } from '@auth-link/web';
|
|
252
|
+
import { client } from './authlink-client';
|
|
253
|
+
|
|
254
|
+
defineProps<{ ticket: Ticket }>();
|
|
255
|
+
function confirmed(result: Confirmation) {
|
|
256
|
+
console.log('Confirmed request:', result.requestId);
|
|
257
|
+
}
|
|
258
|
+
</script>
|
|
259
|
+
|
|
260
|
+
<template>
|
|
261
|
+
<AuthLinkBind v-if="ticket.purpose === 'bind'" :ticket="ticket" :client="client"
|
|
262
|
+
@confirmed="confirmed" />
|
|
263
|
+
<AuthLinkConfirm v-else :ticket="ticket" :client="client" locale="ja"
|
|
264
|
+
@confirmed="confirmed" />
|
|
265
|
+
</template>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`:locale="selectedLanguage"` で表示言語を切り替えられます。言語切り替えによって新しい認証リクエストは作成されません。
|
|
269
|
+
|
|
270
|
+
## レイアウトの選択
|
|
271
|
+
|
|
272
|
+
初期化時の `variant` で選択します。省略時は **A** です。
|
|
273
|
+
|
|
274
|
+
| 値 | デスクトップ | モバイル |
|
|
275
|
+
| --- | --- | --- |
|
|
276
|
+
| `A`(既定) | ミニマルなカードと細いコード更新バー | 中央のカード |
|
|
277
|
+
| `B` | QR と照合コードの二列表示 | 縦並び |
|
|
278
|
+
| `C` | コンパクトなカードとキャンセル操作 | セーフエリア対応のボトムシート |
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
openAuthLinkBind({ ticket, client }); // A、既定言語は簡体字中国語
|
|
282
|
+
openAuthLinkConfirm({ ticket, client, variant: 'B' });
|
|
283
|
+
// modal.setVariant('C');
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
<AuthLinkBind ticket={ticket} client={client} variant="C" />
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```vue
|
|
291
|
+
<AuthLinkConfirm :ticket="ticket" :client="client" variant="B" />
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
すべてのレイアウトで細い進捗バーと「照合コードは30秒ごとに自動更新」を表示します。通常は「スマートフォンの確認待ち」を表示し、残り30秒になったときだけオレンジ色の期限警告と 00:29 形式に切り替わります。コード変更時の短いフェードはモーション低減設定に従い、リクエスト期限は延長されません。
|
|
295
|
+
|
|
296
|
+
React / Vue では表示中にも変更でき、リクエストやポーリングは再作成されません。未対応の値は A に戻ります。B / C のキャンセル操作は閉じるアイコンと同じく表示の監視を停止し、サーバーのリクエストは拒否しません。確認成功時は自動で閉じます。閉じる操作のタッチ領域は 44px で、キーボードのフォーカス表示も維持します。
|
|
297
|
+
|
|
298
|
+
## コンポーネントの設定
|
|
299
|
+
|
|
300
|
+
| 設定 | 型 | 説明 |
|
|
301
|
+
| --- | --- | --- |
|
|
302
|
+
| `ticket` | `Ticket`、必須 | バックエンドで作成したリクエスト |
|
|
303
|
+
| `client` | `AuthLinkClient`、必須 | バックエンドの結果を取得するクライアント |
|
|
304
|
+
| `locale` | `Locale` | 既定は `zh-CN` |
|
|
305
|
+
| `variant` | `DialogVariant` | `A`(既定)、`B`、`C` |
|
|
306
|
+
| `showQr` | `boolean` | 確認画面にも QR を表示。登録画面では常に表示 |
|
|
307
|
+
| `intervalMs` | `number` | 250~10000 ミリ秒。既定は 1500 ミリ秒 |
|
|
308
|
+
| `onRefresh` | `() => void` | 期限切れ後、新規作成して ticket を更新する処理 |
|
|
309
|
+
|
|
310
|
+
| React コールバック / Vue イベント | 引数 | 意味 |
|
|
311
|
+
| --- | --- | --- |
|
|
312
|
+
| `onConfirmed` / `confirmed` | `Confirmation` | バックエンドによる検証と消費が完了 |
|
|
313
|
+
| `onRejected` / `rejected` | `Rejection` | 拒否時はダイアログを自動で閉じる |
|
|
314
|
+
| `onExpired` / `expired` | なし | 有効期限切れ |
|
|
315
|
+
| `onError` / `error` | `Error` | 通信・応答検証・コールバックのエラー |
|
|
316
|
+
|
|
317
|
+
拒否時は `{ status: 'rejected', code: 'REJECTED', requestId, purpose }` を返します。`REJECTED` は SDK の業務ステータスで、HTTP ステータスや承認証明ではありません。拒否・再試行画面は表示せず、`onClose` も通知しません。メッセージや新規作成はサイト側で処理してください。React / Vue では `onRejected` / `@rejected` で ticket を解除してコンポーネントを破棄します。ヘッドレス hook の `snapshot.status` は `rejected` のままです。
|
|
318
|
+
|
|
319
|
+
独自 UI には React の `useAuthLink(options)`、Vue の `useAuthLink(() => options)` を利用できます。返される値は `snapshot` と `retry` です。Vue の snapshot は shallow ref です。retry は既存リクエストを再開し、onRefresh はサイト側のコードで新規作成します。
|
|
320
|
+
|
|
321
|
+
## バックエンド API
|
|
322
|
+
|
|
323
|
+
既定の接頭辞は `/api/authlink` です。両 API で Cookie + 設定した CSRF ヘッダー、または Bearer token による本人確認とリクエスト所有者を検証してください。作成時は `Idempotency-Key` も送信されます。サイトのユーザー ID は検証済みセッション/token から決定します。
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import type { Ticket, Result } from '@auth-link/web';
|
|
327
|
+
|
|
328
|
+
// POST /api/authlink/requests
|
|
329
|
+
// Body: { purpose: 'bind' | 'confirm' }
|
|
330
|
+
// Response: Ticket
|
|
331
|
+
|
|
332
|
+
// POST /api/authlink/requests/{requestId}/result
|
|
333
|
+
// Body: {}
|
|
334
|
+
// Response: Result
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"status": "confirmed",
|
|
340
|
+
"requestId": "<request-id>",
|
|
341
|
+
"verificationId": "<verification-id>",
|
|
342
|
+
"purpose": "confirm",
|
|
343
|
+
"authenticationPolicy": "device-presence.v1",
|
|
344
|
+
"expiresAt": "<same-expiry-as-ticket>"
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
作成 API は AuthLink の公開 ticket(`requestId`、`purpose`、`qr`、`pairingCode`、`expiresAt`、現在の `displayCode`)を返します。displayCode は `value`、`serverTime`、`validUntil`、`periodSeconds: 30` を持ちます。確認用 ticket には displayCode が必要です。
|
|
349
|
+
|
|
350
|
+
結果 API は未完了状態と最新の表示コードを返します。`new` / `pending` / `approved` / `consumed` だけでは成功イベントは発火しません。`confirmed` を返す前に、バックエンドは保存した state と消費用冪等キーで AuthLink の結果を消費し、完全な受領情報を検証して正しいセッションに記録します。requestId / verificationId による永続的な冪等処理が必要です。
|
|
351
|
+
|
|
352
|
+
## 対応言語
|
|
353
|
+
|
|
354
|
+
| Code | Language |
|
|
355
|
+
| --- | --- |
|
|
356
|
+
| `en` | English |
|
|
357
|
+
| `zh-CN` | 简体中文 (default) |
|
|
358
|
+
| `zh-TW` | 繁體中文 |
|
|
359
|
+
| `ja` | 日本語 |
|
|
360
|
+
| `bn` | বাংলা |
|
|
361
|
+
| `es` | Español |
|
|
362
|
+
| `fil` | Filipino |
|
|
363
|
+
| `hi` | हिन्दी |
|
|
364
|
+
| `id` | Bahasa Indonesia |
|
|
365
|
+
| `ms` | Bahasa Melayu |
|
|
366
|
+
| `pt` | Português |
|
|
367
|
+
| `th` | ไทย |
|
|
368
|
+
| `vi` | Tiếng Việt |
|
|
369
|
+
|
|
370
|
+
未指定・未対応の言語コードは簡体字中国語にフォールバックします。ルート要素には `lang` が設定されます。ルートエントリーの `supportedLocales` で一覧を取得できます。言語変更は表示文言だけに作用し、数字やリクエストは変更しません。ドキュメントは英語・中国語・日本語で提供します。
|
|
371
|
+
|
|
372
|
+
## スタイルとエントリー
|
|
373
|
+
|
|
374
|
+
`@auth-link/web/styles.css` を明示的に読み込みます。ダイアログは `document.body` に追加されるため、`:root` または `.authlink-dialog` で `--authlink-foreground`、`--authlink-background`、`--authlink-accent`、`--authlink-muted` を上書きします。クラス名の接頭辞は `authlink-` です。
|
|
375
|
+
|
|
376
|
+
React / Vue はダイアログの `className` に対応します。Vue は文字列・配列・オブジェクト形式の `class` も受け取り、`className` と結合します。再作成ボタンは親が渡した最新の `onRefresh` を呼び出します。
|
|
377
|
+
|
|
378
|
+
| エントリー | 内容 |
|
|
379
|
+
| --- | --- |
|
|
380
|
+
| `@auth-link/web` | クライアント、冪等キー、対応言語、型、基本 API |
|
|
381
|
+
| `@auth-link/web/react` | React コンポーネントと hook |
|
|
382
|
+
| `@auth-link/web/vue` | Vue コンポーネントと composable |
|
|
383
|
+
| `@auth-link/web/styles.css` | スタイル |
|
|
384
|
+
|
|
385
|
+
## セキュリティと有効期限
|
|
386
|
+
|
|
387
|
+
- 数字はアプリとの目視照合用です。数字だけで認証や承認はできません。
|
|
388
|
+
- ブラウザーに生成 seed、端末秘密鍵、テナント秘密情報を渡しません。
|
|
389
|
+
- リクエストは最大 120 秒。コード更新で期限は延長されません。サービスとアプリはモバイルプロトコル 7 に対応する必要があります。
|
|
390
|
+
- サーバー時刻と単調増加時計で残り時間を計算し、通信が止まっても期限切れの数字は隠します。更新境界で短い待ち時間が生じる場合があります。
|
|
391
|
+
- アンマウントや ticket 変更で監視を中止します。インスタンス内のイベント重複防止とは別に、バックエンドで永続的な冪等処理が必要です。
|
|
392
|
+
- フロントエンドのイベントは UI 更新用です。保護 API はバックエンドの信頼できる記録で判断します。
|
|
393
|
+
- QR はローカル生成し、バックエンドの SVG を挿入しません。CSP は `img-src 'self' data:` を許可してください。
|
|
394
|
+
|
|
395
|
+
## ライセンス
|
|
396
|
+
|
|
397
|
+
MIT。ライセンス全文は `LICENSE`、同梱ライブラリの帰属表示は `THIRD_PARTY_NOTICES.md` を参照してください。
|