@authon/react-native 0.3.2 → 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 CHANGED
@@ -2,13 +2,7 @@
2
2
 
3
3
  # @authon/react-native
4
4
 
5
- [Authon](https://authon.dev)용 React Native SDK입니다. 아래 기능을 제공합니다.
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
- Expo 앱에서는 `expo-secure-store`를 권장합니다.
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
- <Navigation />
29
+ <HomeScreen />
39
30
  </AuthonProvider>
40
31
  );
41
32
  }
42
33
  ```
43
34
 
44
- storage adapter를 넣지 않으면 토큰은 메모리에만 남고 앱 재시작 시 사라집니다.
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
- import { SocialButtons } from '@authon/react-native';
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
- 완전히 커스텀한 버튼이 필요하면 `SocialButton`을 직접 쓰거나 `startOAuth()` / `completeOAuth()`를 수동으로 호출하면 됩니다.
141
-
142
- ## 수동 OAuth 플로우
143
-
144
- 가장 단순한 SDK 플로우는 아래와 같습니다.
44
+ ### OAuth 로그인
145
45
 
146
46
  ```tsx
147
- import { Linking } from 'react-native';
148
- import { useAuthon } from '@authon/react-native';
149
-
150
- export function GoogleButton() {
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
- 방식은 가장 단순하지만, 브라우저 주도형이고 완료 감지는 polling 기반입니다.
164
-
165
- ## 권장 Expo OAuth 플로우
166
-
167
- Android에서 더 자연스러운 브라우저 종료 UX가 필요하면 `flow=redirect`와 `returnTo`를 포함해 OAuth URL을 직접 요청하고 `expo-web-browser`로 여는 방식을 권장합니다.
53
+ ### 로그아웃
168
54
 
169
55
  ```tsx
170
- import * as WebBrowser from 'expo-web-browser';
171
- import { Button } from 'react-native';
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
- - [Authon docs](https://authon.dev/docs)
248
- - [Authon OAuth flow](https://authon.dev/docs)
62
+ | 변수 | 필수 | 설명 |
63
+ |------|------|------|
64
+ | `AUTHON_PUBLISHABLE_KEY` | Yes | 퍼블리셔블 키 |
249
65
 
250
66
  ## 라이선스
251
67
 
252
- [MIT](../../LICENSE)
68
+ MIT
package/README.md CHANGED
@@ -2,13 +2,28 @@
2
2
 
3
3
  # @authon/react-native
4
4
 
5
- React Native SDK for [Authon](https://authon.dev). It provides:
5
+ > Drop-in React Native authentication with secure token storage — Auth0 alternative
6
6
 
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`)
7
+ [![npm version](https://img.shields.io/npm/v/@authon/react-native?color=6d28d9)](https://www.npmjs.com/package/@authon/react-native)
8
+ [![License](https://img.shields.io/badge/license-MIT-blue)](../../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
- `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.
22
-
23
- ## Setup
35
+ ## Quick Start
24
36
 
25
37
  ```tsx
26
- import { AuthonProvider } from '@authon/react-native';
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
- export default function App() {
36
- return (
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, gap: 12 }}>
59
+ <View style={{ padding: 24 }}>
108
60
  <Text>Welcome, {user?.displayName ?? user?.email}</Text>
109
- <Button title="Sign out" onPress={signOut} />
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
- {error ? <Text style={{ color: 'red' }}>{error}</Text> : null}
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
- ## Social Buttons
126
-
127
- ```tsx
128
- import { SocialButtons } from '@authon/react-native';
129
-
130
- export function SocialLoginSection() {
75
+ export default function App() {
131
76
  return (
132
- <SocialButtons
133
- onSuccess={() => console.log('Signed in')}
134
- onError={(error) => console.error(error)}
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
- For fully custom buttons, use `SocialButton` or call `startOAuth()` / `completeOAuth()` yourself.
87
+ ## Common Tasks
141
88
 
142
- ## Manual OAuth Flow
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
- export function GoogleButton() {
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
- This is the simplest option, but it is browser-driven and completion is polling-based.
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
- 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
- );
114
+ function GoogleButton() {
115
+ const { startOAuth, completeOAuth } = useAuthon();
190
116
 
191
- if (!response.ok) {
192
- throw new Error(await response.text());
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 response.json() as Promise<{ url: string; state: string }>;
127
+ return <Button title="Sign in with Google" onPress={handlePress} />;
196
128
  }
129
+ ```
197
130
 
198
- export function GoogleButton() {
199
- const { completeOAuth, getToken } = useAuthon();
131
+ ### Get Current User
200
132
 
201
- const handlePress = async () => {
202
- const { url, state } = await requestOAuthUrl('google');
203
- const pollPromise = completeOAuth(state);
133
+ ```tsx
134
+ import { useUser } from '@authon/react-native';
204
135
 
205
- await WebBrowser.openAuthSessionAsync(url, APP_DEEP_LINK);
206
- await pollPromise;
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
- const authonAccessToken = getToken();
209
- // If your app also has its own backend session, exchange authonAccessToken here.
210
- };
144
+ ### Add Email/Password Auth
211
145
 
212
- return <Button title="Continue with Google" onPress={handlePress} />;
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
- Example HTTPS bridge page:
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>
151
+ ### Handle Sign Out
152
+
153
+ ```tsx
154
+ const { signOut } = useAuthon();
155
+ await signOut();
235
156
  ```
236
157
 
237
- ## Important Notes
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
- - 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()`.
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
- ## Docs
185
+ ## Comparison
246
186
 
247
- - [Authon docs](https://authon.dev/docs)
248
- - [Authon OAuth flow](https://authon.dev/docs)
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
- [MIT](../../LICENSE)
197
+ MIT
package/dist/index.cjs CHANGED
@@ -211,6 +211,12 @@ var AuthonMobileClient = class {
211
211
  if (data.status === "completed" && data.accessToken) {
212
212
  return data;
213
213
  }
214
+ if (data.status === "error") {
215
+ return {
216
+ status: "error",
217
+ message: typeof data.message === "string" ? data.message : "OAuth failed"
218
+ };
219
+ }
214
220
  return null;
215
221
  } catch {
216
222
  return null;
@@ -222,6 +228,9 @@ var AuthonMobileClient = class {
222
228
  for (let i = 0; i < maxAttempts; i++) {
223
229
  const result = await this.pollOAuth(state);
224
230
  if (result) {
231
+ if (result.status === "error") {
232
+ throw new Error(result.message || "OAuth failed");
233
+ }
225
234
  this.tokens = this.toTokenPair(result);
226
235
  this.user = result.user;
227
236
  await this.persistTokens();
@@ -911,9 +920,7 @@ function SocialButtons({
911
920
  onSuccess?.();
912
921
  } catch (e) {
913
922
  const error = e instanceof Error ? e : new Error(String(e));
914
- if (error.message !== "OAuth timeout") {
915
- onError?.(error);
916
- }
923
+ onError?.(error);
917
924
  } finally {
918
925
  setLoadingProvider(null);
919
926
  }