@authon/react-native 0.2.2 → 0.3.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/README.ko.md +154 -0
- package/README.md +412 -50
- package/dist/index.cjs +262 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -2
- package/dist/index.d.ts +85 -2
- package/dist/index.js +257 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.ko.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
[English](./README.md) | **한국어**
|
|
2
|
+
|
|
3
|
+
# @authon/react-native
|
|
4
|
+
|
|
5
|
+
[Authon](https://authon.dev)용 React Native SDK — 네이티브 OAuth, 보안 토큰 저장소, React 훅을 제공합니다.
|
|
6
|
+
|
|
7
|
+
## 설치
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @authon/react-native
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @authon/react-native
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`react-native >= 0.72`, `expo-auth-session`, `expo-secure-store`(또는 bare RN 동등 패키지)가 필요합니다.
|
|
16
|
+
|
|
17
|
+
## 빠른 시작
|
|
18
|
+
|
|
19
|
+
### 1. Provider
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
// App.tsx
|
|
23
|
+
import { AuthonProvider } from '@authon/react-native';
|
|
24
|
+
|
|
25
|
+
export default function App() {
|
|
26
|
+
return (
|
|
27
|
+
<AuthonProvider publishableKey="pk_live_...">
|
|
28
|
+
<Navigation />
|
|
29
|
+
</AuthonProvider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. 훅 사용
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { useAuthon, useUser } from '@authon/react-native';
|
|
38
|
+
import { View, Text, Button } from 'react-native';
|
|
39
|
+
|
|
40
|
+
function ProfileScreen() {
|
|
41
|
+
const { isSignedIn, signOut } = useAuthon();
|
|
42
|
+
const { user } = useUser();
|
|
43
|
+
|
|
44
|
+
if (!isSignedIn) {
|
|
45
|
+
return <SignInScreen />;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<View>
|
|
50
|
+
<Text>Welcome, {user?.displayName}</Text>
|
|
51
|
+
<Button title="Sign Out" onPress={signOut} />
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function SignInScreen() {
|
|
57
|
+
const { signInWithOAuth, signInWithEmail } = useAuthon();
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<View>
|
|
61
|
+
<Button title="Sign in with Google" onPress={() => signInWithOAuth('google')} />
|
|
62
|
+
<Button title="Sign in with Apple" onPress={() => signInWithOAuth('apple')} />
|
|
63
|
+
</View>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API 레퍼런스
|
|
69
|
+
|
|
70
|
+
### `<AuthonProvider>`
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<AuthonProvider
|
|
74
|
+
publishableKey="pk_live_..."
|
|
75
|
+
config={{
|
|
76
|
+
apiUrl: 'https://api.authon.dev',
|
|
77
|
+
scheme: 'myapp', // OAuth 리다이렉트용 커스텀 URL 스킴
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 훅
|
|
83
|
+
|
|
84
|
+
#### `useAuthon()`
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const {
|
|
88
|
+
isSignedIn, // boolean
|
|
89
|
+
isLoading, // boolean
|
|
90
|
+
user, // AuthonUser | null
|
|
91
|
+
signInWithOAuth, // (provider: string) => Promise<void>
|
|
92
|
+
signInWithEmail, // (email: string, password: string) => Promise<AuthonUser>
|
|
93
|
+
signOut, // () => Promise<void>
|
|
94
|
+
getToken, // () => Promise<string | null>
|
|
95
|
+
} = useAuthon();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `useUser()`
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const { user, isLoading } = useUser();
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 토큰 저장소
|
|
105
|
+
|
|
106
|
+
토큰은 `expo-secure-store`(Expo) 또는 플랫폼 키체인(bare RN)을 사용하여 저장되며, 자격증명은 저장 시 암호화됩니다.
|
|
107
|
+
|
|
108
|
+
## 다단계 인증 (MFA)
|
|
109
|
+
|
|
110
|
+
`useAuthon()`에서 반환되는 `client`를 통해 MFA에 접근합니다.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import { useAuthon } from '@authon/react-native';
|
|
114
|
+
import { AuthonMfaRequiredError } from '@authon/js';
|
|
115
|
+
|
|
116
|
+
function MfaSetupScreen() {
|
|
117
|
+
const { client } = useAuthon();
|
|
118
|
+
const [qrSvg, setQrSvg] = useState('');
|
|
119
|
+
|
|
120
|
+
const enableMfa = async () => {
|
|
121
|
+
const setup = await client!.setupMfa();
|
|
122
|
+
setQrSvg(setup.qrCodeSvg); // QR을 SVG로 렌더링
|
|
123
|
+
// setup.backupCodes — 사용자에게 안전하게 보관하도록 표시
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const verifySetup = async (code: string) => {
|
|
127
|
+
await client!.verifyMfaSetup(code);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// MFA 로그인 플로우
|
|
131
|
+
const signIn = async (email: string, password: string) => {
|
|
132
|
+
try {
|
|
133
|
+
await client!.signInWithEmail(email, password);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
if (err instanceof AuthonMfaRequiredError) {
|
|
136
|
+
// TOTP 입력 화면으로 이동
|
|
137
|
+
// 이후: await client!.verifyMfa(err.mfaToken, code);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// ...
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
전체 API 레퍼런스는 [`@authon/js` MFA 문서](../js/README.md#multi-factor-authentication-mfa)를 참고하세요.
|
|
147
|
+
|
|
148
|
+
## 문서
|
|
149
|
+
|
|
150
|
+
[authon.dev/docs](https://authon.dev/docs)
|
|
151
|
+
|
|
152
|
+
## 라이선스
|
|
153
|
+
|
|
154
|
+
[MIT](../../LICENSE)
|
package/README.md
CHANGED
|
@@ -1,107 +1,469 @@
|
|
|
1
|
+
**English** | [한국어](./README.ko.md)
|
|
2
|
+
|
|
1
3
|
# @authon/react-native
|
|
2
4
|
|
|
3
|
-
React Native SDK for [Authon](https://authon.dev)
|
|
5
|
+
React Native SDK for [Authon](https://authon.dev) authentication. Provides React hooks, social login with in-app browser, and secure token storage.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm install @authon/react-native
|
|
9
|
-
# or
|
|
10
|
-
pnpm add @authon/react-native
|
|
10
|
+
npm install @authon/react-native @authon/js
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Peer dependencies:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react-native react-native-svg
|
|
17
|
+
```
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
| Peer dependency | Version |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `react` | `^18.0.0 \|\| ^19.0.0` |
|
|
22
|
+
| `react-native` | `>=0.70.0` |
|
|
23
|
+
| `react-native-svg` | `>=12.0.0` |
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
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).
|
|
18
30
|
|
|
19
31
|
```tsx
|
|
20
32
|
// App.tsx
|
|
33
|
+
import React from 'react';
|
|
21
34
|
import { AuthonProvider } from '@authon/react-native';
|
|
35
|
+
import * as SecureStore from 'expo-secure-store';
|
|
36
|
+
|
|
37
|
+
const storage = {
|
|
38
|
+
getItem: (key: string) => SecureStore.getItemAsync(key),
|
|
39
|
+
setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),
|
|
40
|
+
removeItem: (key: string) => SecureStore.deleteItemAsync(key),
|
|
41
|
+
};
|
|
22
42
|
|
|
23
43
|
export default function App() {
|
|
24
44
|
return (
|
|
25
|
-
<AuthonProvider publishableKey="pk_live_...">
|
|
45
|
+
<AuthonProvider publishableKey="pk_live_..." storage={storage}>
|
|
26
46
|
<Navigation />
|
|
27
47
|
</AuthonProvider>
|
|
28
48
|
);
|
|
29
49
|
}
|
|
30
50
|
```
|
|
31
51
|
|
|
32
|
-
|
|
52
|
+
Without a storage adapter, tokens persist only in memory and are lost on app restart.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Hooks
|
|
57
|
+
|
|
58
|
+
### `useAuthon()`
|
|
59
|
+
|
|
60
|
+
Returns the full auth context.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { useAuthon } from '@authon/react-native';
|
|
64
|
+
|
|
65
|
+
const {
|
|
66
|
+
isLoaded, // boolean — true once the initial auth check is complete
|
|
67
|
+
isSignedIn, // boolean
|
|
68
|
+
userId, // string | null
|
|
69
|
+
sessionId, // string | null
|
|
70
|
+
accessToken, // string | null
|
|
71
|
+
user, // AuthonUser | null
|
|
72
|
+
signIn, // (params: SignInParams) => Promise<void>
|
|
73
|
+
signUp, // (params: SignUpParams) => Promise<void>
|
|
74
|
+
signOut, // () => Promise<void>
|
|
75
|
+
getToken, // () => string | null — returns current access token
|
|
76
|
+
providers, // OAuthProviderType[] — enabled providers from your project config
|
|
77
|
+
branding, // BrandingConfig | null
|
|
78
|
+
startOAuth, // (provider, redirectUri?) => Promise<{ url: string; state: string }>
|
|
79
|
+
completeOAuth, // (state: string) => Promise<void>
|
|
80
|
+
on, // (event, listener) => () => void — subscribe to auth events
|
|
81
|
+
client, // AuthonMobileClient — underlying client instance
|
|
82
|
+
} = useAuthon();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `useUser()`
|
|
86
|
+
|
|
87
|
+
Convenience hook that returns only user state.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { useUser } from '@authon/react-native';
|
|
91
|
+
|
|
92
|
+
const { isLoaded, isSignedIn, user } = useUser();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Examples
|
|
98
|
+
|
|
99
|
+
### Login screen with email and password
|
|
33
100
|
|
|
34
101
|
```tsx
|
|
35
|
-
import {
|
|
36
|
-
import { View,
|
|
102
|
+
import React, { useState } from 'react';
|
|
103
|
+
import { View, TextInput, Button, Text, ActivityIndicator } from 'react-native';
|
|
104
|
+
import { useAuthon } from '@authon/react-native';
|
|
105
|
+
|
|
106
|
+
export function LoginScreen() {
|
|
107
|
+
const { signIn, isLoaded } = useAuthon();
|
|
108
|
+
const [email, setEmail] = useState('');
|
|
109
|
+
const [password, setPassword] = useState('');
|
|
110
|
+
const [loading, setLoading] = useState(false);
|
|
111
|
+
const [error, setError] = useState<string | null>(null);
|
|
37
112
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
113
|
+
const handleSignIn = async () => {
|
|
114
|
+
setLoading(true);
|
|
115
|
+
setError(null);
|
|
116
|
+
try {
|
|
117
|
+
await signIn({ strategy: 'email_password', email, password });
|
|
118
|
+
} catch (err: any) {
|
|
119
|
+
setError(err.message);
|
|
120
|
+
} finally {
|
|
121
|
+
setLoading(false);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
41
124
|
|
|
42
|
-
if (!
|
|
43
|
-
return <SignInScreen />;
|
|
44
|
-
}
|
|
125
|
+
if (!isLoaded) return <ActivityIndicator />;
|
|
45
126
|
|
|
46
127
|
return (
|
|
47
|
-
<View>
|
|
48
|
-
<
|
|
49
|
-
|
|
128
|
+
<View style={{ padding: 24, gap: 12 }}>
|
|
129
|
+
<TextInput
|
|
130
|
+
placeholder="Email"
|
|
131
|
+
value={email}
|
|
132
|
+
onChangeText={setEmail}
|
|
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} />
|
|
144
|
+
</View>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Social login buttons
|
|
150
|
+
|
|
151
|
+
Use the built-in `SocialButtons` component. It fetches the enabled providers from your Authon project and renders buttons automatically.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import React from 'react';
|
|
155
|
+
import { View } from 'react-native';
|
|
156
|
+
import { SocialButtons } from '@authon/react-native';
|
|
157
|
+
|
|
158
|
+
export function SocialLoginSection() {
|
|
159
|
+
return (
|
|
160
|
+
<View style={{ padding: 24 }}>
|
|
161
|
+
<SocialButtons
|
|
162
|
+
onSuccess={() => console.log('Signed in')}
|
|
163
|
+
onError={(err) => console.error(err)}
|
|
164
|
+
/>
|
|
50
165
|
</View>
|
|
51
166
|
);
|
|
52
167
|
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Compact icon-only layout:
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
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
|
+
```
|
|
195
|
+
|
|
196
|
+
### Manual OAuth flow with in-app browser
|
|
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.
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import React, { useState } from 'react';
|
|
202
|
+
import { Button, Linking } from 'react-native';
|
|
203
|
+
import { useAuthon } from '@authon/react-native';
|
|
204
|
+
|
|
205
|
+
export function GoogleSignInButton() {
|
|
206
|
+
const { startOAuth, completeOAuth } = useAuthon();
|
|
207
|
+
const [loading, setLoading] = useState(false);
|
|
208
|
+
|
|
209
|
+
const handleGoogleSignIn = async () => {
|
|
210
|
+
setLoading(true);
|
|
211
|
+
try {
|
|
212
|
+
const { url, state } = await startOAuth('google');
|
|
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
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<Button
|
|
228
|
+
title={loading ? 'Signing in...' : 'Sign in with Google'}
|
|
229
|
+
onPress={handleGoogleSignIn}
|
|
230
|
+
disabled={loading}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Session management
|
|
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';
|
|
53
242
|
|
|
54
|
-
function
|
|
55
|
-
const {
|
|
243
|
+
export function SessionsScreen() {
|
|
244
|
+
const { client, userId, signOut } = useAuthon();
|
|
245
|
+
const [sessions, setSessions] = useState<any[]>([]);
|
|
246
|
+
|
|
247
|
+
useEffect(() => {
|
|
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]);
|
|
56
255
|
|
|
57
256
|
return (
|
|
58
257
|
<View>
|
|
59
|
-
<Button title="Sign
|
|
60
|
-
<Button title="Sign in with Apple" onPress={() => signInWithOAuth('apple')} />
|
|
258
|
+
<Button title="Sign Out (all devices)" onPress={signOut} />
|
|
61
259
|
</View>
|
|
62
260
|
);
|
|
63
261
|
}
|
|
64
262
|
```
|
|
65
263
|
|
|
66
|
-
|
|
264
|
+
### MFA setup
|
|
67
265
|
|
|
68
|
-
|
|
266
|
+
MFA (TOTP) is configured on the client side through the underlying `@authon/js` client. Access it via `client` from `useAuthon()`.
|
|
69
267
|
|
|
70
268
|
```tsx
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
>
|
|
269
|
+
import React, { useState } from 'react';
|
|
270
|
+
import { View, TextInput, Button, Text, Image } from 'react-native';
|
|
271
|
+
import { useAuthon } from '@authon/react-native';
|
|
272
|
+
|
|
273
|
+
export function MfaSetupScreen() {
|
|
274
|
+
const { client } = useAuthon();
|
|
275
|
+
const [qrCodeUri, setQrCodeUri] = useState<string | null>(null);
|
|
276
|
+
const [backupCodes, setBackupCodes] = useState<string[]>([]);
|
|
277
|
+
const [code, setCode] = useState('');
|
|
278
|
+
|
|
279
|
+
const setupMfa = async () => {
|
|
280
|
+
// Use the underlying client's MFA methods (via @authon/js)
|
|
281
|
+
const jsClient = (client as any)._jsClient;
|
|
282
|
+
if (!jsClient) return;
|
|
283
|
+
|
|
284
|
+
const setup = await jsClient.setupMfa();
|
|
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
|
+
);
|
|
313
|
+
}
|
|
78
314
|
```
|
|
79
315
|
|
|
80
|
-
###
|
|
316
|
+
### Web3 wallet connection
|
|
81
317
|
|
|
82
|
-
|
|
318
|
+
```tsx
|
|
319
|
+
import React, { useState } from 'react';
|
|
320
|
+
import { Button, View, Text } from 'react-native';
|
|
321
|
+
import { useAuthon } from '@authon/react-native';
|
|
83
322
|
|
|
84
|
-
|
|
85
|
-
const {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
323
|
+
export function Web3ConnectScreen() {
|
|
324
|
+
const { client } = useAuthon();
|
|
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
|
+
};
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<View style={{ padding: 24 }}>
|
|
353
|
+
<Button title="Connect Wallet" onPress={connectWallet} />
|
|
354
|
+
</View>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
94
357
|
```
|
|
95
358
|
|
|
96
|
-
|
|
359
|
+
### Auth event subscription
|
|
97
360
|
|
|
98
|
-
```
|
|
99
|
-
|
|
361
|
+
```tsx
|
|
362
|
+
import { useEffect } from 'react';
|
|
363
|
+
import { useAuthon } from '@authon/react-native';
|
|
364
|
+
|
|
365
|
+
function App() {
|
|
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;
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## OAuth Note
|
|
397
|
+
|
|
398
|
+
Unlike web SDKs, `@authon/react-native` does not use popups. OAuth flows open the system browser via `Linking.openURL`. After the user authenticates, the SDK polls the Authon API to complete the session (up to 3 minutes). The `SocialButtons` component handles this polling automatically.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## API Reference
|
|
403
|
+
|
|
404
|
+
### `<AuthonProvider>`
|
|
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
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### `SignInParams`
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
interface SignInParams {
|
|
427
|
+
strategy: 'email_password' | 'oauth';
|
|
428
|
+
email?: string;
|
|
429
|
+
password?: string;
|
|
430
|
+
provider?: string;
|
|
431
|
+
}
|
|
100
432
|
```
|
|
101
433
|
|
|
102
|
-
###
|
|
434
|
+
### `SignUpParams`
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
interface SignUpParams {
|
|
438
|
+
email: string;
|
|
439
|
+
password: string;
|
|
440
|
+
displayName?: string;
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### `AuthonMobileClient` methods
|
|
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 |
|
|
103
465
|
|
|
104
|
-
|
|
466
|
+
---
|
|
105
467
|
|
|
106
468
|
## Documentation
|
|
107
469
|
|