@authon/react-native 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +181 -83
- package/README.md +124 -346
- package/dist/index.cjs +10 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,35 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
# @authon/react-native
|
|
4
4
|
|
|
5
|
-
React Native SDK for [Authon](https://authon.dev)
|
|
5
|
+
React Native SDK for [Authon](https://authon.dev). It provides:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
- `AuthonProvider` for app-wide auth state
|
|
8
|
+
- `useAuthon()` and `useUser()` hooks
|
|
9
|
+
- secure token persistence through a storage adapter
|
|
10
|
+
- `SocialButton` / `SocialButtons`
|
|
11
|
+
- low-level OAuth helpers (`startOAuth`, `completeOAuth`, `client`)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Install
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm install react-native react-native-svg
|
|
16
|
+
npm install @authon/react-native react-native-svg
|
|
17
|
+
npx expo install expo-secure-store expo-web-browser
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| `react` | `^18.0.0 \|\| ^19.0.0` |
|
|
22
|
-
| `react-native` | `>=0.70.0` |
|
|
23
|
-
| `react-native-svg` | `>=12.0.0` |
|
|
24
|
-
|
|
25
|
-
---
|
|
20
|
+
`expo-secure-store` is the recommended storage adapter for Expo apps.
|
|
21
|
+
`expo-web-browser` is optional but recommended when you want a more controlled OAuth flow in Expo.
|
|
26
22
|
|
|
27
23
|
## Setup
|
|
28
24
|
|
|
29
|
-
Wrap your app with `AuthonProvider` and pass a token storage adapter. For secure storage, use `expo-secure-store` (Expo) or `react-native-keychain` (bare RN).
|
|
30
|
-
|
|
31
25
|
```tsx
|
|
32
|
-
// App.tsx
|
|
33
|
-
import React from 'react';
|
|
34
26
|
import { AuthonProvider } from '@authon/react-native';
|
|
35
27
|
import * as SecureStore from 'expo-secure-store';
|
|
36
28
|
|
|
@@ -49,62 +41,48 @@ export default function App() {
|
|
|
49
41
|
}
|
|
50
42
|
```
|
|
51
43
|
|
|
52
|
-
Without a storage adapter, tokens
|
|
53
|
-
|
|
54
|
-
---
|
|
44
|
+
Without a storage adapter, tokens remain in memory only.
|
|
55
45
|
|
|
56
46
|
## Hooks
|
|
57
47
|
|
|
58
48
|
### `useAuthon()`
|
|
59
49
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
import { useAuthon } from '@authon/react-native';
|
|
64
|
-
|
|
50
|
+
```ts
|
|
65
51
|
const {
|
|
66
|
-
isLoaded,
|
|
67
|
-
isSignedIn,
|
|
68
|
-
userId,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
client, // AuthonMobileClient — underlying client instance
|
|
52
|
+
isLoaded,
|
|
53
|
+
isSignedIn,
|
|
54
|
+
userId,
|
|
55
|
+
accessToken,
|
|
56
|
+
user,
|
|
57
|
+
signIn,
|
|
58
|
+
signUp,
|
|
59
|
+
signOut,
|
|
60
|
+
getToken,
|
|
61
|
+
providers,
|
|
62
|
+
branding,
|
|
63
|
+
startOAuth,
|
|
64
|
+
completeOAuth,
|
|
65
|
+
on,
|
|
66
|
+
client,
|
|
82
67
|
} = useAuthon();
|
|
83
68
|
```
|
|
84
69
|
|
|
85
70
|
### `useUser()`
|
|
86
71
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
import { useUser } from '@authon/react-native';
|
|
91
|
-
|
|
72
|
+
```ts
|
|
92
73
|
const { isLoaded, isSignedIn, user } = useUser();
|
|
93
74
|
```
|
|
94
75
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
## Examples
|
|
98
|
-
|
|
99
|
-
### Login screen with email and password
|
|
76
|
+
## Email / Password Example
|
|
100
77
|
|
|
101
78
|
```tsx
|
|
102
|
-
import
|
|
79
|
+
import { useState } from 'react';
|
|
103
80
|
import { View, TextInput, Button, Text, ActivityIndicator } from 'react-native';
|
|
104
|
-
import { useAuthon } from '@authon/react-native';
|
|
81
|
+
import { useAuthon, useUser } from '@authon/react-native';
|
|
105
82
|
|
|
106
83
|
export function LoginScreen() {
|
|
107
|
-
const {
|
|
84
|
+
const { isLoaded } = useUser();
|
|
85
|
+
const { signIn, signOut, user, isSignedIn } = useAuthon();
|
|
108
86
|
const [email, setEmail] = useState('');
|
|
109
87
|
const [password, setPassword] = useState('');
|
|
110
88
|
const [loading, setLoading] = useState(false);
|
|
@@ -116,7 +94,7 @@ export function LoginScreen() {
|
|
|
116
94
|
try {
|
|
117
95
|
await signIn({ strategy: 'email_password', email, password });
|
|
118
96
|
} catch (err: any) {
|
|
119
|
-
setError(err.message);
|
|
97
|
+
setError(err.message ?? 'Sign-in failed');
|
|
120
98
|
} finally {
|
|
121
99
|
setLoading(false);
|
|
122
100
|
}
|
|
@@ -124,350 +102,150 @@ export function LoginScreen() {
|
|
|
124
102
|
|
|
125
103
|
if (!isLoaded) return <ActivityIndicator />;
|
|
126
104
|
|
|
105
|
+
if (isSignedIn) {
|
|
106
|
+
return (
|
|
107
|
+
<View style={{ padding: 24, gap: 12 }}>
|
|
108
|
+
<Text>Welcome, {user?.displayName ?? user?.email}</Text>
|
|
109
|
+
<Button title="Sign out" onPress={signOut} />
|
|
110
|
+
</View>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
127
114
|
return (
|
|
128
115
|
<View style={{ padding: 24, gap: 12 }}>
|
|
129
|
-
<TextInput
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
keyboardType="email-address"
|
|
134
|
-
autoCapitalize="none"
|
|
135
|
-
/>
|
|
136
|
-
<TextInput
|
|
137
|
-
placeholder="Password"
|
|
138
|
-
value={password}
|
|
139
|
-
onChangeText={setPassword}
|
|
140
|
-
secureTextEntry
|
|
141
|
-
/>
|
|
142
|
-
{error && <Text style={{ color: 'red' }}>{error}</Text>}
|
|
143
|
-
<Button title={loading ? 'Signing in...' : 'Sign In'} onPress={handleSignIn} disabled={loading} />
|
|
116
|
+
<TextInput placeholder="Email" value={email} onChangeText={setEmail} autoCapitalize="none" />
|
|
117
|
+
<TextInput placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry />
|
|
118
|
+
{error ? <Text style={{ color: 'red' }}>{error}</Text> : null}
|
|
119
|
+
<Button title={loading ? 'Signing in...' : 'Sign in'} onPress={handleSignIn} disabled={loading} />
|
|
144
120
|
</View>
|
|
145
121
|
);
|
|
146
122
|
}
|
|
147
123
|
```
|
|
148
124
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
Use the built-in `SocialButtons` component. It fetches the enabled providers from your Authon project and renders buttons automatically.
|
|
125
|
+
## Social Buttons
|
|
152
126
|
|
|
153
127
|
```tsx
|
|
154
|
-
import React from 'react';
|
|
155
|
-
import { View } from 'react-native';
|
|
156
128
|
import { SocialButtons } from '@authon/react-native';
|
|
157
129
|
|
|
158
130
|
export function SocialLoginSection() {
|
|
159
131
|
return (
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
/>
|
|
165
|
-
</View>
|
|
132
|
+
<SocialButtons
|
|
133
|
+
onSuccess={() => console.log('Signed in')}
|
|
134
|
+
onError={(error) => console.error(error)}
|
|
135
|
+
/>
|
|
166
136
|
);
|
|
167
137
|
}
|
|
168
138
|
```
|
|
169
139
|
|
|
170
|
-
|
|
140
|
+
For fully custom buttons, use `SocialButton` or call `startOAuth()` / `completeOAuth()` yourself.
|
|
171
141
|
|
|
172
|
-
|
|
173
|
-
<SocialButtons
|
|
174
|
-
compact
|
|
175
|
-
onSuccess={() => console.log('Signed in')}
|
|
176
|
-
onError={(err) => console.error(err)}
|
|
177
|
-
/>
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Individual `SocialButton`:
|
|
181
|
-
|
|
182
|
-
```tsx
|
|
183
|
-
import { SocialButton } from '@authon/react-native';
|
|
184
|
-
|
|
185
|
-
<SocialButton
|
|
186
|
-
provider="google"
|
|
187
|
-
onPress={(provider) => handleOAuth(provider)}
|
|
188
|
-
loading={isLoading}
|
|
189
|
-
label="Continue with Google" // optional — defaults to "Continue with Google"
|
|
190
|
-
compact={false} // optional — icon-only square button
|
|
191
|
-
height={48} // optional — button height in px
|
|
192
|
-
borderRadius={10} // optional
|
|
193
|
-
/>
|
|
194
|
-
```
|
|
142
|
+
## Manual OAuth Flow
|
|
195
143
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
`SocialButtons` handles OAuth automatically. For custom flows, use `startOAuth` and `completeOAuth` directly. OAuth uses the device browser (via `Linking.openURL`) rather than a popup.
|
|
144
|
+
The basic SDK flow looks like this:
|
|
199
145
|
|
|
200
146
|
```tsx
|
|
201
|
-
import
|
|
202
|
-
import { Button, Linking } from 'react-native';
|
|
147
|
+
import { Linking } from 'react-native';
|
|
203
148
|
import { useAuthon } from '@authon/react-native';
|
|
204
149
|
|
|
205
|
-
export function
|
|
150
|
+
export function GoogleButton() {
|
|
206
151
|
const { startOAuth, completeOAuth } = useAuthon();
|
|
207
|
-
const [loading, setLoading] = useState(false);
|
|
208
152
|
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// Open the OAuth URL in the default browser
|
|
214
|
-
await Linking.openURL(url);
|
|
215
|
-
// Poll for OAuth completion (3 minute timeout)
|
|
216
|
-
await completeOAuth(state);
|
|
217
|
-
} catch (err: any) {
|
|
218
|
-
if (err.message !== 'OAuth timeout') {
|
|
219
|
-
console.error('OAuth failed:', err);
|
|
220
|
-
}
|
|
221
|
-
} finally {
|
|
222
|
-
setLoading(false);
|
|
223
|
-
}
|
|
153
|
+
const handlePress = async () => {
|
|
154
|
+
const { url, state } = await startOAuth('google');
|
|
155
|
+
await Linking.openURL(url);
|
|
156
|
+
await completeOAuth(state);
|
|
224
157
|
};
|
|
225
158
|
|
|
226
|
-
|
|
227
|
-
<Button
|
|
228
|
-
title={loading ? 'Signing in...' : 'Sign in with Google'}
|
|
229
|
-
onPress={handleGoogleSignIn}
|
|
230
|
-
disabled={loading}
|
|
231
|
-
/>
|
|
232
|
-
);
|
|
159
|
+
// ...
|
|
233
160
|
}
|
|
234
161
|
```
|
|
235
162
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
```tsx
|
|
239
|
-
import React, { useEffect, useState } from 'react';
|
|
240
|
-
import { View, Text, Button, FlatList } from 'react-native';
|
|
241
|
-
import { useAuthon } from '@authon/react-native';
|
|
242
|
-
|
|
243
|
-
export function SessionsScreen() {
|
|
244
|
-
const { client, userId, signOut } = useAuthon();
|
|
245
|
-
const [sessions, setSessions] = useState<any[]>([]);
|
|
163
|
+
This is the simplest option, but it is browser-driven and completion is polling-based.
|
|
246
164
|
|
|
247
|
-
|
|
248
|
-
if (!userId) return;
|
|
249
|
-
// Access the underlying AuthonMobileClient for advanced operations
|
|
250
|
-
client.getUser().then(() => {
|
|
251
|
-
// sessions are managed server-side; use @authon/node on the backend
|
|
252
|
-
// to list and revoke sessions via authon.sessions.list(userId)
|
|
253
|
-
});
|
|
254
|
-
}, [userId]);
|
|
165
|
+
## Recommended Expo OAuth Flow
|
|
255
166
|
|
|
256
|
-
|
|
257
|
-
<View>
|
|
258
|
-
<Button title="Sign Out (all devices)" onPress={signOut} />
|
|
259
|
-
</View>
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### MFA setup
|
|
265
|
-
|
|
266
|
-
MFA (TOTP) is configured on the client side through the underlying `@authon/js` client. Access it via `client` from `useAuthon()`.
|
|
167
|
+
If you want a cleaner Android experience with `expo-web-browser`, request the OAuth URL manually with `flow=redirect` and `returnTo`.
|
|
267
168
|
|
|
268
169
|
```tsx
|
|
269
|
-
import
|
|
270
|
-
import {
|
|
170
|
+
import * as WebBrowser from 'expo-web-browser';
|
|
171
|
+
import { Button } from 'react-native';
|
|
271
172
|
import { useAuthon } from '@authon/react-native';
|
|
272
173
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
174
|
+
const API_URL = 'https://api.authon.dev';
|
|
175
|
+
const PUBLISHABLE_KEY = 'pk_live_...';
|
|
176
|
+
const APP_DEEP_LINK = 'myapp://oauth-callback';
|
|
177
|
+
const RETURN_TO = 'https://auth.example.com/authon/mobile-callback';
|
|
178
|
+
|
|
179
|
+
async function requestOAuthUrl(provider: 'google') {
|
|
180
|
+
const params = new URLSearchParams({
|
|
181
|
+
redirectUri: `${API_URL}/v1/auth/oauth/redirect`,
|
|
182
|
+
flow: 'redirect',
|
|
183
|
+
returnTo: RETURN_TO,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const response = await fetch(
|
|
187
|
+
`${API_URL}/v1/auth/oauth/${provider}/url?${params.toString()}`,
|
|
188
|
+
{ headers: { 'x-api-key': PUBLISHABLE_KEY } },
|
|
189
|
+
);
|
|
278
190
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (!jsClient) return;
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
throw new Error(await response.text());
|
|
193
|
+
}
|
|
283
194
|
|
|
284
|
-
|
|
285
|
-
setQrCodeUri(setup.qrCodeUri);
|
|
286
|
-
setBackupCodes(setup.backupCodes);
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const verifyMfa = async () => {
|
|
290
|
-
const jsClient = (client as any)._jsClient;
|
|
291
|
-
await jsClient.verifyMfaSetup(code);
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
return (
|
|
295
|
-
<View style={{ padding: 24, gap: 12 }}>
|
|
296
|
-
{qrCodeUri ? (
|
|
297
|
-
<>
|
|
298
|
-
<Image source={{ uri: qrCodeUri }} style={{ width: 200, height: 200 }} />
|
|
299
|
-
<Text>Backup codes: {backupCodes.join(', ')}</Text>
|
|
300
|
-
<TextInput
|
|
301
|
-
placeholder="Enter 6-digit code"
|
|
302
|
-
value={code}
|
|
303
|
-
onChangeText={setCode}
|
|
304
|
-
keyboardType="numeric"
|
|
305
|
-
/>
|
|
306
|
-
<Button title="Verify" onPress={verifyMfa} />
|
|
307
|
-
</>
|
|
308
|
-
) : (
|
|
309
|
-
<Button title="Enable MFA" onPress={setupMfa} />
|
|
310
|
-
)}
|
|
311
|
-
</View>
|
|
312
|
-
);
|
|
195
|
+
return response.json() as Promise<{ url: string; state: string }>;
|
|
313
196
|
}
|
|
314
|
-
```
|
|
315
197
|
|
|
316
|
-
|
|
198
|
+
export function GoogleButton() {
|
|
199
|
+
const { completeOAuth, getToken } = useAuthon();
|
|
317
200
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
import { useAuthon } from '@authon/react-native';
|
|
201
|
+
const handlePress = async () => {
|
|
202
|
+
const { url, state } = await requestOAuthUrl('google');
|
|
203
|
+
const pollPromise = completeOAuth(state);
|
|
322
204
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const [walletAddress, setWalletAddress] = useState<string | null>(null);
|
|
326
|
-
|
|
327
|
-
const connectWallet = async () => {
|
|
328
|
-
// Request nonce from Authon API via the underlying client
|
|
329
|
-
const apiUrl = client.getApiUrl();
|
|
330
|
-
const token = client.getAccessToken();
|
|
331
|
-
|
|
332
|
-
// 1. Get a sign message from Authon
|
|
333
|
-
const nonceRes = await fetch(`${apiUrl}/v1/auth/web3/nonce`, {
|
|
334
|
-
method: 'POST',
|
|
335
|
-
headers: {
|
|
336
|
-
'Content-Type': 'application/json',
|
|
337
|
-
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
338
|
-
'x-api-key': 'pk_live_...',
|
|
339
|
-
},
|
|
340
|
-
body: JSON.stringify({ address: walletAddress, chain: 'evm' }),
|
|
341
|
-
});
|
|
342
|
-
const { message } = await nonceRes.json();
|
|
343
|
-
|
|
344
|
-
// 2. Sign with your wallet library (e.g. WalletConnect, ethers)
|
|
345
|
-
// const signature = await wallet.signMessage(message);
|
|
346
|
-
|
|
347
|
-
// 3. Verify and link wallet
|
|
348
|
-
// await fetch(`${apiUrl}/v1/auth/web3/verify`, { ... })
|
|
349
|
-
};
|
|
205
|
+
await WebBrowser.openAuthSessionAsync(url, APP_DEEP_LINK);
|
|
206
|
+
await pollPromise;
|
|
350
207
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
</View>
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### Auth event subscription
|
|
360
|
-
|
|
361
|
-
```tsx
|
|
362
|
-
import { useEffect } from 'react';
|
|
363
|
-
import { useAuthon } from '@authon/react-native';
|
|
208
|
+
const authonAccessToken = getToken();
|
|
209
|
+
// If your app also has its own backend session, exchange authonAccessToken here.
|
|
210
|
+
};
|
|
364
211
|
|
|
365
|
-
|
|
366
|
-
const { on } = useAuthon();
|
|
367
|
-
|
|
368
|
-
useEffect(() => {
|
|
369
|
-
const unsubSignedIn = on('signedIn', (user) => {
|
|
370
|
-
console.log('User signed in:', user.id);
|
|
371
|
-
});
|
|
372
|
-
const unsubSignedOut = on('signedOut', () => {
|
|
373
|
-
console.log('User signed out');
|
|
374
|
-
});
|
|
375
|
-
const unsubRefreshed = on('tokenRefreshed', () => {
|
|
376
|
-
console.log('Token refreshed');
|
|
377
|
-
});
|
|
378
|
-
const unsubError = on('error', (err) => {
|
|
379
|
-
console.error('Auth error:', err);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
return () => {
|
|
383
|
-
unsubSignedIn();
|
|
384
|
-
unsubSignedOut();
|
|
385
|
-
unsubRefreshed();
|
|
386
|
-
unsubError();
|
|
387
|
-
};
|
|
388
|
-
}, [on]);
|
|
389
|
-
|
|
390
|
-
return null;
|
|
212
|
+
return <Button title="Continue with Google" onPress={handlePress} />;
|
|
391
213
|
}
|
|
392
214
|
```
|
|
393
215
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
## OAuth Note
|
|
216
|
+
Example HTTPS bridge page:
|
|
397
217
|
|
|
398
|
-
|
|
218
|
+
```html
|
|
219
|
+
<!doctype html>
|
|
220
|
+
<html>
|
|
221
|
+
<body>
|
|
222
|
+
<script>
|
|
223
|
+
const params = new URLSearchParams(window.location.search);
|
|
224
|
+
const state = params.get('authon_oauth_state');
|
|
225
|
+
const error = params.get('authon_oauth_error');
|
|
399
226
|
|
|
400
|
-
|
|
227
|
+
const target = new URL('myapp://oauth-callback');
|
|
228
|
+
if (state) target.searchParams.set('state', state);
|
|
229
|
+
if (error) target.searchParams.set('error', error);
|
|
401
230
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
| Prop | Type | Description |
|
|
407
|
-
|---|---|---|
|
|
408
|
-
| `publishableKey` | `string` | Your Authon publishable key (`pk_live_...`) |
|
|
409
|
-
| `apiUrl` | `string` | Optional — custom API base URL |
|
|
410
|
-
| `storage` | `TokenStorage` | Optional — secure storage adapter for token persistence |
|
|
411
|
-
| `children` | `React.ReactNode` | App content |
|
|
412
|
-
|
|
413
|
-
### `TokenStorage` interface
|
|
414
|
-
|
|
415
|
-
```typescript
|
|
416
|
-
interface TokenStorage {
|
|
417
|
-
getItem(key: string): Promise<string | null>;
|
|
418
|
-
setItem(key: string, value: string): Promise<void>;
|
|
419
|
-
removeItem(key: string): Promise<void>;
|
|
420
|
-
}
|
|
231
|
+
window.location.replace(target.toString());
|
|
232
|
+
</script>
|
|
233
|
+
</body>
|
|
234
|
+
</html>
|
|
421
235
|
```
|
|
422
236
|
|
|
423
|
-
|
|
237
|
+
## Important Notes
|
|
424
238
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
provider?: string;
|
|
431
|
-
}
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
### `SignUpParams`
|
|
239
|
+
- Do not register `myapp://...` directly as the provider redirect URI. Keep provider redirect URIs on `{apiUrl}/v1/auth/oauth/redirect`.
|
|
240
|
+
- Use `returnTo` for your app callback bridge. `returnTo` should be an HTTPS URL you control, and its origin must be listed in `ALLOWED_REDIRECT_ORIGINS`.
|
|
241
|
+
- If you need a custom app scheme, let the HTTPS bridge page redirect into `myapp://...`.
|
|
242
|
+
- On Android, `dismissAuthSession()` cannot reliably close an already-open Custom Tab. Plan your flow around `openAuthSessionAsync()` and a proper bridge.
|
|
243
|
+
- If your mobile app also maintains its own backend session, exchange `getToken()` with your backend immediately after `completeOAuth()`.
|
|
435
244
|
|
|
436
|
-
|
|
437
|
-
interface SignUpParams {
|
|
438
|
-
email: string;
|
|
439
|
-
password: string;
|
|
440
|
-
displayName?: string;
|
|
441
|
-
}
|
|
442
|
-
```
|
|
245
|
+
## Docs
|
|
443
246
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
| Method | Returns | Description |
|
|
447
|
-
|---|---|---|
|
|
448
|
-
| `signIn(params)` | `Promise<{ tokens, user }>` | Sign in with email/password or OAuth |
|
|
449
|
-
| `signUp(params)` | `Promise<{ tokens, user }>` | Create account and sign in |
|
|
450
|
-
| `signOut()` | `Promise<void>` | Sign out and clear local session |
|
|
451
|
-
| `getUser()` | `Promise<AuthonUser \| null>` | Fetch current user from API |
|
|
452
|
-
| `getCachedUser()` | `AuthonUser \| null` | Return locally cached user |
|
|
453
|
-
| `getAccessToken()` | `string \| null` | Return current access token |
|
|
454
|
-
| `isAuthenticated()` | `boolean` | Check if token is valid and not expired |
|
|
455
|
-
| `refreshToken(token?)` | `Promise<TokenPair \| null>` | Refresh the access token |
|
|
456
|
-
| `getProviders()` | `Promise<OAuthProviderType[]>` | Fetch enabled OAuth providers |
|
|
457
|
-
| `getBranding()` | `Promise<BrandingConfig \| null>` | Fetch project branding config |
|
|
458
|
-
| `getOAuthUrl(provider, redirectUri)` | `Promise<{ url, state }>` | Get OAuth authorization URL |
|
|
459
|
-
| `completeOAuth(state)` | `Promise<{ tokens, user }>` | Poll for OAuth completion |
|
|
460
|
-
| `getApiUrl()` | `string` | Return configured API base URL |
|
|
461
|
-
| `on(event, listener)` | `() => void` | Subscribe to auth events, returns unsubscribe function |
|
|
462
|
-
| `setStorage(storage)` | `void` | Set token storage adapter |
|
|
463
|
-
| `initialize()` | `Promise<TokenPair \| null>` | Load tokens from storage on app start |
|
|
464
|
-
| `destroy()` | `void` | Clear timers and event listeners |
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
## Documentation
|
|
469
|
-
|
|
470
|
-
[authon.dev/docs](https://authon.dev/docs)
|
|
247
|
+
- [Authon docs](https://authon.dev/docs)
|
|
248
|
+
- [Authon OAuth flow](https://authon.dev/docs)
|
|
471
249
|
|
|
472
250
|
## License
|
|
473
251
|
|
package/dist/index.cjs
CHANGED
|
@@ -187,8 +187,14 @@ var AuthonMobileClient = class {
|
|
|
187
187
|
await this.ensureInitialized();
|
|
188
188
|
return this._branding;
|
|
189
189
|
}
|
|
190
|
-
async getOAuthUrl(provider,
|
|
191
|
-
const
|
|
190
|
+
async getOAuthUrl(provider, options) {
|
|
191
|
+
const normalized = typeof options === "string" ? { redirectUri: options } : options ?? {};
|
|
192
|
+
const redirectUri = normalized.redirectUri || `${this.apiUrl}/v1/auth/oauth/redirect`;
|
|
193
|
+
const flow = normalized.flow || "redirect";
|
|
194
|
+
const params = new URLSearchParams({ redirectUri, flow });
|
|
195
|
+
if (normalized.returnTo) {
|
|
196
|
+
params.set("returnTo", normalized.returnTo);
|
|
197
|
+
}
|
|
192
198
|
return await this.request(
|
|
193
199
|
"GET",
|
|
194
200
|
`/v1/auth/oauth/${provider}/url?${params.toString()}`
|
|
@@ -500,9 +506,8 @@ function AuthonProvider({ children, storage, ...config }) {
|
|
|
500
506
|
return client.getAccessToken();
|
|
501
507
|
}, [client]);
|
|
502
508
|
const startOAuth = (0, import_react.useCallback)(
|
|
503
|
-
async (provider,
|
|
504
|
-
|
|
505
|
-
return client.getOAuthUrl(provider, uri);
|
|
509
|
+
async (provider, options) => {
|
|
510
|
+
return client.getOAuthUrl(provider, options);
|
|
506
511
|
},
|
|
507
512
|
[client]
|
|
508
513
|
);
|