@seamless-auth/react 0.0.10 → 0.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/CHANGELOG.md +15 -0
- package/README.md +422 -54
- package/dist/AuthProvider.d.ts +20 -29
- package/dist/client/createSeamlessAuthClient.d.ts +171 -0
- package/dist/client/webauthnPrf.d.ts +27 -0
- package/dist/components/AuthFallbackOptions.d.ts +4 -1
- package/dist/fetchWithAuth.d.ts +0 -2
- package/dist/hooks/useAuthClient.d.ts +1 -0
- package/dist/hooks/usePasskeySupport.d.ts +4 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/scopedRoles.d.ts +2 -0
- package/dist/types.d.ts +43 -0
- package/package.json +13 -4
- package/dist/context/InternalAuthContext.d.ts +0 -11
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @seamless-auth/react
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 24a17b8: Release the current React SDK improvements as the first Changesets-managed package version.
|
|
8
|
+
|
|
9
|
+
This release includes the public headless client and React hooks for custom auth UIs, expanded provider helpers for OAuth and step-up flows, scoped role utilities, OTP and passkey flow polish, and the reviewed release PR workflow for future package publishing.
|
|
10
|
+
|
|
11
|
+
## 0.1.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Last published version before Changesets-managed release PRs.
|
package/README.md
CHANGED
|
@@ -1,69 +1,87 @@
|
|
|
1
|
-
# Seamless Auth React
|
|
2
|
-
|
|
3
1
|
# @seamless-auth/react
|
|
4
2
|
|
|
5
3
|
[](https://www.npmjs.com/package/@seamless-auth/react)
|
|
4
|
+
[](https://github.com/fells-code/seamless-auth-react/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/fells-code/seamless-auth-react/actions/workflows/release.yml)
|
|
6
6
|
[](https://app.codecov.io/gh/fells-code/seamless-auth-react)
|
|
7
7
|
[](./LICENSE)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## Features
|
|
9
|
+
`@seamless-auth/react` is a React SDK for Seamless Auth. It gives you a provider for auth state, a headless client and hooks for custom auth UIs, and optional prebuilt auth routes when you want a faster drop-in flow.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
- Includes `useAuth()` hook for access to auth state and actions
|
|
15
|
-
- Ships with pre-built login, MFA, and passkey routes
|
|
16
|
-
- Lets consumer apps handle routing via `react-router-dom`
|
|
17
|
-
- Supports automatic session validation on load
|
|
11
|
+
## What It Exports
|
|
18
12
|
|
|
19
|
-
|
|
13
|
+
- `AuthProvider`
|
|
14
|
+
- `AuthRoutes`
|
|
15
|
+
- `useAuth()`
|
|
16
|
+
- `createSeamlessAuthClient()`
|
|
17
|
+
- `useAuthClient()`
|
|
18
|
+
- `usePasskeySupport()`
|
|
19
|
+
- `hasScopedRole()` and `roleGrantsAccess()`
|
|
20
|
+
- types including `AuthContextType`, `Credential`, `User`, `OAuthProvider`, `StepUpStatus`, and the headless client input/result types
|
|
20
21
|
|
|
21
22
|
## Installation
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
npm install seamless-auth
|
|
25
|
+
npm install @seamless-auth/react
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
## Releases
|
|
29
|
+
|
|
30
|
+
Published versions are listed in [CHANGELOG.md](./CHANGELOG.md) and GitHub Releases. Releases are
|
|
31
|
+
managed with Changesets: adopter-facing changes include a changeset, the `Release` workflow opens a
|
|
32
|
+
version PR for review, and merging that PR publishes the npm package with provenance from GitHub
|
|
33
|
+
Actions. See [RELEASES.md](./RELEASES.md) for maintainer release details.
|
|
34
|
+
|
|
35
|
+
## Choose Your Integration Style
|
|
36
|
+
|
|
37
|
+
You can use this package in three ways:
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
1. `AuthProvider` + `useAuth()` for auth state and core auth actions
|
|
40
|
+
2. `createSeamlessAuthClient()` or `useAuthClient()` to build fully custom login and registration screens
|
|
41
|
+
3. `AuthRoutes` when you want the built-in login, OTP, magic-link, and passkey screens
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
Most apps will use `AuthProvider` either way.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### Wrap your app with `AuthProvider`
|
|
32
48
|
|
|
33
49
|
```tsx
|
|
34
|
-
import { AuthProvider } from 'seamless-auth
|
|
50
|
+
import { AuthProvider } from '@seamless-auth/react';
|
|
35
51
|
import { BrowserRouter } from 'react-router-dom';
|
|
36
52
|
|
|
37
53
|
<BrowserRouter>
|
|
38
|
-
<AuthProvider apiHost="https://your.api
|
|
54
|
+
<AuthProvider apiHost="https://your.api">
|
|
39
55
|
<AppRoutes />
|
|
40
56
|
</AuthProvider>
|
|
41
57
|
</BrowserRouter>;
|
|
42
58
|
```
|
|
43
59
|
|
|
44
|
-
###
|
|
60
|
+
### Read auth state with `useAuth()`
|
|
45
61
|
|
|
46
62
|
```tsx
|
|
47
|
-
import { useAuth } from 'seamless-auth
|
|
63
|
+
import { useAuth } from '@seamless-auth/react';
|
|
64
|
+
|
|
65
|
+
function Dashboard() {
|
|
66
|
+
const { user, logout, refreshSession } = useAuth();
|
|
48
67
|
|
|
49
|
-
const Dashboard = () => {
|
|
50
|
-
const { user, logout } = useAuth();
|
|
51
68
|
return (
|
|
52
69
|
<div>
|
|
53
|
-
Welcome, {user?.email}
|
|
54
|
-
<button onClick={
|
|
70
|
+
<p>Welcome, {user?.email}</p>
|
|
71
|
+
<button onClick={() => void refreshSession()}>Refresh session</button>
|
|
72
|
+
<button onClick={() => void logout()}>Logout</button>
|
|
55
73
|
</div>
|
|
56
74
|
);
|
|
57
|
-
}
|
|
75
|
+
}
|
|
58
76
|
```
|
|
59
77
|
|
|
60
|
-
###
|
|
78
|
+
### Use built-in auth routes with `AuthRoutes`
|
|
61
79
|
|
|
62
80
|
```tsx
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
81
|
+
import { AuthRoutes, useAuth } from '@seamless-auth/react';
|
|
82
|
+
import { Route, Routes } from 'react-router-dom';
|
|
65
83
|
|
|
66
|
-
|
|
84
|
+
function AppRoutes() {
|
|
67
85
|
const { isAuthenticated } = useAuth();
|
|
68
86
|
|
|
69
87
|
return (
|
|
@@ -75,59 +93,409 @@ const AppRoutes = () => {
|
|
|
75
93
|
)}
|
|
76
94
|
</Routes>
|
|
77
95
|
);
|
|
78
|
-
}
|
|
96
|
+
}
|
|
79
97
|
```
|
|
80
98
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
---
|
|
99
|
+
You are still responsible for your app’s route protection and redirects.
|
|
84
100
|
|
|
85
|
-
##
|
|
101
|
+
## `useAuth()` API
|
|
86
102
|
|
|
87
|
-
|
|
103
|
+
`useAuth()` returns the current auth state plus the provider-backed helpers:
|
|
88
104
|
|
|
89
105
|
```ts
|
|
90
106
|
{
|
|
91
|
-
user:
|
|
107
|
+
user: User | null;
|
|
108
|
+
credentials: Credential[];
|
|
109
|
+
stepUpStatus: StepUpStatus | null;
|
|
92
110
|
isAuthenticated: boolean;
|
|
111
|
+
loading: boolean;
|
|
112
|
+
apiHost: string;
|
|
113
|
+
hasSignedInBefore: boolean;
|
|
114
|
+
markSignedIn(): void;
|
|
115
|
+
hasRole(role: string): boolean | undefined;
|
|
116
|
+
hasScopedRole(role: string | string[]): boolean | undefined;
|
|
117
|
+
listOAuthProviders(): Promise<OAuthProvidersResult>;
|
|
118
|
+
startOAuthLogin(input: StartOAuthLoginInput): Promise<StartOAuthLoginResult>;
|
|
119
|
+
finishOAuthLogin(input: FinishOAuthLoginInput): Promise<void>;
|
|
120
|
+
refreshSession(): Promise<void>;
|
|
121
|
+
refreshStepUpStatus(): Promise<StepUpStatus | null>;
|
|
122
|
+
verifyStepUpWithPasskey(): Promise<StepUpVerificationResult>;
|
|
123
|
+
verifyStepUpWithPasskeyPrf(input: PasskeyPrfInput): Promise<StepUpWithPasskeyPrfResult>;
|
|
93
124
|
logout(): Promise<void>;
|
|
125
|
+
logoutAllSessions(): Promise<void>;
|
|
94
126
|
deleteUser(): Promise<void>;
|
|
95
|
-
|
|
127
|
+
login(identifier: string, passkeyAvailable: boolean): Promise<Response>;
|
|
128
|
+
handlePasskeyLogin(): Promise<boolean>;
|
|
129
|
+
updateCredential(credential: Credential): Promise<Credential>;
|
|
130
|
+
deleteCredential(credentialId: string): Promise<void>;
|
|
96
131
|
}
|
|
97
132
|
```
|
|
98
133
|
|
|
99
|
-
|
|
134
|
+
Use `refreshSession()` after completing a custom auth flow that should update provider state.
|
|
135
|
+
|
|
136
|
+
### `hasSignedInBefore`
|
|
137
|
+
|
|
138
|
+
`hasSignedInBefore` is a small convenience flag backed by `localStorage`. The provider reads the `seamlessauth_seen` key on load and sets the flag to `true` after `markSignedIn()` runs.
|
|
139
|
+
|
|
140
|
+
This is mainly useful for login UIs that want to branch between first-time and returning-user behavior. For example, the built-in `Login` view uses it to default returning users to sign-in mode instead of registration.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { useAuth } from '@seamless-auth/react';
|
|
144
|
+
|
|
145
|
+
function SignInHint() {
|
|
146
|
+
const { hasSignedInBefore } = useAuth();
|
|
147
|
+
|
|
148
|
+
return hasSignedInBefore ? (
|
|
149
|
+
<p>Welcome back. Sign in with your email, phone, or passkey.</p>
|
|
150
|
+
) : (
|
|
151
|
+
<p>New here? Start by creating your account.</p>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
If you are building a fully custom flow, call `markSignedIn()` after a successful sign-in or registration step once you want future visits treated as returning-user sessions.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
const { markSignedIn, refreshSession } = useAuth();
|
|
160
|
+
|
|
161
|
+
async function completeLogin() {
|
|
162
|
+
const response = await authClient.login({
|
|
163
|
+
identifier: 'user@example.com',
|
|
164
|
+
passkeyAvailable: true,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (response.ok) {
|
|
168
|
+
markSignedIn();
|
|
169
|
+
await refreshSession();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
To disable this auto-detection entirely, pass `autoDetectPreviousSignin={false}` to `AuthProvider`.
|
|
175
|
+
|
|
176
|
+
### Scoped roles
|
|
177
|
+
|
|
178
|
+
`hasRole(role)` remains an exact role check. Use `hasScopedRole(role)` for colon-separated scoped
|
|
179
|
+
roles such as `admin:read` and `admin:write`.
|
|
100
180
|
|
|
101
|
-
|
|
181
|
+
```tsx
|
|
182
|
+
const { hasRole, hasScopedRole } = useAuth();
|
|
183
|
+
|
|
184
|
+
hasRole('admin'); // exact legacy role check
|
|
185
|
+
hasScopedRole('admin:read'); // true for admin, admin:read, or admin:write
|
|
186
|
+
hasScopedRole('admin:write'); // true for admin or admin:write
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The package also exports standalone `hasScopedRole(roles, required)` and `roleGrantsAccess(...)`
|
|
190
|
+
helpers for code that is not inside `AuthProvider`.
|
|
191
|
+
|
|
192
|
+
### Step-up authentication
|
|
193
|
+
|
|
194
|
+
Use step-up authentication before sensitive actions that should require a fresh user verification, such as deleting an account, changing MFA settings, or viewing recovery material.
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
import { useAuth } from '@seamless-auth/react';
|
|
198
|
+
|
|
199
|
+
function DeleteAccountButton() {
|
|
200
|
+
const { refreshStepUpStatus, verifyStepUpWithPasskey } = useAuth();
|
|
201
|
+
|
|
202
|
+
async function handleDeleteAccount() {
|
|
203
|
+
const status = await refreshStepUpStatus();
|
|
204
|
+
const fresh = status?.fresh ? true : (await verifyStepUpWithPasskey()).success;
|
|
205
|
+
|
|
206
|
+
if (!fresh) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await deleteAccount();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return <button onClick={() => void handleDeleteAccount()}>Delete account</button>;
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The current step-up backend supports WebAuthn/passkeys. `refreshStepUpStatus()` calls `/step-up/status`, and `verifyStepUpWithPasskey()` performs the `/step-up/webauthn/start` and `/step-up/webauthn/finish` challenge flow.
|
|
218
|
+
|
|
219
|
+
### WebAuthn PRF
|
|
220
|
+
|
|
221
|
+
WebAuthn PRF lets a compatible passkey and browser derive local key material during a WebAuthn assertion. Seamless Auth verifies the passkey assertion on the server, while the React SDK returns the PRF output only to the browser caller. PRF output is stripped before `/webAuthn/login/finish` and `/step-up/webauthn/finish`, and should never be logged, stored, or sent to your API.
|
|
222
|
+
|
|
223
|
+
Browser and authenticator support is not universal. Call `isPasskeyPrfSupported()` before offering PRF-required flows, and keep a fallback for passkeys that authenticate successfully without returning PRF output.
|
|
224
|
+
|
|
225
|
+
Treat PRF salts as sensitive in client logs. PRF output is browser-local key material; keep it in
|
|
226
|
+
memory only as long as your application needs it and do not send it to Seamless Auth or your own API.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { createSeamlessAuthClient } from '@seamless-auth/react';
|
|
230
|
+
|
|
231
|
+
const authClient = createSeamlessAuthClient({
|
|
232
|
+
apiHost: 'https://your.api',
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const prfSupported = await authClient.isPasskeyPrfSupported();
|
|
236
|
+
|
|
237
|
+
if (prfSupported) {
|
|
238
|
+
await authClient.registerPasskey({
|
|
239
|
+
metadata: {
|
|
240
|
+
friendlyName: 'My laptop',
|
|
241
|
+
platform: 'macOS',
|
|
242
|
+
browser: 'Chrome',
|
|
243
|
+
deviceInfo: navigator.userAgent,
|
|
244
|
+
},
|
|
245
|
+
requirePrf: true,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
For local key unwrap flows such as Seamless Secrets, use PRF during step-up and consume the returned bytes in browser memory:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
const result = await authClient.verifyStepUpWithPasskeyPrf({
|
|
254
|
+
salt: vaultSaltBase64url,
|
|
255
|
+
credentialId,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (!result.success || !result.prf) {
|
|
259
|
+
throw new Error('PRF step-up failed');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const vaultUnlockMaterial: { credentialId: string; output: Uint8Array } = {
|
|
263
|
+
credentialId: result.credentialId!,
|
|
264
|
+
output: result.prf.output,
|
|
265
|
+
};
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The salt may be an `ArrayBuffer`, `ArrayBufferView`, or base64url string. Authentication proves identity and user presence; the PRF output is local key material for your application to use without sending it to Seamless Auth.
|
|
269
|
+
|
|
270
|
+
### OAuth Login
|
|
271
|
+
|
|
272
|
+
OAuth lets your app offer external identity providers such as Google, GitHub, Facebook, or custom
|
|
273
|
+
OIDC-style providers configured on the Seamless Auth API. The React SDK does not receive provider
|
|
274
|
+
access tokens. It only starts the provider redirect and completes the callback so Seamless Auth can
|
|
275
|
+
issue the normal access/refresh session.
|
|
276
|
+
|
|
277
|
+
Use `listOAuthProviders()` when you want to render enabled providers dynamically:
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
import { useEffect, useState } from 'react';
|
|
281
|
+
import { useAuth } from '@seamless-auth/react';
|
|
282
|
+
import type { OAuthProvider } from '@seamless-auth/react';
|
|
283
|
+
|
|
284
|
+
function OAuthButtons() {
|
|
285
|
+
const { listOAuthProviders, startOAuthLogin } = useAuth();
|
|
286
|
+
const [providers, setProviders] = useState<OAuthProvider[]>([]);
|
|
287
|
+
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
void listOAuthProviders().then(result => setProviders(result.providers));
|
|
290
|
+
}, [listOAuthProviders]);
|
|
291
|
+
|
|
292
|
+
async function signIn(providerId: string) {
|
|
293
|
+
const result = await startOAuthLogin({
|
|
294
|
+
providerId,
|
|
295
|
+
redirectUri: `${window.location.origin}/oauth/callback`,
|
|
296
|
+
returnTo: `${window.location.origin}/dashboard`,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
window.location.assign(result.authorizationUrl);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div>
|
|
304
|
+
{providers.map(provider => (
|
|
305
|
+
<button key={provider.id} onClick={() => void signIn(provider.id)}>
|
|
306
|
+
Continue with {provider.name}
|
|
307
|
+
</button>
|
|
308
|
+
))}
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Create a callback route that reads the provider query params and asks Seamless Auth to complete the
|
|
315
|
+
login:
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
import { useEffect } from 'react';
|
|
319
|
+
import { useAuth } from '@seamless-auth/react';
|
|
320
|
+
|
|
321
|
+
function OAuthCallback() {
|
|
322
|
+
const { finishOAuthLogin } = useAuth();
|
|
323
|
+
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
const params = new URLSearchParams(window.location.search);
|
|
326
|
+
const providerId = 'google';
|
|
327
|
+
const code = params.get('code');
|
|
328
|
+
const state = params.get('state');
|
|
329
|
+
|
|
330
|
+
if (!code || !state) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
void finishOAuthLogin({ providerId, code, state }).then(() => {
|
|
335
|
+
window.location.assign('/dashboard');
|
|
336
|
+
});
|
|
337
|
+
}, [finishOAuthLogin]);
|
|
338
|
+
|
|
339
|
+
return <p>Finishing sign-in...</p>;
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
For fully custom UI without `useAuth()`, call the headless client directly:
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
const providers = await authClient.listOAuthProviders();
|
|
347
|
+
const started = await authClient.startOAuthLogin({
|
|
348
|
+
providerId: providers.providers[0].id,
|
|
349
|
+
redirectUri: `${window.location.origin}/oauth/callback`,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
window.location.assign(started.authorizationUrl);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
OAuth must be enabled on the Seamless Auth API with `LOGIN_METHODS` including `oauth` and at least
|
|
356
|
+
one configured `oauth_providers` entry. Provider client secrets live on the server and are referenced
|
|
357
|
+
by environment variable name; they are never passed through this SDK.
|
|
358
|
+
|
|
359
|
+
For production providers, configure exact `redirectUris` on the Seamless Auth API. The SDK should
|
|
360
|
+
send the callback URL it expects to receive, but redirect allowlisting, signed state expiry, OIDC
|
|
361
|
+
nonce handling, email verification policy, and account-linking policy are enforced by the API.
|
|
362
|
+
|
|
363
|
+
The built-in views avoid logging OTPs, magic-link tokens, bootstrap tokens, PRF salts, or raw
|
|
364
|
+
exception payloads that may contain sensitive request URLs.
|
|
365
|
+
|
|
366
|
+
## Headless Client
|
|
367
|
+
|
|
368
|
+
For custom auth UIs, use the exported client directly:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import { createSeamlessAuthClient } from '@seamless-auth/react';
|
|
372
|
+
|
|
373
|
+
const authClient = createSeamlessAuthClient({
|
|
374
|
+
apiHost: 'https://your.api',
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const response = await authClient.login({
|
|
378
|
+
identifier: 'user@example.com',
|
|
379
|
+
passkeyAvailable: true,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
if (response.ok) {
|
|
383
|
+
// Continue your custom flow
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The headless client exposes helpers for:
|
|
388
|
+
|
|
389
|
+
- current-user/session lookup
|
|
390
|
+
- login and passkey login
|
|
391
|
+
- registration
|
|
392
|
+
- phone OTP and email OTP
|
|
393
|
+
- magic-link request, verify, and polling
|
|
394
|
+
- OAuth provider listing, start, and callback completion
|
|
395
|
+
- passkey registration
|
|
396
|
+
- step-up status and passkey verification
|
|
397
|
+
- logout and delete-user
|
|
398
|
+
- credential update and deletion
|
|
399
|
+
|
|
400
|
+
Client methods return raw `Response` objects except for the passkey convenience helpers:
|
|
401
|
+
|
|
402
|
+
- `loginWithPasskey(options?: PasskeyLoginOptions): Promise<PasskeyLoginWithPrfResult>`
|
|
403
|
+
- `registerPasskey(metadata | { metadata, requestPrf?, requirePrf? }): Promise<PasskeyRegistrationResult>`
|
|
404
|
+
- `isPasskeyPrfSupported(): Promise<boolean>`
|
|
405
|
+
- `verifyStepUpWithPasskey(): Promise<StepUpVerificationResult>`
|
|
406
|
+
- `verifyStepUpWithPasskeyPrf(input): Promise<StepUpWithPasskeyPrfResult>`
|
|
407
|
+
- `listOAuthProviders(): Promise<OAuthProvidersResult>`
|
|
408
|
+
- `startOAuthLogin(input): Promise<StartOAuthLoginResult>`
|
|
409
|
+
- `finishOAuthLogin(input): Promise<Response>`
|
|
410
|
+
|
|
411
|
+
## React Hooks For Custom UI
|
|
412
|
+
|
|
413
|
+
If you want custom React screens but do not want to manually recreate the client, use the exported hooks:
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
import { useAuth, useAuthClient, usePasskeySupport } from '@seamless-auth/react';
|
|
417
|
+
|
|
418
|
+
function CustomLogin() {
|
|
419
|
+
const { refreshSession } = useAuth();
|
|
420
|
+
const authClient = useAuthClient();
|
|
421
|
+
const { passkeySupported, loading } = usePasskeySupport();
|
|
422
|
+
|
|
423
|
+
async function handleEmailLogin() {
|
|
424
|
+
const response = await authClient.login({
|
|
425
|
+
identifier: 'user@example.com',
|
|
426
|
+
passkeyAvailable: passkeySupported,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (response.ok) {
|
|
430
|
+
await refreshSession();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return (
|
|
435
|
+
<button disabled={loading} onClick={() => void handleEmailLogin()}>
|
|
436
|
+
Sign in
|
|
437
|
+
</button>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## Built-In Routes
|
|
443
|
+
|
|
444
|
+
`AuthRoutes` currently includes:
|
|
102
445
|
|
|
103
446
|
- `/login`
|
|
104
|
-
- `/mfaLogin`
|
|
105
447
|
- `/passKeyLogin`
|
|
448
|
+
- `/verifyPhoneOTP`
|
|
449
|
+
- `/verifyEmailOTP`
|
|
450
|
+
- `/verify-magiclink`
|
|
106
451
|
- `/registerPasskey`
|
|
107
|
-
- `/
|
|
452
|
+
- `/magiclinks-sent`
|
|
108
453
|
|
|
109
|
-
|
|
454
|
+
These are optional UI wrappers over the same SDK primitives the package now exports for custom flows.
|
|
110
455
|
|
|
111
|
-
|
|
456
|
+
## Backend Expectations
|
|
112
457
|
|
|
113
|
-
|
|
458
|
+
This package assumes a Seamless Auth-compatible backend with the auth adapter mounted at `/auth`.
|
|
114
459
|
|
|
115
|
-
|
|
460
|
+
- Requests target `${apiHost}/auth/...`
|
|
461
|
+
- `apiHost` may be provided with or without a trailing slash
|
|
462
|
+
- Requests are sent with `credentials: 'include'`
|
|
463
|
+
- `AuthProvider` validates the current session by calling `/users/me` on load
|
|
116
464
|
|
|
117
|
-
-
|
|
118
|
-
- Creating your own version
|
|
119
|
-
- Replacing the component in your app
|
|
465
|
+
The built-in flows assume compatible endpoints for:
|
|
120
466
|
|
|
121
|
-
|
|
467
|
+
- `/login`
|
|
468
|
+
- `DELETE /logout` for the current session
|
|
469
|
+
- `DELETE /logout/all` for every session owned by the current user
|
|
470
|
+
- `/registration/register`
|
|
471
|
+
- `/webAuthn/login/start`
|
|
472
|
+
- `/webAuthn/login/finish`
|
|
473
|
+
- `/webAuthn/register/start`
|
|
474
|
+
- `/webAuthn/register/finish`
|
|
475
|
+
- `/otp/generate-phone-otp`
|
|
476
|
+
- `/otp/generate-email-otp`
|
|
477
|
+
- `/otp/verify-phone-otp`
|
|
478
|
+
- `/otp/verify-email-otp`
|
|
479
|
+
- `/magic-link`
|
|
480
|
+
- `/magic-link/check`
|
|
481
|
+
- `/magic-link/verify/:token`
|
|
482
|
+
- `/oauth/providers`
|
|
483
|
+
- `/oauth/:providerId/start`
|
|
484
|
+
- `/oauth/:providerId/callback`
|
|
485
|
+
- `/step-up/status`
|
|
486
|
+
- `/step-up/webauthn/start`
|
|
487
|
+
- `/step-up/webauthn/finish`
|
|
488
|
+
- `/users/me`
|
|
489
|
+
- `/users/credentials`
|
|
490
|
+
- `/users/delete`
|
|
122
491
|
|
|
123
492
|
## Notes
|
|
124
493
|
|
|
125
|
-
- This package
|
|
126
|
-
- It is designed to
|
|
127
|
-
- The `AuthProvider`
|
|
128
|
-
|
|
129
|
-
---
|
|
494
|
+
- This package does not create its own `<BrowserRouter>`.
|
|
495
|
+
- It is designed to fit into your app’s existing routing tree.
|
|
496
|
+
- The quickest path is `AuthProvider` + `AuthRoutes`.
|
|
497
|
+
- The most flexible path is `AuthProvider` + custom UI using `useAuth()`, `useAuthClient()`, and `usePasskeySupport()`.
|
|
130
498
|
|
|
131
499
|
## License
|
|
132
500
|
|
|
133
|
-
|
|
501
|
+
AGPL-3.0-only
|
package/dist/AuthProvider.d.ts
CHANGED
|
@@ -1,44 +1,36 @@
|
|
|
1
|
+
import { FinishOAuthLoginInput, OAuthProvidersResult, StartOAuthLoginInput, StartOAuthLoginResult, StepUpWithPasskeyPrfResult, StepUpStatus, StepUpVerificationResult } from '@/client/createSeamlessAuthClient';
|
|
2
|
+
import { PasskeyPrfInput } from '@/client/webauthnPrf';
|
|
3
|
+
import { Credential, Organization, User } from '@/types';
|
|
1
4
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { AuthMode } from './fetchWithAuth';
|
|
3
|
-
import { AuthenticatorTransportFuture, CredentialDeviceType } from '@simplewebauthn/browser';
|
|
4
|
-
export interface User {
|
|
5
|
-
id: string;
|
|
6
|
-
email: string;
|
|
7
|
-
phone: string;
|
|
8
|
-
roles?: string[];
|
|
9
|
-
}
|
|
10
|
-
type AuthToken = {
|
|
11
|
-
oneTimeToken: string;
|
|
12
|
-
expiresAt: string;
|
|
13
|
-
};
|
|
14
5
|
export interface AuthContextType {
|
|
15
6
|
user: User | null;
|
|
16
|
-
logout: () => void
|
|
17
|
-
|
|
7
|
+
logout: () => Promise<void>;
|
|
8
|
+
logoutAllSessions: () => Promise<void>;
|
|
9
|
+
deleteUser: () => Promise<void>;
|
|
10
|
+
refreshSession: () => Promise<void>;
|
|
18
11
|
isAuthenticated: boolean;
|
|
19
12
|
hasRole: (role: string) => boolean | undefined;
|
|
13
|
+
hasScopedRole: (role: string | string[]) => boolean | undefined;
|
|
20
14
|
apiHost: string;
|
|
21
|
-
token: AuthToken | null;
|
|
22
15
|
markSignedIn: () => void;
|
|
23
16
|
hasSignedInBefore: boolean;
|
|
24
|
-
mode: AuthMode;
|
|
25
17
|
credentials: Credential[];
|
|
18
|
+
organizations: Organization[];
|
|
19
|
+
activeOrganization: Organization | null;
|
|
20
|
+
switchOrganization: (organizationId: string) => Promise<void>;
|
|
21
|
+
listOAuthProviders: () => Promise<OAuthProvidersResult>;
|
|
22
|
+
startOAuthLogin: (input: StartOAuthLoginInput) => Promise<StartOAuthLoginResult>;
|
|
23
|
+
finishOAuthLogin: (input: FinishOAuthLoginInput) => Promise<void>;
|
|
24
|
+
stepUpStatus: StepUpStatus | null;
|
|
26
25
|
updateCredential: (credential: Credential) => Promise<Credential>;
|
|
27
26
|
deleteCredential: (credentialId: string) => Promise<void>;
|
|
27
|
+
login: (identifier: string, passkeyAvailable: boolean) => Promise<Response | null>;
|
|
28
|
+
handlePasskeyLogin: () => Promise<boolean>;
|
|
29
|
+
refreshStepUpStatus: () => Promise<StepUpStatus | null>;
|
|
30
|
+
verifyStepUpWithPasskey: () => Promise<StepUpVerificationResult>;
|
|
31
|
+
verifyStepUpWithPasskeyPrf: (input: PasskeyPrfInput) => Promise<StepUpWithPasskeyPrfResult>;
|
|
28
32
|
loading: boolean;
|
|
29
33
|
}
|
|
30
|
-
export interface Credential {
|
|
31
|
-
id: string;
|
|
32
|
-
counter: number;
|
|
33
|
-
transports?: AuthenticatorTransportFuture[];
|
|
34
|
-
deviceType: CredentialDeviceType;
|
|
35
|
-
backedup: boolean;
|
|
36
|
-
friendlyName: string | null;
|
|
37
|
-
lastUsedAt: Date | null;
|
|
38
|
-
platform: string | null;
|
|
39
|
-
browser: string | null;
|
|
40
|
-
deviceInfo: string | null;
|
|
41
|
-
}
|
|
42
34
|
/**
|
|
43
35
|
* Returns the current context
|
|
44
36
|
* @returns {AuthContextType} AuthContext
|
|
@@ -48,7 +40,6 @@ interface AuthProviderProps {
|
|
|
48
40
|
children: ReactNode;
|
|
49
41
|
apiHost: string;
|
|
50
42
|
autoDetectPreviousSignin?: boolean;
|
|
51
|
-
mode?: AuthMode;
|
|
52
43
|
}
|
|
53
44
|
export declare const AuthProvider: React.FC<AuthProviderProps>;
|
|
54
45
|
export {};
|