@auth-link/web 0.0.4 → 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/CHANGELOG.md +26 -0
- package/README.md +90 -1958
- package/dist/index.js +469 -155
- package/dist/index.js.map +3 -3
- package/dist/react.js +473 -152
- package/dist/react.js.map +3 -3
- package/dist/styles.css +6 -0
- package/dist/types/core.d.ts +2 -0
- package/dist/types/i18n.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/modal.d.ts +2 -0
- package/dist/types/passcode.d.ts +29 -0
- package/dist/types/react.d.ts +4 -0
- package/dist/types/types.d.ts +2 -1
- package/dist/types/view.d.ts +4 -0
- package/dist/types/vue.d.ts +4 -0
- package/dist/vue.js +485 -152
- package/dist/vue.js.map +3 -3
- package/docs/README.en.md +32 -375
- package/docs/README.ja.md +32 -375
- package/docs/README.zh-CN.md +32 -445
- package/examples/react-client.tsx +24 -0
- package/examples/vue-client.vue +31 -0
- package/package.json +6 -3
- package/docs/go-vue.en.md +0 -256
- package/docs/go-vue.ja.md +0 -256
- package/docs/go-vue.zh-CN.md +0 -260
- package/examples/vue-cms.vue +0 -109
package/docs/README.en.md
CHANGED
|
@@ -1,405 +1,62 @@
|
|
|
1
|
-
# AuthLink Web
|
|
1
|
+
# AuthLink Web 1.1
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](README.en.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Direct rotating-code verification
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The app directly displays a six-digit rotating code. No per-action approval, authorization requestId, businessId or polling is required. First-time enrollment still scans a QR and verifies the phone; entering its current code on the website completes binding.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
The flow is site login → Go `/api/session` returns authenticated/csrf → configure the cross-origin client with `csrfToken: () => session.csrf` → create bind/confirm → Go consumes, validates and commits the receipt → Vue dismisses the dialog → a separate business API checks the saved receipt and publishes. `baseUrl` targets Go; `authLinkOrigin` identifies AuthLink. Tenant keys stay in Go. Use your own fetch plus `validateTicket` for post/version fields; SDK create() sends purpose only.
|
|
12
|
-
|
|
13
|
-
## Features
|
|
14
|
-
|
|
15
|
-
- Phone-binding QR codes generated locally in the browser.
|
|
16
|
-
- Matching numbers that update every 30 seconds, synchronized with the AuthLink app.
|
|
17
|
-
- Confirmation, rejection, expiry and error callbacks.
|
|
18
|
-
- React components and hook, Vue components and composable, and framework-independent tools.
|
|
19
|
-
- 13 interface languages, **Simplified Chinese by default**, with per-component language selection.
|
|
20
|
-
- TypeScript declarations, separate CSS and optional React/Vue peer dependencies.
|
|
21
|
-
|
|
22
|
-
This is a browser integration library. It works with your application backend, the AuthLink service and the mobile app. The service verifies the original phone’s hardware-backed proof after the user approves in the app. Screen-lock checks and system passkeys are not required. Installing the library alone does not create an authentication service.
|
|
23
|
-
|
|
24
|
-
## Install
|
|
9
|
+
The CMS backend must reserve one immutable pending action per session before opening the prompt. Another tab cannot replace it. Obtain the user from the server session and check CSRF and business permissions. Verify the passcode on the server and atomically commit verificationId with the saved operation before returning success. onVerified only updates the UI.
|
|
25
10
|
|
|
26
11
|
```sh
|
|
27
|
-
npm install @auth-link/web
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Compatibility: React 18.2+ / 19, Vue 3.3+, TypeScript, ESM. Install only the framework you use. The package declares Node 22.13+ for tooling.
|
|
31
|
-
|
|
32
|
-
## Browser integration
|
|
33
|
-
|
|
34
|
-
1. Configure your backend's AuthLink tenant credentials.
|
|
35
|
-
2. Implement the two application backend endpoints below (same-origin or explicitly allowed cross-origin), authenticated to the current website user.
|
|
36
|
-
3. Create a request on a user action and pass its ticket to a component.
|
|
37
|
-
4. Your backend verifies and consumes the result before returning a confirmed response.
|
|
38
|
-
|
|
39
|
-
The browser can call this library directly. Tenant credentials and result consumption belong on your backend; never embed a tenant API key in frontend code. The supplied client defaults to same-origin and also supports explicitly allowed cross-origin backends. You can supply a custom `AuthLinkClient` to integrate another transport while preserving server-side credential handling and verification.
|
|
40
|
-
|
|
41
|
-
## Create a client (default same-origin session mode)
|
|
42
|
-
|
|
43
|
-
`readCsrfTokenFromYourCms` is application code, not an SDK export. It reads a CSRF token issued by your own backend for the current logged-in session; it must not generate one in the browser or return an AuthLink API key. The name does not require a CMS.
|
|
44
|
-
|
|
45
|
-
Reuse your framework's existing CSRF mechanism. Otherwise, generate a cryptographically random token on the server (for example, Node's `randomBytes(32).toString('base64url')`), persist it in the session, and render it into the authenticated page with HTML attribute escaping. `{{ csrfToken }}` below is a server-template variable, not a literal value. Do not publicly cache pages containing session tokens.
|
|
46
|
-
|
|
47
|
-
```html
|
|
48
|
-
<meta name="csrf-token" content="{{ csrfToken }}">
|
|
12
|
+
npm install @auth-link/web@^1.1.0
|
|
49
13
|
```
|
|
50
14
|
|
|
51
|
-
The SDK reads the token for every request, sends it as `X-CSRF-Token`, and includes same-origin session cookies. Your backend must validate that header against the token in the same session on both creation and result routes, reject missing/mismatched tokens, and check the expected request origin. Sending a header alone does not provide CSRF protection. Obtain a fresh token after login/session changes; a SPA may supply a token returned by its own same-origin session bootstrap endpoint. If your framework expects another header name, set `csrfHeaderName` or provide a custom `AuthLinkClient`.
|
|
52
|
-
|
|
53
|
-
Create `authlink-client.ts`:
|
|
54
|
-
|
|
55
15
|
```ts
|
|
56
|
-
|
|
57
|
-
import { createAuthLinkClient } from '@auth-link/web';
|
|
16
|
+
import { openPasscode, createPasscodeClient } from '@auth-link/web';
|
|
58
17
|
import '@auth-link/web/styles.css';
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return token;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export const client = createAuthLinkClient({
|
|
69
|
-
baseUrl: '/api/authlink',
|
|
70
|
-
csrfToken: readCsrfTokenFromYourCms,
|
|
71
|
-
// AuthLink production API origin.
|
|
72
|
-
authLinkOrigin: 'https://api.getauthlink.com',
|
|
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),
|
|
73
24
|
});
|
|
25
|
+
// modal.setLocale('en'); modal.setVariant('C'); modal.destroy();
|
|
74
26
|
```
|
|
75
27
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
## Separate frontend and backend origins (since 0.0.2)
|
|
79
|
-
|
|
80
|
-
The default is still same-origin `/api/authlink`. For direct cross-origin requests, configure an absolute `baseUrl` and exact `allowedBackendOrigins`, for example frontend `https://www.example.com` and application API `https://api.example.com`. Subdomains and different ports are different origins. Entries must be origins only, without paths, wildcards or embedded credentials, and must come from trusted configuration. HTTPS is required except for explicitly allowed HTTP loopback development origins (localhost, 127.0.0.1, [::1]).
|
|
81
|
-
|
|
82
|
-
The SDK validates the destination before evaluating credentials and refuses redirects. `authLinkOrigin` separately validates the AuthLink service origin encoded in the QR; it is not the application API allowlist.
|
|
83
|
-
|
|
84
|
-
### Cross-origin session cookies
|
|
85
|
-
|
|
86
|
-
This complete initialization function uses an example `GET /api/session` endpoint that **your application implements**, returning `{ "csrfToken": "current-session-token" }` for the authenticated session with `Cache-Control: no-store` and CORS enabled. Replace that path with your existing session endpoint.
|
|
87
|
-
|
|
88
|
-
`csrfToken` is the JSON property chosen for this generic example. The Go CMS walkthrough uses `{ authenticated: true, csrf: '…' }` instead; map session.csrf to the SDK csrfToken callback without renaming your backend response.
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
import { createAuthLinkClient } from '@auth-link/web';
|
|
92
|
-
|
|
93
|
-
/** /api/session is an example endpoint implemented by your application backend. */
|
|
94
|
-
export async function createSessionBackendClient(backendOrigin: string) {
|
|
95
|
-
const response = await fetch(`${backendOrigin}/api/session`, {
|
|
96
|
-
credentials: 'include', cache: 'no-store', redirect: 'error',
|
|
97
|
-
});
|
|
98
|
-
if (!response.ok) throw new Error('Session initialization failed');
|
|
99
|
-
const session: unknown = await response.json();
|
|
100
|
-
if (!session || typeof session !== 'object' || !('csrfToken' in session) ||
|
|
101
|
-
typeof session.csrfToken !== 'string' || !session.csrfToken) {
|
|
102
|
-
throw new Error('The session endpoint must return { csrfToken: string }');
|
|
103
|
-
}
|
|
104
|
-
let currentCsrfToken = session.csrfToken;
|
|
105
|
-
return {
|
|
106
|
-
client: createAuthLinkClient({
|
|
107
|
-
baseUrl: `${backendOrigin}/api/authlink`,
|
|
108
|
-
allowedBackendOrigins: [backendOrigin],
|
|
109
|
-
credentials: 'include',
|
|
110
|
-
csrfToken: () => currentCsrfToken,
|
|
111
|
-
}),
|
|
112
|
-
// Call after your application's session/token refresh; recreate on account changes.
|
|
113
|
-
updateCsrfToken(value: string) { currentCsrfToken = value; },
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
After login, call `await createSessionBackendClient('https://api.example.com')` and pass the returned `client` to the existing components. Call the returned `updateCsrfToken(newToken)` when your session refreshes its CSRF token. Close old dialogs and recreate session state on logout/account changes. Cross-origin cookies require `credentials: 'include'`, but their SameSite, Secure, domain/path and browser third-party cookie restrictions still apply. Cross-origin does not necessarily mean cross-site. Truly cross-site cookies typically require `SameSite=None; Secure`, and browser policy may still block them.
|
|
119
|
-
|
|
120
|
-
### Cross-origin Bearer / JWT
|
|
121
|
-
|
|
122
|
-
Pass your website's existing mutable login state. Its login/refresh flow updates `auth.accessToken`; the callback reads it on every creation and poll.
|
|
123
|
-
|
|
124
|
-
```ts
|
|
125
|
-
import { createAuthLinkClient } from '@auth-link/web';
|
|
126
|
-
|
|
127
|
-
/** auth is your website's existing login state, updated by its login/refresh flow. */
|
|
128
|
-
export function createBearerBackendClient(backendOrigin: string, auth: { accessToken: string }) {
|
|
129
|
-
return createAuthLinkClient({
|
|
130
|
-
baseUrl: `${backendOrigin}/api/authlink`,
|
|
131
|
-
allowedBackendOrigins: [backendOrigin],
|
|
132
|
-
credentials: 'omit',
|
|
133
|
-
accessToken: () => auth.accessToken,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
This must be the website user's login token, never an AuthLink tenant API key. The SDK adds `Authorization: Bearer ...`. With `accessToken`, credentials default to `omit` and CSRF can be omitted for a backend that exclusively authenticates Bearer tokens without cookie fallback. Explicit `include` or `same-origin` credentials require a CSRF provider even with Bearer authentication.
|
|
28
|
+
The default client posts only {passcode} to POST /api/auth with current CMS credentials and CSRF. To submit a form at the same time, supply your own client.verify callback and send {form,passcode} to your CMS. The CMS checks the form against its saved snapshot; never send tenant keys or passcode seeds to the browser.
|
|
139
29
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Handle `OPTIONS` preflight and return appropriate CORS headers on preflight, actual responses and errors, including the session bootstrap endpoint. For a frontend at `https://www.example.com`:
|
|
143
|
-
|
|
144
|
-
```http
|
|
145
|
-
Access-Control-Allow-Origin: https://www.example.com
|
|
146
|
-
Access-Control-Allow-Credentials: true
|
|
147
|
-
Access-Control-Allow-Methods: GET, POST, OPTIONS
|
|
148
|
-
Access-Control-Allow-Headers: Content-Type, X-CSRF-Token, Idempotency-Key, Authorization
|
|
149
|
-
Vary: Origin
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Credentialed CORS cannot use `Access-Control-Allow-Origin: *`. Match a server-side origin allowlist before returning the current allowed origin; do not reflect arbitrary origins. Preflight generally has no login credentials: validate its origin/method/headers, then authenticate the actual request and validate CSRF for cookies and request ownership. Add custom CSRF/extra headers to `Access-Control-Allow-Headers`. The SDK cannot enable backend CORS or read results using `no-cors`. See [MDN CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS).
|
|
153
|
-
|
|
154
|
-
### Transport options
|
|
155
|
-
|
|
156
|
-
| Option | Behavior |
|
|
157
|
-
| --- | --- |
|
|
158
|
-
| `baseUrl` | Application API prefix, defaults to `/api/authlink`; absolute for cross-origin |
|
|
159
|
-
| `allowedBackendOrigins` | Additional exact application backend origins; same-origin remains allowed |
|
|
160
|
-
| `credentials` | Defaults to `same-origin`, or `omit` with `accessToken`; use `include` for cross-origin cookies |
|
|
161
|
-
| `csrfToken` | Per-request sync/async token callback, required for cookie/session mode |
|
|
162
|
-
| `csrfHeaderName` | Defaults to `X-CSRF-Token`, configurable for framework integration |
|
|
163
|
-
| `accessToken` | Per-request sync/async website login token callback, without the `Bearer ` prefix |
|
|
164
|
-
| `headers` | Extra `HeadersInit`, or sync/async callback returning it |
|
|
165
|
-
| `authLinkOrigin` | Trusted QR service origin, independent of the application API origin |
|
|
166
|
-
| `fetch` | Optional implementation; destination validation still applies |
|
|
167
|
-
|
|
168
|
-
Custom headers cannot override SDK-managed `Accept`, `Content-Type`, `Authorization`, `Idempotency-Key` or the configured CSRF header (`RESERVED_HEADER`). Use `accessToken` for Bearer auth and `csrfHeaderName` to adapt framework CSRF validation. Import `ClientOptions` from the root entry for configuration types.
|
|
169
|
-
|
|
170
|
-
Unlisted origins produce `BACKEND_ORIGIN_NOT_ALLOWED` (replacing `CMS_ORIGIN_REQUIRED`); invalid allowlist entries or URLs produce `INVALID_BACKEND_ORIGIN` / `INVALID_BASE_URL`. Missing login/CSRF tokens produce `ACCESS_TOKEN_REQUIRED` / `CSRF_REQUIRED`. Cancellation and the 10-second timeout also cover async credential providers. Browser CORS failures normally surface as `NETWORK_ERROR`; inspect browser developer tools for details.
|
|
171
|
-
|
|
172
|
-
## Open a dialog directly (React, Vue or plain JavaScript)
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
import { openAuthLinkBind, openAuthLinkConfirm, requestKey } from '@auth-link/web';
|
|
176
|
-
import { client } from './authlink-client';
|
|
177
|
-
import '@auth-link/web/styles.css';
|
|
30
|
+
## Options and callbacks
|
|
178
31
|
|
|
179
|
-
|
|
180
|
-
const ticket = await client.create('confirm', { idempotencyKey: requestKey() });
|
|
181
|
-
const modal = openAuthLinkConfirm({
|
|
182
|
-
ticket, client, locale: 'en',
|
|
183
|
-
onConfirmed: result => console.log('Confirmed:', result.requestId),
|
|
184
|
-
onRejected: result => console.log(result.code), // REJECTED; dialog already closed
|
|
185
|
-
onClose: () => console.log('Dialog closed'),
|
|
186
|
-
});
|
|
187
|
-
// Bind instead: openAuthLinkBind({ ticket: bindingTicket, client }).
|
|
188
|
-
// modal.close(); modal.setLocale('ja');
|
|
189
|
-
```
|
|
32
|
+
Required: client.verify(passcode, signal) and expiresAt (the pending CMS action deadline). Optional: locale, variant A/B/C, bindingQr and authLinkOrigin for first enrollment only, onVerified, onError, onClose, onExpired. Success contains status, verificationId, authenticationPolicy and expiresAt; it has no requestId or businessId.
|
|
190
33
|
|
|
191
|
-
|
|
34
|
+
Cookie authentication requires CSRF. Bearer authentication uses accessToken with credentials=omit. For a cross-origin CMS explicitly set allowedBackendOrigins. Bare HTTPS origins are required outside loopback development.
|
|
192
35
|
|
|
193
|
-
## React
|
|
36
|
+
## React and Vue
|
|
194
37
|
|
|
195
|
-
|
|
38
|
+
The React component takes the same options. Vue emits verified, error, close and expired. Keep client references stable. Thirteen languages remain supported; Simplified Chinese is the default. A is minimal, B is split, C is compact.
|
|
196
39
|
|
|
197
40
|
```tsx
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
import { AuthLinkBind, AuthLinkConfirm } from '@auth-link/web/react';
|
|
203
|
-
import { client } from './authlink-client';
|
|
204
|
-
|
|
205
|
-
export function AuthLinkPanel({ mode }: { mode: Mode }) {
|
|
206
|
-
const [ticket, setTicket] = useState<Ticket | null>(null);
|
|
207
|
-
const [busy, setBusy] = useState(false);
|
|
208
|
-
const [message, setMessage] = useState('');
|
|
209
|
-
const key = useRef<string | null>(null);
|
|
210
|
-
const creating = useRef(false);
|
|
211
|
-
function finish(message: string) {
|
|
212
|
-
setTicket(null);
|
|
213
|
-
setMessage(message);
|
|
214
|
-
}
|
|
215
|
-
function showBound(requestId: string) {
|
|
216
|
-
// UI feedback only. The backend has already verified and saved the binding.
|
|
217
|
-
finish(`Phone bound (request ${requestId})`);
|
|
218
|
-
}
|
|
219
|
-
function showConfirmed(requestId: string) {
|
|
220
|
-
finish(`Confirmed (request ${requestId})`);
|
|
221
|
-
}
|
|
222
|
-
const start = async () => {
|
|
223
|
-
if (creating.current || ticket) return;
|
|
224
|
-
creating.current = true;
|
|
225
|
-
setBusy(true);
|
|
226
|
-
setMessage('');
|
|
227
|
-
try {
|
|
228
|
-
key.current ??= requestKey();
|
|
229
|
-
setTicket(await client.create(mode, { idempotencyKey: key.current }));
|
|
230
|
-
key.current = null;
|
|
231
|
-
} catch { setMessage('Request failed; retry the same creation.'); }
|
|
232
|
-
finally { creating.current = false; setBusy(false); }
|
|
233
|
-
};
|
|
234
|
-
const Component = mode === 'bind' ? AuthLinkBind : AuthLinkConfirm;
|
|
235
|
-
return <>
|
|
236
|
-
{!ticket && <button type="button" disabled={busy} onClick={() => void start()}>Start verification</button>}
|
|
237
|
-
{ticket && <Component ticket={ticket} client={client}
|
|
238
|
-
onConfirmed={result => result.purpose === 'bind' ? showBound(result.requestId) : showConfirmed(result.requestId)}
|
|
239
|
-
onClose={() => finish('Closed')}
|
|
240
|
-
onRejected={() => finish('Request rejected on the phone')}
|
|
241
|
-
onExpired={() => finish('Request expired; start again')}
|
|
242
|
-
onError={error => setMessage(error.message)} />}
|
|
243
|
-
<p role="status">{message}</p>
|
|
244
|
-
</>;
|
|
245
|
-
}
|
|
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} />
|
|
246
45
|
```
|
|
247
|
-
|
|
248
|
-
The backend must verify, consume and persist the binding/confirmation before returning `confirmed`. `showBound` does not grant access or save a binding. You can instead show a toast, reload your own account-status API or navigate to a management page. Reload real binding state from the backend after a page refresh; never treat a callback, local state or localStorage as authorization. The example retains the same creation idempotency key after a failed request.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
Mounting either component opens a dialog; unmount it in `onClose` (for example, clear the selected ticket). SSR renders no dialog and makes no requests. Omit `locale` for Simplified Chinese. Use `locale="zh-CN"` for Simplified Chinese or another code from the language table. Keep `client` and `ticket` stable between renders. The React entry preserves `"use client"`; SSR rendering does not create requests.
|
|
252
|
-
|
|
253
|
-
## Vue
|
|
254
|
-
|
|
255
46
|
```vue
|
|
256
|
-
<script setup
|
|
257
|
-
import {
|
|
258
|
-
import type { Ticket, Confirmation } from '@auth-link/web';
|
|
259
|
-
import { client } from './authlink-client';
|
|
260
|
-
|
|
261
|
-
defineProps<{ ticket: Ticket }>();
|
|
262
|
-
function confirmed(result: Confirmation) {
|
|
263
|
-
console.log('Confirmed request:', result.requestId);
|
|
264
|
-
}
|
|
47
|
+
<script setup>
|
|
48
|
+
import { AuthLinkPasscode } from '@auth-link/web/vue';
|
|
265
49
|
</script>
|
|
266
|
-
|
|
267
50
|
<template>
|
|
268
|
-
<
|
|
269
|
-
@
|
|
270
|
-
<AuthLinkConfirm v-else :ticket="ticket" :client="client" locale="ja"
|
|
271
|
-
@confirmed="confirmed" />
|
|
51
|
+
<AuthLinkPasscode :client="client" :expires-at="pending.expiresAt"
|
|
52
|
+
locale="zh-CN" variant="B" @verified="updateUI" />
|
|
272
53
|
</template>
|
|
273
54
|
```
|
|
274
55
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
## Choose a layout
|
|
278
|
-
|
|
279
|
-
Set `variant` when opening a dialog or mounting a component. Omit it for **A**.
|
|
280
|
-
|
|
281
|
-
| Value | Desktop | Mobile |
|
|
282
|
-
| --- | --- | --- |
|
|
283
|
-
| `A` (default) | Minimal centered card with a thin code-refresh progress bar | Centered card |
|
|
284
|
-
| `B` | QR and matching code in two columns | Vertical layout |
|
|
285
|
-
| `C` | Compact card with footer cancellation | Bottom sheet with safe-area padding |
|
|
286
|
-
|
|
287
|
-
```ts
|
|
288
|
-
openAuthLinkBind({ ticket, client }); // A, Simplified Chinese by default
|
|
289
|
-
openAuthLinkConfirm({ ticket, client, variant: 'B' });
|
|
290
|
-
// modal.setVariant('C'); // Presentation only; preserves the request and polling.
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
```tsx
|
|
294
|
-
<AuthLinkBind ticket={ticket} client={client} variant="C" />
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
```vue
|
|
298
|
-
<AuthLinkConfirm :ticket="ticket" :client="client" variant="B" />
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
All layouts show a thin progress bar and a static “Code updates automatically every 30 seconds” caption. The footer normally says “Waiting for your phone”; only in the last 30 seconds does it switch to an amber “Request expiring soon · 00:29” warning. Code changes fade in briefly, respect reduced-motion preferences, and never extend the request expiry.
|
|
302
|
-
|
|
303
|
-
All variants use the same verification, expiry and callbacks. `variant` changes can be reactive in React and Vue without restarting observation. Invalid runtime values fall back to A. The footer Cancel control in B/C has the same behavior as the close icon: it closes observation, without rejecting the server request. Confirmation still dismisses the dialog automatically. A uses a subtle close icon; all close controls retain a 44px hit target and keyboard focus indication.
|
|
304
|
-
|
|
305
|
-
## Component options
|
|
306
|
-
|
|
307
|
-
| Option | Type | Meaning |
|
|
308
|
-
| --- | --- | --- |
|
|
309
|
-
| `ticket` | `Ticket`, required | Existing request returned by your backend |
|
|
310
|
-
| `client` | `AuthLinkClient`, required | Client that queries your backend |
|
|
311
|
-
| `locale` | `Locale` | Interface language; defaults to `zh-CN` |
|
|
312
|
-
| `variant` | `DialogVariant` | `A` (default), `B` or `C`; changes layout only |
|
|
313
|
-
| `showQr` | `boolean` | Optional QR on confirmation panels; binding panels always show it |
|
|
314
|
-
| `intervalMs` | `number` | Polling interval, 250–10000 ms; default 1500 ms |
|
|
315
|
-
| `onRefresh` | `() => void` | Create a new request from the expiry panel and replace the ticket |
|
|
316
|
-
|
|
317
|
-
| React callback / Vue event | Argument | Meaning |
|
|
318
|
-
| --- | --- | --- |
|
|
319
|
-
| `onConfirmed` / `confirmed` | `Confirmation` | Backend has verified and consumed the result |
|
|
320
|
-
| `onRejected` / `rejected` | `Rejection` | Phone rejected; dialog closes automatically |
|
|
321
|
-
| `onExpired` / `expired` | None | Request expired |
|
|
322
|
-
| `onClose` / `close` | None | Dialog closed; unmount the component / clear its ticket |
|
|
323
|
-
| `onError` / `error` | `Error` | Network, response validation or callback failure |
|
|
324
|
-
|
|
325
|
-
Rejection returns `{ status: 'rejected', code: 'REJECTED', requestId, purpose }`. `REJECTED` is an SDK business status, not an HTTP status code or an authorization receipt. The SDK closes the dialog without showing a rejection/retry panel or firing `onClose`. Handle any message or new request in your website's callback. In React/Vue, clear the selected ticket in `onRejected` / `@rejected` to unmount the component. Headless hooks retain `snapshot.status === 'rejected'` for custom UI.
|
|
326
|
-
|
|
327
|
-
`useAuthLink(options)` for React and `useAuthLink(() => options)` for Vue expose `snapshot` and `retry` for custom interfaces. Vue's snapshot is a shallow ref. Retry resumes the same request; `onRefresh` creates a new one through your code.
|
|
328
|
-
|
|
329
|
-
## Backend contract
|
|
330
|
-
|
|
331
|
-
The default prefix is `/api/authlink`. Both endpoints authenticate the website user using session cookies plus the configured CSRF header, or Bearer authentication. Creation also sends a stable `Idempotency-Key`. Derive the account from the validated session/token rather than a browser-supplied account ID.
|
|
332
|
-
|
|
333
|
-
```ts
|
|
334
|
-
import type { Ticket, Result } from '@auth-link/web';
|
|
335
|
-
|
|
336
|
-
// POST /api/authlink/requests
|
|
337
|
-
// Body: { purpose: 'bind' | 'confirm' }
|
|
338
|
-
// Response: Ticket
|
|
339
|
-
|
|
340
|
-
// POST /api/authlink/requests/{requestId}/result
|
|
341
|
-
// Body: {}
|
|
342
|
-
// Response: Result
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
```json
|
|
346
|
-
{
|
|
347
|
-
"status": "confirmed",
|
|
348
|
-
"requestId": "<request-id>",
|
|
349
|
-
"verificationId": "<verification-id>",
|
|
350
|
-
"purpose": "confirm",
|
|
351
|
-
"authenticationPolicy": "device-presence.v1",
|
|
352
|
-
"expiresAt": "<same-expiry-as-ticket>"
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
Creation returns the public ticket from AuthLink: `requestId`, `purpose`, `qr`, `pairingCode`, `expiresAt` and the current `displayCode`. The display frame contains `value`, `serverTime`, `validUntil`, `periodSeconds: 30`. A confirmation ticket requires a display frame.
|
|
357
|
-
|
|
358
|
-
The result endpoint verifies the authenticated user and request ownership (plus CSRF for cookie mode) and forwards pending states and the current display frame. States `new`, `pending`, `approved` and `consumed` alone do not trigger success. Before returning `confirmed`, your backend must consume the AuthLink result with its saved state and stable consume idempotency key, check the complete receipt and persist confirmation for the correct session. Persist idempotence using request/verification IDs.
|
|
359
|
-
|
|
360
|
-
## Languages
|
|
361
|
-
|
|
362
|
-
| Code | Language |
|
|
363
|
-
| --- | --- |
|
|
364
|
-
| `en` | English |
|
|
365
|
-
| `zh-CN` | 简体中文 (default) |
|
|
366
|
-
| `zh-TW` | 繁體中文 |
|
|
367
|
-
| `ja` | 日本語 |
|
|
368
|
-
| `bn` | বাংলা |
|
|
369
|
-
| `es` | Español |
|
|
370
|
-
| `fil` | Filipino |
|
|
371
|
-
| `hi` | हिन्दी |
|
|
372
|
-
| `id` | Bahasa Indonesia |
|
|
373
|
-
| `ms` | Bahasa Melayu |
|
|
374
|
-
| `pt` | Português |
|
|
375
|
-
| `th` | ไทย |
|
|
376
|
-
| `vi` | Tiếng Việt |
|
|
377
|
-
|
|
378
|
-
Missing or unsupported locale codes fall back to Simplified Chinese. The panel sets its root `lang` attribute. Import `supportedLocales` from the root entry to get the supported codes. Language selection changes display text only, never the verification request or matching numbers. Documentation is available in English, Chinese and Japanese through the links above.
|
|
379
|
-
|
|
380
|
-
## Styles and entry points
|
|
381
|
-
|
|
382
|
-
Explicitly import `@auth-link/web/styles.css`. Override `--authlink-foreground`, `--authlink-background`, `--authlink-accent` and `--authlink-muted` on `:root` or `.authlink-dialog`; dialogs are mounted under `document.body`. All component classes use the `authlink-` prefix.
|
|
383
|
-
|
|
384
|
-
React and Vue accept `className` for the dialog. Vue also accepts standard string, array or object `class` attributes, combined with `className`. Refresh buttons use the latest `onRefresh` callback supplied by the parent.
|
|
385
|
-
|
|
386
|
-
| Import | Contents |
|
|
387
|
-
| --- | --- |
|
|
388
|
-
| `@auth-link/web` | Client, request key, supported locales, types and core tools |
|
|
389
|
-
| `@auth-link/web/react` | React components and hook |
|
|
390
|
-
| `@auth-link/web/vue` | Vue components and composable |
|
|
391
|
-
| `@auth-link/web/styles.css` | Component styles |
|
|
392
|
-
|
|
393
|
-
## Security and timing
|
|
56
|
+
## Errors and cleanup
|
|
394
57
|
|
|
395
|
-
-
|
|
396
|
-
- The browser receives only public tickets and current display frames, never generation seeds, device keys or tenant secrets.
|
|
397
|
-
- A request lasts at most 120 seconds; rotating the code does not extend it. Backend and app must support AuthLink mobile protocol 7.
|
|
398
|
-
- Countdown uses server time and a monotonic clock. Expired numbers are hidden even if a network call stalls; a short update gap at rotation is expected.
|
|
399
|
-
- Component unmount/request changes abort polling. Confirmation is deduplicated per mounted instance; your backend still needs persistent idempotence.
|
|
400
|
-
- Frontend callbacks update UI. Protected APIs must rely on trusted backend records, never a JavaScript callback or a browser-supplied success flag.
|
|
401
|
-
- QR generation is local; backend SVG is not injected and no third-party QR service receives the ticket. Allow `img-src 'self' data:` in your CSP.
|
|
58
|
+
A used code cannot authorize a second action, even in another session. After sign-in or first enrollment, wait for the next code. PASSCODE_INVALID/PASSCODE_USED/PASSCODE_LOCKED are validation errors, not success. Retain the same backend idempotency key and original code when a result is unknown; retries must not repeat the business action.
|
|
402
59
|
|
|
403
|
-
|
|
60
|
+
destroy() aborts browser work and clears timers without a close callback. close() also calls onClose. Closing a dialog does not roll back server work: your CMS must manage cancellation and its pending slot. Waiting and local expiry never poll.
|
|
404
61
|
|
|
405
|
-
|
|
62
|
+
The older openAuthLink / Ticket / Consume workflow remains available for the earlier protocol; new apps use openPasscode / AuthLinkPasscode and the backend Authenticate/authenticate method. No old-binding migration is provided.
|