@authon/react-native 0.3.3 → 0.3.4
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 +21 -205
- package/README.md +109 -164
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @authon/react-native
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- 앱 전역 인증 상태를 위한 `AuthonProvider`
|
|
8
|
-
- `useAuthon()`, `useUser()` 훅
|
|
9
|
-
- storage adapter 기반 보안 토큰 저장
|
|
10
|
-
- `SocialButton`, `SocialButtons`
|
|
11
|
-
- 저수준 OAuth 헬퍼(`startOAuth`, `completeOAuth`, `client`)
|
|
5
|
+
> React Native 모바일 인증 -- 보안 토큰 저장 -- 셀프 호스팅 Clerk 대안
|
|
12
6
|
|
|
13
7
|
## 설치
|
|
14
8
|
|
|
@@ -17,13 +11,10 @@ npm install @authon/react-native react-native-svg
|
|
|
17
11
|
npx expo install expo-secure-store expo-web-browser
|
|
18
12
|
```
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
`expo-web-browser`는 필수는 아니지만, Android에서 더 안정적인 OAuth UX가 필요하면 함께 설치하는 편이 좋습니다.
|
|
22
|
-
|
|
23
|
-
## 설정
|
|
14
|
+
## 빠른 시작
|
|
24
15
|
|
|
25
16
|
```tsx
|
|
26
|
-
import { AuthonProvider } from '@authon/react-native';
|
|
17
|
+
import { AuthonProvider, useAuthon } from '@authon/react-native';
|
|
27
18
|
import * as SecureStore from 'expo-secure-store';
|
|
28
19
|
|
|
29
20
|
const storage = {
|
|
@@ -35,218 +26,43 @@ const storage = {
|
|
|
35
26
|
export default function App() {
|
|
36
27
|
return (
|
|
37
28
|
<AuthonProvider publishableKey="pk_live_..." storage={storage}>
|
|
38
|
-
<
|
|
29
|
+
<HomeScreen />
|
|
39
30
|
</AuthonProvider>
|
|
40
31
|
);
|
|
41
32
|
}
|
|
42
33
|
```
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
## 훅
|
|
47
|
-
|
|
48
|
-
### `useAuthon()`
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
const {
|
|
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,
|
|
67
|
-
} = useAuthon();
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### `useUser()`
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
const { isLoaded, isSignedIn, user } = useUser();
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## 이메일 / 비밀번호 예제
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
import { useState } from 'react';
|
|
80
|
-
import { View, TextInput, Button, Text, ActivityIndicator } from 'react-native';
|
|
81
|
-
import { useAuthon, useUser } from '@authon/react-native';
|
|
82
|
-
|
|
83
|
-
export function LoginScreen() {
|
|
84
|
-
const { isLoaded } = useUser();
|
|
85
|
-
const { signIn, signOut, user, isSignedIn } = useAuthon();
|
|
86
|
-
const [email, setEmail] = useState('');
|
|
87
|
-
const [password, setPassword] = useState('');
|
|
88
|
-
const [loading, setLoading] = useState(false);
|
|
89
|
-
const [error, setError] = useState<string | null>(null);
|
|
90
|
-
|
|
91
|
-
const handleSignIn = async () => {
|
|
92
|
-
setLoading(true);
|
|
93
|
-
setError(null);
|
|
94
|
-
try {
|
|
95
|
-
await signIn({ strategy: 'email_password', email, password });
|
|
96
|
-
} catch (err: any) {
|
|
97
|
-
setError(err.message ?? '로그인에 실패했습니다.');
|
|
98
|
-
} finally {
|
|
99
|
-
setLoading(false);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
if (!isLoaded) return <ActivityIndicator />;
|
|
104
|
-
|
|
105
|
-
if (isSignedIn) {
|
|
106
|
-
return (
|
|
107
|
-
<View style={{ padding: 24, gap: 12 }}>
|
|
108
|
-
<Text>Welcome, {user?.displayName ?? user?.email}</Text>
|
|
109
|
-
<Button title="로그아웃" onPress={signOut} />
|
|
110
|
-
</View>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<View style={{ padding: 24, gap: 12 }}>
|
|
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 ? '로그인 중...' : '로그인'} onPress={handleSignIn} disabled={loading} />
|
|
120
|
-
</View>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
```
|
|
35
|
+
## 주요 작업
|
|
124
36
|
|
|
125
|
-
|
|
37
|
+
### 이메일 로그인
|
|
126
38
|
|
|
127
39
|
```tsx
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
export function SocialLoginSection() {
|
|
131
|
-
return (
|
|
132
|
-
<SocialButtons
|
|
133
|
-
onSuccess={() => console.log('Signed in')}
|
|
134
|
-
onError={(error) => console.error(error)}
|
|
135
|
-
/>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
40
|
+
const { signIn } = useAuthon();
|
|
41
|
+
await signIn({ strategy: 'email_password', email, password });
|
|
138
42
|
```
|
|
139
43
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
## 수동 OAuth 플로우
|
|
143
|
-
|
|
144
|
-
가장 단순한 SDK 플로우는 아래와 같습니다.
|
|
44
|
+
### OAuth 로그인
|
|
145
45
|
|
|
146
46
|
```tsx
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const { startOAuth, completeOAuth } = useAuthon();
|
|
152
|
-
|
|
153
|
-
const handlePress = async () => {
|
|
154
|
-
const { url, state } = await startOAuth('google');
|
|
155
|
-
await Linking.openURL(url);
|
|
156
|
-
await completeOAuth(state);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// ...
|
|
160
|
-
}
|
|
47
|
+
const { startOAuth, completeOAuth } = useAuthon();
|
|
48
|
+
const { url, state } = await startOAuth('google');
|
|
49
|
+
await Linking.openURL(url);
|
|
50
|
+
await completeOAuth(state);
|
|
161
51
|
```
|
|
162
52
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
## 권장 Expo OAuth 플로우
|
|
166
|
-
|
|
167
|
-
Android에서 더 자연스러운 브라우저 종료 UX가 필요하면 `flow=redirect`와 `returnTo`를 포함해 OAuth URL을 직접 요청하고 `expo-web-browser`로 여는 방식을 권장합니다.
|
|
53
|
+
### 로그아웃
|
|
168
54
|
|
|
169
55
|
```tsx
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
import { useAuthon } from '@authon/react-native';
|
|
173
|
-
|
|
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
|
-
);
|
|
190
|
-
|
|
191
|
-
if (!response.ok) {
|
|
192
|
-
throw new Error(await response.text());
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return response.json() as Promise<{ url: string; state: string }>;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function GoogleButton() {
|
|
199
|
-
const { completeOAuth, getToken } = useAuthon();
|
|
200
|
-
|
|
201
|
-
const handlePress = async () => {
|
|
202
|
-
const { url, state } = await requestOAuthUrl('google');
|
|
203
|
-
const pollPromise = completeOAuth(state);
|
|
204
|
-
|
|
205
|
-
await WebBrowser.openAuthSessionAsync(url, APP_DEEP_LINK);
|
|
206
|
-
await pollPromise;
|
|
207
|
-
|
|
208
|
-
const authonAccessToken = getToken();
|
|
209
|
-
// 앱이 자체 백엔드 세션도 가진다면 여기서 authonAccessToken을 교환하세요.
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
return <Button title="Google로 계속하기" onPress={handlePress} />;
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
HTTPS 브리지 페이지 예제:
|
|
217
|
-
|
|
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');
|
|
226
|
-
|
|
227
|
-
const target = new URL('myapp://oauth-callback');
|
|
228
|
-
if (state) target.searchParams.set('state', state);
|
|
229
|
-
if (error) target.searchParams.set('error', error);
|
|
230
|
-
|
|
231
|
-
window.location.replace(target.toString());
|
|
232
|
-
</script>
|
|
233
|
-
</body>
|
|
234
|
-
</html>
|
|
56
|
+
const { signOut } = useAuthon();
|
|
57
|
+
await signOut();
|
|
235
58
|
```
|
|
236
59
|
|
|
237
|
-
##
|
|
238
|
-
|
|
239
|
-
- `myapp://...`를 OAuth 제공자 redirect URI로 직접 등록하지 마세요. 제공자 redirect URI는 항상 `{apiUrl}/v1/auth/oauth/redirect`여야 합니다.
|
|
240
|
-
- 앱 복귀용 브리지는 `returnTo`에 넣습니다. `returnTo`는 HTTPS URL이어야 하며, 해당 origin은 `ALLOWED_REDIRECT_ORIGINS`에 포함돼야 합니다.
|
|
241
|
-
- 커스텀 앱 스킴을 쓸 경우 HTTPS 브리지 페이지에서 `myapp://...`로 한 번 더 넘기세요.
|
|
242
|
-
- Android에서는 이미 열린 Custom Tab을 `dismissAuthSession()`으로 확실하게 닫을 수 없습니다. `openAuthSessionAsync()`와 브리지 체인을 기준으로 설계하세요.
|
|
243
|
-
- 앱이 자체 백엔드 세션도 운영한다면 `completeOAuth()` 직후 `getToken()`을 백엔드에 넘겨 자체 세션을 발급받으세요.
|
|
244
|
-
|
|
245
|
-
## 문서
|
|
60
|
+
## 환경 변수
|
|
246
61
|
|
|
247
|
-
|
|
248
|
-
|
|
62
|
+
| 변수 | 필수 | 설명 |
|
|
63
|
+
|------|------|------|
|
|
64
|
+
| `AUTHON_PUBLISHABLE_KEY` | Yes | 퍼블리셔블 키 |
|
|
249
65
|
|
|
250
66
|
## 라이선스
|
|
251
67
|
|
|
252
|
-
|
|
68
|
+
MIT
|
package/README.md
CHANGED
|
@@ -2,13 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
# @authon/react-native
|
|
4
4
|
|
|
5
|
-
React Native
|
|
5
|
+
> Drop-in React Native authentication with secure token storage — Auth0 alternative
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/@authon/react-native)
|
|
8
|
+
[](../../LICENSE)
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
Before installing the SDK, create an Authon project and get your API keys:
|
|
13
|
+
|
|
14
|
+
1. **Create a project** at [Authon Dashboard](https://authon.dev/dashboard/overview)
|
|
15
|
+
- Click "Create Project" and enter your app name
|
|
16
|
+
- Select the authentication methods you want (Email/Password, OAuth providers, etc.)
|
|
17
|
+
|
|
18
|
+
2. **Get your API keys** from Project Settings → API Keys
|
|
19
|
+
- **Publishable Key** (`pk_live_...`) — use in your frontend code
|
|
20
|
+
- **Test Key** (`pk_test_...`) — for development, enables Dev Teleport
|
|
21
|
+
|
|
22
|
+
3. **Configure OAuth providers** (optional) in Project Settings → OAuth
|
|
23
|
+
- Add Google, Apple, GitHub, etc. with their respective Client ID and Secret
|
|
24
|
+
- Set the redirect URL to `https://api.authon.dev/v1/auth/oauth/redirect`
|
|
25
|
+
|
|
26
|
+
> **Test vs Live keys:** Use `pk_test_...` during development. Switch to `pk_live_...` before deploying to production. Test keys use a sandbox environment with no rate limits.
|
|
12
27
|
|
|
13
28
|
## Install
|
|
14
29
|
|
|
@@ -17,13 +32,13 @@ npm install @authon/react-native react-native-svg
|
|
|
17
32
|
npx expo install expo-secure-store expo-web-browser
|
|
18
33
|
```
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
`expo-web-browser` is optional but recommended when you want a more controlled OAuth flow in Expo.
|
|
22
|
-
|
|
23
|
-
## Setup
|
|
35
|
+
## Quick Start
|
|
24
36
|
|
|
25
37
|
```tsx
|
|
26
|
-
|
|
38
|
+
// App.tsx — complete working file
|
|
39
|
+
import React, { useState } from 'react';
|
|
40
|
+
import { View, Text, Button, TextInput, ActivityIndicator } from 'react-native';
|
|
41
|
+
import { AuthonProvider, useAuthon, useUser } from '@authon/react-native';
|
|
27
42
|
import * as SecureStore from 'expo-secure-store';
|
|
28
43
|
|
|
29
44
|
const storage = {
|
|
@@ -32,81 +47,18 @@ const storage = {
|
|
|
32
47
|
removeItem: (key: string) => SecureStore.deleteItemAsync(key),
|
|
33
48
|
};
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<AuthonProvider publishableKey="pk_live_..." storage={storage}>
|
|
38
|
-
<Navigation />
|
|
39
|
-
</AuthonProvider>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Without a storage adapter, tokens remain in memory only.
|
|
45
|
-
|
|
46
|
-
## Hooks
|
|
47
|
-
|
|
48
|
-
### `useAuthon()`
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
const {
|
|
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,
|
|
67
|
-
} = useAuthon();
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### `useUser()`
|
|
71
|
-
|
|
72
|
-
```ts
|
|
73
|
-
const { isLoaded, isSignedIn, user } = useUser();
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Email / Password Example
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
import { useState } from 'react';
|
|
80
|
-
import { View, TextInput, Button, Text, ActivityIndicator } from 'react-native';
|
|
81
|
-
import { useAuthon, useUser } from '@authon/react-native';
|
|
82
|
-
|
|
83
|
-
export function LoginScreen() {
|
|
84
|
-
const { isLoaded } = useUser();
|
|
85
|
-
const { signIn, signOut, user, isSignedIn } = useAuthon();
|
|
50
|
+
function HomeScreen() {
|
|
51
|
+
const { isLoaded, isSignedIn, user, signIn, signOut } = useAuthon();
|
|
86
52
|
const [email, setEmail] = useState('');
|
|
87
53
|
const [password, setPassword] = useState('');
|
|
88
|
-
const [loading, setLoading] = useState(false);
|
|
89
|
-
const [error, setError] = useState<string | null>(null);
|
|
90
|
-
|
|
91
|
-
const handleSignIn = async () => {
|
|
92
|
-
setLoading(true);
|
|
93
|
-
setError(null);
|
|
94
|
-
try {
|
|
95
|
-
await signIn({ strategy: 'email_password', email, password });
|
|
96
|
-
} catch (err: any) {
|
|
97
|
-
setError(err.message ?? 'Sign-in failed');
|
|
98
|
-
} finally {
|
|
99
|
-
setLoading(false);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
54
|
|
|
103
55
|
if (!isLoaded) return <ActivityIndicator />;
|
|
104
56
|
|
|
105
57
|
if (isSignedIn) {
|
|
106
58
|
return (
|
|
107
|
-
<View style={{ padding: 24
|
|
59
|
+
<View style={{ padding: 24 }}>
|
|
108
60
|
<Text>Welcome, {user?.displayName ?? user?.email}</Text>
|
|
109
|
-
<Button title="Sign
|
|
61
|
+
<Button title="Sign Out" onPress={signOut} />
|
|
110
62
|
</View>
|
|
111
63
|
);
|
|
112
64
|
}
|
|
@@ -115,39 +67,32 @@ export function LoginScreen() {
|
|
|
115
67
|
<View style={{ padding: 24, gap: 12 }}>
|
|
116
68
|
<TextInput placeholder="Email" value={email} onChangeText={setEmail} autoCapitalize="none" />
|
|
117
69
|
<TextInput placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry />
|
|
118
|
-
|
|
119
|
-
<Button title={loading ? 'Signing in...' : 'Sign in'} onPress={handleSignIn} disabled={loading} />
|
|
70
|
+
<Button title="Sign In" onPress={() => signIn({ strategy: 'email_password', email, password })} />
|
|
120
71
|
</View>
|
|
121
72
|
);
|
|
122
73
|
}
|
|
123
|
-
```
|
|
124
74
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
```tsx
|
|
128
|
-
import { SocialButtons } from '@authon/react-native';
|
|
129
|
-
|
|
130
|
-
export function SocialLoginSection() {
|
|
75
|
+
export default function App() {
|
|
131
76
|
return (
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
77
|
+
<AuthonProvider
|
|
78
|
+
publishableKey="pk_live_YOUR_PUBLISHABLE_KEY"
|
|
79
|
+
storage={storage}
|
|
80
|
+
>
|
|
81
|
+
<HomeScreen />
|
|
82
|
+
</AuthonProvider>
|
|
136
83
|
);
|
|
137
84
|
}
|
|
138
85
|
```
|
|
139
86
|
|
|
140
|
-
|
|
87
|
+
## Common Tasks
|
|
141
88
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
The basic SDK flow looks like this:
|
|
89
|
+
### Add Google OAuth Login
|
|
145
90
|
|
|
146
91
|
```tsx
|
|
147
92
|
import { Linking } from 'react-native';
|
|
148
93
|
import { useAuthon } from '@authon/react-native';
|
|
149
94
|
|
|
150
|
-
|
|
95
|
+
function GoogleButton() {
|
|
151
96
|
const { startOAuth, completeOAuth } = useAuthon();
|
|
152
97
|
|
|
153
98
|
const handlePress = async () => {
|
|
@@ -156,97 +101,97 @@ export function GoogleButton() {
|
|
|
156
101
|
await completeOAuth(state);
|
|
157
102
|
};
|
|
158
103
|
|
|
159
|
-
|
|
104
|
+
return <Button title="Sign in with Google" onPress={handlePress} />;
|
|
160
105
|
}
|
|
161
106
|
```
|
|
162
107
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
## Recommended Expo OAuth Flow
|
|
166
|
-
|
|
167
|
-
If you want a cleaner Android experience with `expo-web-browser`, request the OAuth URL manually with `flow=redirect` and `returnTo`.
|
|
108
|
+
### Add Google OAuth (Expo recommended)
|
|
168
109
|
|
|
169
110
|
```tsx
|
|
170
111
|
import * as WebBrowser from 'expo-web-browser';
|
|
171
|
-
import { Button } from 'react-native';
|
|
172
112
|
import { useAuthon } from '@authon/react-native';
|
|
173
113
|
|
|
174
|
-
|
|
175
|
-
const
|
|
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
|
-
);
|
|
114
|
+
function GoogleButton() {
|
|
115
|
+
const { startOAuth, completeOAuth } = useAuthon();
|
|
190
116
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
117
|
+
const handlePress = async () => {
|
|
118
|
+
const { url, state } = await startOAuth('google', {
|
|
119
|
+
flow: 'redirect',
|
|
120
|
+
returnTo: 'https://your-domain.com/mobile-callback',
|
|
121
|
+
});
|
|
122
|
+
const pollPromise = completeOAuth(state);
|
|
123
|
+
await WebBrowser.openAuthSessionAsync(url, 'myapp://oauth-callback');
|
|
124
|
+
await pollPromise;
|
|
125
|
+
};
|
|
194
126
|
|
|
195
|
-
return
|
|
127
|
+
return <Button title="Sign in with Google" onPress={handlePress} />;
|
|
196
128
|
}
|
|
129
|
+
```
|
|
197
130
|
|
|
198
|
-
|
|
199
|
-
const { completeOAuth, getToken } = useAuthon();
|
|
131
|
+
### Get Current User
|
|
200
132
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const pollPromise = completeOAuth(state);
|
|
133
|
+
```tsx
|
|
134
|
+
import { useUser } from '@authon/react-native';
|
|
204
135
|
|
|
205
|
-
|
|
206
|
-
|
|
136
|
+
function Profile() {
|
|
137
|
+
const { isLoaded, isSignedIn, user } = useUser();
|
|
138
|
+
if (!isLoaded) return <ActivityIndicator />;
|
|
139
|
+
if (!isSignedIn) return <Text>Not signed in</Text>;
|
|
140
|
+
return <Text>Email: {user?.email}</Text>;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
207
143
|
|
|
208
|
-
|
|
209
|
-
// If your app also has its own backend session, exchange authonAccessToken here.
|
|
210
|
-
};
|
|
144
|
+
### Add Email/Password Auth
|
|
211
145
|
|
|
212
|
-
|
|
213
|
-
}
|
|
146
|
+
```tsx
|
|
147
|
+
const { signIn } = useAuthon();
|
|
148
|
+
await signIn({ strategy: 'email_password', email: 'user@example.com', password: 'MyP@ssw0rd' });
|
|
214
149
|
```
|
|
215
150
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
|
|
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');
|
|
226
|
-
|
|
227
|
-
const target = new URL('myapp://oauth-callback');
|
|
228
|
-
if (state) target.searchParams.set('state', state);
|
|
229
|
-
if (error) target.searchParams.set('error', error);
|
|
230
|
-
|
|
231
|
-
window.location.replace(target.toString());
|
|
232
|
-
</script>
|
|
233
|
-
</body>
|
|
234
|
-
</html>
|
|
151
|
+
### Handle Sign Out
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
const { signOut } = useAuthon();
|
|
155
|
+
await signOut();
|
|
235
156
|
```
|
|
236
157
|
|
|
237
|
-
##
|
|
158
|
+
## Environment Variables
|
|
159
|
+
|
|
160
|
+
| Variable | Required | Description |
|
|
161
|
+
|----------|----------|-------------|
|
|
162
|
+
| `AUTHON_PUBLISHABLE_KEY` | Yes | Project publishable key (`pk_live_...` or `pk_test_...`) |
|
|
163
|
+
| `AUTHON_API_URL` | No | Optional — defaults to `api.authon.dev` |
|
|
164
|
+
|
|
165
|
+
## API Reference
|
|
166
|
+
|
|
167
|
+
### Components
|
|
168
|
+
|
|
169
|
+
| Component | Description |
|
|
170
|
+
|-----------|-------------|
|
|
171
|
+
| `<AuthonProvider>` | Provider with `publishableKey`, `apiUrl`, `storage` |
|
|
172
|
+
| `<SocialButtons>` | Renders all enabled OAuth provider buttons |
|
|
173
|
+
| `<SocialButton>` | Single OAuth provider button with icon |
|
|
174
|
+
|
|
175
|
+
### Hooks
|
|
238
176
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
177
|
+
| Hook | Returns |
|
|
178
|
+
|------|---------|
|
|
179
|
+
| `useAuthon()` | `{ isLoaded, isSignedIn, user, signIn, signUp, signOut, getToken, providers, branding, startOAuth, completeOAuth, on, client }` |
|
|
180
|
+
| `useUser()` | `{ isLoaded, isSignedIn, user }` |
|
|
181
|
+
| `useAuthonWeb3()` | Web3 wallet auth |
|
|
182
|
+
| `useAuthonPasswordless()` | OTP and magic link |
|
|
183
|
+
| `useAuthonPasskeys()` | Passkey registration and auth |
|
|
244
184
|
|
|
245
|
-
##
|
|
185
|
+
## Comparison
|
|
246
186
|
|
|
247
|
-
|
|
248
|
-
|
|
187
|
+
| Feature | Authon | Clerk | Auth0 |
|
|
188
|
+
|---------|--------|-------|-------|
|
|
189
|
+
| Pricing | Free | $25/mo+ | $23/mo+ |
|
|
190
|
+
| React Native | Yes | Yes | Yes |
|
|
191
|
+
| Secure token storage | Yes | Yes | Yes |
|
|
192
|
+
| Web3 auth | Yes | No | No |
|
|
193
|
+
| MFA/Passkeys | Yes | Yes | Yes |
|
|
249
194
|
|
|
250
195
|
## License
|
|
251
196
|
|
|
252
|
-
|
|
197
|
+
MIT
|