react-native-nitro-auth 0.6.1 → 0.6.3

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.md CHANGED
@@ -1,33 +1,19 @@
1
1
  # react-native-nitro-auth
2
2
 
3
- [![npm](https://img.shields.io/badge/npm-v0.6.1-f97316?style=flat-square)](https://www.npmjs.com/package/react-native-nitro-auth)
4
- [![license](https://img.shields.io/badge/license-MIT-007ec6?style=flat-square)](https://github.com/JoaoPauloCMarra/react-native-nitro-auth/blob/main/LICENSE)
5
- [![react-native](https://img.shields.io/badge/react--native-%3E%3D0.75-61dafb?style=flat-square)](https://reactnative.dev/)
6
- [![nitro-modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.35.0-black?style=flat-square)](https://nitro.margelo.com/)
3
+ [![npm version](https://img.shields.io/npm/v/react-native-nitro-auth?color=f97316&label=npm)](https://www.npmjs.com/package/react-native-nitro-auth)
4
+ [![license](https://img.shields.io/npm/l/react-native-nitro-auth?color=007ec6)](https://github.com/JoaoPauloCMarra/react-native-nitro-auth/blob/main/LICENSE)
5
+ [![React Native](https://img.shields.io/badge/react--native-%3E%3D0.75-61dafb)](https://reactnative.dev/)
6
+ [![Expo](https://img.shields.io/badge/expo-SDK%2056-000020)](https://expo.dev/)
7
+ [![Nitro Modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.35.7-black)](https://nitro.margelo.com/)
8
+ [![TypeScript](https://img.shields.io/badge/typescript-6.0-3178c6)](https://www.typescriptlang.org/)
7
9
 
8
- Fast React Native authentication for Google Sign-In, Apple Sign-In, and Microsoft Entra ID, built on Nitro Modules and JSI.
10
+ Google Sign-In, Apple Sign-In, and Microsoft Entra ID for React Native and
11
+ Expo, powered by Nitro Modules.
9
12
 
10
- `react-native-nitro-auth` gives Expo and React Native apps one typed API for native social login, web OAuth, token refresh, incremental scopes, and auth state listeners without owning your app's long-term token storage.
11
-
12
- ## Why Use It?
13
-
14
- - One package for Google, Apple, and Microsoft authentication on React Native.
15
- - Native iOS and Android bridges powered by `react-native-nitro-modules`.
16
- - Expo config plugin for client IDs, URL schemes, entitlements, and Android resources.
17
- - Web implementation for Expo web with Google, Apple, and Microsoft OAuth.
18
- - Typed `useAuth()` hook, `AuthService`, `SocialButton`, and `AuthError`.
19
- - App-owned persistence model: tokens stay in memory unless your app stores a snapshot.
20
- - Built-in flows for silent restore, token refresh, account picker, login hints, and incremental Google scopes.
21
- - Consistent `AuthError` mapping for async `AuthService` failures on native and web.
22
-
23
- ## Choose Your Path
24
-
25
- | Need | Use |
26
- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
27
- | Google, Apple, or Microsoft sign-in in an Expo or React Native app | `react-native-nitro-auth` |
28
- | Generic OAuth or OIDC provider not covered by this package | `expo-auth-session` or `react-native-app-auth` |
29
- | Firebase user management, password auth, MFA, and hosted auth platform | `@react-native-firebase/auth`, Auth0, Authgear, or your IDaaS SDK |
30
- | Server-side session validation | Your backend; client JWT decode is display-only |
13
+ Use it when you want one typed authentication API for native social login, web
14
+ OAuth, token refresh, incremental scopes, account listeners, and consistent
15
+ `AuthError` handling. The package keeps tokens in memory; your app decides what
16
+ to persist and where.
31
17
 
32
18
  ## Install
33
19
 
@@ -35,35 +21,25 @@ Fast React Native authentication for Google Sign-In, Apple Sign-In, and Microsof
35
21
  bun add react-native-nitro-auth react-native-nitro-modules
36
22
  ```
37
23
 
38
- For Expo projects, prebuild after adding the config plugin:
24
+ For Expo development builds:
39
25
 
40
26
  ```sh
41
- bunx expo prebuild --clean
27
+ bunx expo install react-native-nitro-auth react-native-nitro-modules
28
+ bunx expo prebuild
42
29
  ```
43
30
 
44
- The example app uses Expo Continuous Native Generation. Its `apps/example/android` and `apps/example/ios` folders are generated local artifacts and intentionally ignored by git.
45
-
46
- For bare React Native projects, install pods after installing the package:
31
+ For bare React Native apps:
47
32
 
48
33
  ```sh
49
34
  cd ios && pod install
50
35
  ```
51
36
 
52
- ## Requirements
37
+ Expo Go cannot load Nitro native modules. Use an Expo development build or a
38
+ bare app.
53
39
 
54
- | Runtime | Requirement |
55
- | ------------------ | ---------------------------------------- |
56
- | React Native | `>=0.75` peer range |
57
- | Nitro Modules | `>=0.35` peer range |
58
- | iOS | 16.4+ for Expo SDK 56 |
59
- | Android | min SDK 24+ recommended |
60
- | Validated baseline | Expo SDK 56, React Native 0.85, React 19 |
40
+ ## Expo Config
61
41
 
62
- The package keeps a wide React Native peer range for existing consumers, but this release is validated against Expo SDK 56, React Native 0.85.3, React 19.2.3, and Nitro Modules 0.35.7.
63
-
64
- ## Expo Setup
65
-
66
- Add the plugin to `app.json` or `app.config.js`.
42
+ Add the plugin to `app.json` or `app.config.js` before prebuild:
67
43
 
68
44
  ```js
69
45
  export default {
@@ -103,464 +79,176 @@ export default {
103
79
  microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
104
80
  microsoftTenant: process.env.MICROSOFT_TENANT,
105
81
  microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
106
- nitroAuthWebStorage: "memory",
107
- nitroAuthPersistTokensOnWeb: false,
82
+ nitroAuthWebStorage: "session",
108
83
  },
109
84
  },
110
85
  };
111
86
  ```
112
87
 
113
- ### Plugin Options
114
-
115
- | Option | Platform | Purpose |
116
- | ---------------------------- | -------- | ---------------------------------------------------------------------- |
117
- | `ios.googleClientId` | iOS | Google iOS OAuth client ID |
118
- | `ios.googleServerClientId` | iOS | Google web/server client ID for server auth code flows |
119
- | `ios.googleUrlScheme` | iOS | Reversed iOS client ID URL scheme |
120
- | `ios.appleSignIn` | iOS | Adds Apple Sign-In entitlement when `true` |
121
- | `ios.microsoftClientId` | iOS | Microsoft app/client ID |
122
- | `ios.microsoftTenant` | iOS | Microsoft tenant, `common`, `organizations`, `consumers`, or tenant ID |
123
- | `ios.microsoftB2cDomain` | iOS | Azure AD B2C domain |
124
- | `android.googleClientId` | Android | Google web OAuth client ID |
125
- | `android.microsoftClientId` | Android | Microsoft app/client ID |
126
- | `android.microsoftTenant` | Android | Microsoft tenant |
127
- | `android.microsoftB2cDomain` | Android | Azure AD B2C domain |
128
-
129
- ## Provider Setup
130
-
131
- ### Google Sign-In
132
-
133
- Create OAuth clients in Google Cloud Console:
134
-
135
- - iOS client ID for your bundle identifier.
136
- - Web client ID for Android, web, and server auth code flows.
137
- - Android SHA-1/SHA-256 entries for local debug and release signing.
138
-
139
- Use the iOS reversed client ID as `GOOGLE_IOS_URL_SCHEME`.
140
-
141
- ### Apple Sign-In
142
-
143
- Set `ios.appleSignIn: true` in the config plugin. Apple returns name and email only on the first authorization for a user. Store any profile fields you need in your own backend or app state.
144
-
145
- Apple Sign-In is supported on iOS and web. It is intentionally reported as `unsupported_provider` on Android.
146
-
147
- ### Microsoft Entra ID
148
-
149
- Create an app registration in Microsoft Entra ID and add redirect URIs:
150
-
151
- - iOS: `msauth.<bundleIdentifier>://auth`
152
- - Android: `msauth://<androidPackage>/<clientId>`
153
- - Web: your web origin, for example `https://app.example.com`
154
-
155
- Use `microsoftTenant` for `common`, `organizations`, `consumers`, a tenant ID, or a B2C policy path. Use `microsoftB2cDomain` for Azure AD B2C.
88
+ Plugin options:
89
+
90
+ | Option | Platform | Required for |
91
+ | ---------------------------- | -------- | -------------------------------- |
92
+ | `ios.googleClientId` | iOS | Google Sign-In on iOS. |
93
+ | `ios.googleServerClientId` | iOS | Google server auth code flow. |
94
+ | `ios.googleUrlScheme` | iOS | Google redirect URL scheme. |
95
+ | `ios.appleSignIn` | iOS | Apple Sign-In entitlement. |
96
+ | `ios.microsoftClientId` | iOS | Microsoft Entra ID native login. |
97
+ | `ios.microsoftTenant` | iOS | Microsoft tenant override. |
98
+ | `ios.microsoftB2cDomain` | iOS | Microsoft B2C hostname. |
99
+ | `android.googleClientId` | Android | Google Sign-In on Android. |
100
+ | `android.microsoftClientId` | Android | Microsoft Entra ID native login. |
101
+ | `android.microsoftTenant` | Android | Microsoft tenant override. |
102
+ | `android.microsoftB2cDomain` | Android | Microsoft B2C hostname. |
103
+
104
+ Web reads provider client IDs from `expo.extra`; native platforms read values
105
+ written by the plugin during prebuild.
106
+
107
+ Microsoft tenant values are validated before opening the authorization URL. Use
108
+ `common`, `organizations`, `consumers`, a tenant ID, or a tenant domain for
109
+ standard Entra ID. For B2C, set `microsoftB2cDomain` to a hostname such as
110
+ `contoso.b2clogin.com` and set `microsoftTenant` to a policy such as
111
+ `B2C_1_signin`. For custom B2C domains, set `microsoftTenant` to a tenant/policy
112
+ path such as `contoso.onmicrosoft.com/B2C_1_signin`.
156
113
 
157
114
  ## Quick Start
158
115
 
159
116
  ```tsx
160
- import { Button, Text, View } from "react-native";
161
117
  import {
162
- AuthError,
163
- type GoogleLoginOptions,
118
+ AuthService,
164
119
  useAuth,
120
+ type ProviderLoginOptions,
165
121
  } from "react-native-nitro-auth";
166
122
 
167
- const googleOptions = {
168
- scopes: ["email", "profile"],
169
- forceAccountPicker: true,
170
- } satisfies GoogleLoginOptions;
171
-
172
- export function SignInScreen() {
173
- const { user, loading, login, logout, getAccessToken } = useAuth();
123
+ export function SignInButton() {
124
+ const { user, login, logout } = useAuth();
174
125
 
175
126
  async function signInWithGoogle() {
176
- try {
177
- await login("google", googleOptions);
178
- } catch (e) {
179
- const error = AuthError.from(e);
180
- console.warn(error.code, error.underlyingMessage);
181
- }
182
- }
127
+ const options: ProviderLoginOptions<"google"> = {
128
+ scopes: ["openid", "profile", "email"],
129
+ };
183
130
 
184
- async function readToken() {
185
- const token = await getAccessToken();
186
- console.log(token);
131
+ await login("google", options);
187
132
  }
188
133
 
189
- return (
190
- <View>
191
- <Text>{user?.email ?? "Signed out"}</Text>
192
- <Button
193
- title={loading ? "Signing in..." : "Sign in with Google"}
194
- onPress={signInWithGoogle}
195
- />
196
- <Button title="Get access token" onPress={readToken} />
197
- <Button title="Sign out" onPress={logout} />
198
- </View>
199
- );
200
- }
201
- ```
202
-
203
- ## SocialButton
134
+ if (user) {
135
+ return <Button title="Sign out" onPress={logout} />;
136
+ }
204
137
 
205
- ```tsx
206
- import { SocialButton } from "react-native-nitro-auth";
207
-
208
- export function AuthButtons() {
209
- return (
210
- <>
211
- <SocialButton provider="google" />
212
- <SocialButton provider="apple" variant="black" />
213
- <SocialButton provider="microsoft" variant="outline" />
214
- </>
215
- );
138
+ return <Button title="Continue with Google" onPress={signInWithGoogle} />;
216
139
  }
217
- ```
218
-
219
- ## AuthService
220
-
221
- Use `AuthService` when you need auth outside React components.
222
-
223
- ```ts
224
- import { AuthService } from "react-native-nitro-auth";
225
-
226
- await AuthService.silentRestore();
227
140
 
228
- const unsubscribe = AuthService.onAuthStateChanged((user) => {
229
- console.log(user?.email);
230
- });
231
-
232
- const tokensUnsubscribe = AuthService.onTokensRefreshed((tokens) => {
233
- console.log(tokens.expirationTime);
234
- });
235
-
236
- unsubscribe();
237
- tokensUnsubscribe();
238
- ```
239
-
240
- ## Login Options
241
-
242
- ```ts
243
- await login("google", {
244
- scopes: ["email", "profile"],
245
- loginHint: "user@example.com",
246
- nonce: "opaque-nonce",
247
- useOneTap: true,
248
- forceAccountPicker: true,
249
- filterByAuthorizedAccounts: true,
250
- useLegacyGoogleSignIn: true,
251
- forceCodeForRefreshToken: true,
252
- hostedDomain: "company.com",
253
- requestVerifiedPhoneNumber: true,
254
- });
255
-
256
- await login("apple", {
257
- scopes: ["email", "name"],
258
- nonce: "opaque-nonce",
259
- });
260
-
261
- await login("microsoft", {
262
- scopes: ["openid", "profile", "email", "offline_access", "User.Read"],
263
- loginHint: "user@example.com",
141
+ await AuthService.login("microsoft", {
264
142
  tenant: "organizations",
265
143
  prompt: "select_account",
266
144
  });
267
145
  ```
268
146
 
269
- | Option | Provider | Platform | Notes |
270
- | ---------------------------- | ------------ | ---------------- | ----------------------------------------------------------- |
271
- | `scopes` | All | iOS, Android, web | Requested OAuth scopes. Apple is unavailable on Android. |
272
- | `loginHint` | Google, Microsoft | iOS, Android, web | Prefills account selection when supported by the provider. |
273
- | `nonce` | Google, Apple | iOS, Android, web | Passed to provider ID-token flows when the SDK supports it. |
274
- | `useOneTap` | Google | Android | Enables Credential Manager auto-select. |
275
- | `useSheet` | Google | iOS | Compatibility alias; prefer `forceAccountPicker`. |
276
- | `forceAccountPicker` | Google | iOS, Android, web | Forces an account picker. Android uses the legacy chooser. |
277
- | `filterByAuthorizedAccounts` | Google | Android | Limits Credential Manager to authorized accounts. |
278
- | `useLegacyGoogleSignIn` | Google | Android | Uses legacy Google Sign-In for server auth code flows. |
279
- | `forceCodeForRefreshToken` | Google | Android | Forces a new server auth code on the legacy Google path. |
280
- | `hostedDomain` | Google | iOS, Android, web | Hints or filters Google Workspace hosted-domain accounts. |
281
- | `openIDRealm` | Google | iOS, web | Adds OpenID realm support where the SDK exposes it. |
282
- | `requestVerifiedPhoneNumber` | Google | Android | Requests verified phone number through Credential Manager. |
283
- | `tenant` | Microsoft | iOS, Android, web | Overrides configured tenant. |
284
- | `prompt` | Microsoft | iOS, Android, web | `login`, `consent`, `select_account`, or `none`. |
285
-
286
- ## Incremental Scopes
147
+ ## Providers
287
148
 
288
- ```ts
289
- const calendarScope = "https://www.googleapis.com/auth/calendar.readonly";
149
+ | Provider | Native | Web | Notes |
150
+ | --------- | ------------ | --- | --------------------------------------------------------------------- |
151
+ | Google | iOS, Android | Yes | Supports account picker, login hint, refresh, and incremental scopes. |
152
+ | Apple | iOS | Yes | Apple returns name and email only on first authorization. |
153
+ | Microsoft | iOS, Android | Yes | Supports tenant and B2C configuration. |
290
154
 
291
- await requestScopes([calendarScope]);
292
- await revokeScopes([calendarScope]);
293
- ```
155
+ Use `expo-auth-session`, `react-native-app-auth`, Auth0, Firebase Auth, or your
156
+ identity provider SDK when you need a generic OAuth/OIDC provider, password
157
+ auth, MFA, hosted user management, or server session management.
294
158
 
295
- On Android, incremental Google scope requests use the legacy Google Sign-In APIs because Credential Manager does not expose an equivalent existing-account scope query.
296
-
297
- ## Storage Model
298
-
299
- Native tokens are kept in memory by design. The package does not persist Microsoft refresh tokens or provider tokens to disk. Your app owns persistence and secure storage policy.
300
-
301
- For app-managed persistence, store only the minimum state your product needs:
302
-
303
- ```ts
304
- import { AuthService } from "react-native-nitro-auth";
305
-
306
- const snapshot = {
307
- user: AuthService.currentUser,
308
- scopes: AuthService.grantedScopes,
309
- updatedAt: Date.now(),
310
- };
311
- ```
312
-
313
- On web, the default is also memory storage. You can opt into browser storage with:
314
-
315
- ```js
316
- extra: {
317
- nitroAuthWebStorage: "session", // "session", "local", or "memory"
318
- nitroAuthPersistTokensOnWeb: true,
319
- }
320
- ```
321
-
322
- ## Error Contract
323
-
324
- All public async APIs throw `AuthError`, including provider errors surfaced by native and web `AuthService` implementations.
325
-
326
- ```ts
327
- try {
328
- await AuthService.login("microsoft");
329
- } catch (e) {
330
- const error = AuthError.from(e);
331
- switch (error.code) {
332
- case "cancelled":
333
- break;
334
- case "configuration_error":
335
- break;
336
- case "token_error":
337
- break;
338
- default:
339
- break;
340
- }
341
- }
342
- ```
343
-
344
- Known error codes:
345
-
346
- ```ts
347
- type AuthErrorCode =
348
- | "cancelled"
349
- | "timeout"
350
- | "popup_blocked"
351
- | "network_error"
352
- | "configuration_error"
353
- | "not_signed_in"
354
- | "operation_in_progress"
355
- | "unsupported_provider"
356
- | "invalid_state"
357
- | "invalid_nonce"
358
- | "token_error"
359
- | "no_id_token"
360
- | "parse_error"
361
- | "refresh_failed"
362
- | "unknown";
363
- ```
364
-
365
- `underlyingMessage` keeps the raw native or OAuth message when it differs from the stable code.
366
-
367
- ## API Reference
368
-
369
- ### Exports
370
-
371
- ```ts
372
- export * from "react-native-nitro-auth";
373
- ```
159
+ ## API
374
160
 
375
161
  Main exports:
376
162
 
377
- - `useAuth()`
378
- - `AuthService`
379
- - `SocialButton`
380
- - `AuthError`
381
- - `isAuthErrorCode()`
382
- - `toAuthErrorCode()`
383
- - `AuthProvider`
384
- - `AuthUser`
385
- - `AuthTokens`
386
- - `LoginOptions`
387
- - `ProviderLoginOptions`
388
- - `LoginOptionsByProvider`
389
- - `GoogleLoginOptions`
390
- - `GoogleIOSLoginOptions`
391
- - `GoogleAndroidLoginOptions`
392
- - `GoogleWebLoginOptions`
393
- - `AppleLoginOptions`
394
- - `AppleIOSLoginOptions`
395
- - `AppleWebLoginOptions`
396
- - `MicrosoftLoginOptions`
397
- - `AuthLogin`
398
- - `TypedAuth`
399
-
400
- ### useAuth()
163
+ - `useAuth()` for React state, login, logout, refresh, and listeners.
164
+ - `AuthService` for imperative login, refresh, logout, and user reads.
165
+ - `SocialButton` for provider-aware UI.
166
+ - `AuthProvider` for the `"google"`, `"apple"`, and `"microsoft"` provider names.
167
+ - `AuthError` and `AuthErrorCode` for deterministic failures.
168
+ - Provider option types for strongly typed login calls.
401
169
 
402
- ```ts
403
- type UseAuthReturn = {
404
- user: AuthUser | undefined;
405
- scopes: string[];
406
- loading: boolean;
407
- error: AuthError | undefined;
408
- hasPlayServices: boolean;
409
- login(provider: AuthProvider, options?: LoginOptions): Promise<void>;
410
- logout(): void;
411
- requestScopes(scopes: string[]): Promise<void>;
412
- revokeScopes(scopes: string[]): Promise<void>;
413
- revokeAccess(): Promise<void>;
414
- getAccessToken(): Promise<string | undefined>;
415
- refreshToken(): Promise<AuthTokens>;
416
- silentRestore(): Promise<void>;
417
- };
418
- ```
419
-
420
- ### Strong Login Types
421
-
422
- `AuthService.login()` and `useAuth().login()` infer the allowed option object from the provider argument. Provider-specific option types intentionally reject unsupported keys, so TypeScript can catch mistakes before they become native configuration bugs.
423
-
424
- ```ts
425
- await AuthService.login("apple", {
426
- nonce: "opaque-nonce",
427
- });
428
-
429
- await AuthService.login("microsoft", {
430
- tenant: "organizations",
431
- prompt: "select_account",
432
- });
433
- ```
434
-
435
- Provider/platform option helpers are exported for config builders and AI-generated integrations:
170
+ Provider-aware login calls reject unsupported option fields at compile time:
436
171
 
437
172
  ```ts
438
173
  import type {
439
- GoogleAndroidLoginOptions,
440
- GoogleIOSLoginOptions,
174
+ ProviderLoginOptions,
441
175
  MicrosoftLoginOptions,
442
176
  } from "react-native-nitro-auth";
443
177
 
444
- const androidGoogleOptions = {
445
- useOneTap: true,
446
- filterByAuthorizedAccounts: true,
447
- requestVerifiedPhoneNumber: true,
448
- } satisfies GoogleAndroidLoginOptions;
449
-
450
- const iosGoogleOptions = {
178
+ const googleOptions: ProviderLoginOptions<"google"> = {
179
+ scopes: ["openid", "email"],
451
180
  hostedDomain: "company.com",
452
- openIDRealm: "https://example.com",
453
- } satisfies GoogleIOSLoginOptions;
181
+ forceAccountPicker: true,
182
+ };
454
183
 
455
- const microsoftOptions = {
184
+ const microsoftOptions: MicrosoftLoginOptions = {
456
185
  tenant: "organizations",
457
186
  prompt: "select_account",
458
- } satisfies MicrosoftLoginOptions;
459
- ```
460
-
461
- Examples of mistakes that TypeScript rejects:
462
-
463
- ```ts
464
- await AuthService.login("apple", {
465
- tenant: "organizations",
466
- });
467
-
468
- await AuthService.login("microsoft", {
469
- nonce: "opaque-nonce",
470
- });
471
- ```
472
-
473
- ### AuthUser
474
-
475
- ```ts
476
- type AuthUser = {
477
- provider: "google" | "apple" | "microsoft";
478
- email?: string;
479
- name?: string;
480
- photo?: string;
481
- idToken?: string;
482
- accessToken?: string;
483
- refreshToken?: string;
484
- serverAuthCode?: string;
485
- authorizationCode?: string;
486
- userId?: string;
487
- phoneNumber?: string;
488
- hostedDomain?: string;
489
- scopes?: string[];
490
- expirationTime?: number;
491
- underlyingError?: string;
492
187
  };
493
188
  ```
494
189
 
495
- ## Example App
190
+ Supported login options:
496
191
 
497
- The example app is the fastest way to verify setup and read a complete integration.
192
+ | Provider | Options |
193
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
194
+ | Google | `scopes`, `loginHint`, `nonce`, `forceAccountPicker`, `hostedDomain`, `useSheet`, `openIDRealm`, `useOneTap`, `filterByAuthorizedAccounts`, `useLegacyGoogleSignIn`, `forceCodeForRefreshToken`, `requestVerifiedPhoneNumber` |
195
+ | Apple | `scopes`, `nonce` |
196
+ | Microsoft | `scopes`, `loginHint`, `tenant`, `prompt` |
498
197
 
499
- ```sh
500
- cp apps/example/.env.example apps/example/.env.local
501
- bun install
502
- bun run example:prebuild:clean
503
- bun run example:ios
504
- bun run example:android
505
- ```
198
+ `prompt` is typed as `"login"`, `"consent"`, `"select_account"`, or `"none"`.
506
199
 
507
- The demo includes:
200
+ ## Storage Model
508
201
 
509
- - Provider cards for Google, Apple, and Microsoft.
510
- - Token and scope operations.
511
- - Silent restore, account picker, revoke access, and native logging actions.
512
- - Platform-gated controls for each supported Google, Apple, and Microsoft option.
513
- - App-owned disk snapshot example with `react-native-nitro-storage`.
514
- - Runtime smoke tests for the public API.
202
+ Tokens are held in memory. Persist only the snapshot your app actually needs,
203
+ preferably in your own secure storage or backend session. JWT decode on the
204
+ client is for display and routing only; signature validation belongs on your
205
+ server.
515
206
 
516
- ## Troubleshooting
207
+ ## Error Contract
517
208
 
518
- | Symptom | Check |
519
- | ------------------------------------- | -------------------------------------------------------------------------------- |
520
- | `configuration_error` on Google | Client ID is missing or wrong for the current platform |
521
- | Google works in debug but not release | Add release SHA-1/SHA-256 fingerprints to Google Cloud Console |
522
- | Android `hasPlayServices` is false | Use an emulator image with Google Play Services |
523
- | Apple email/name missing | Apple only returns these fields on first authorization |
524
- | Microsoft `invalid_state` | Redirect URI or app resume path is wrong, or an old auth redirect completed late |
525
- | Microsoft `token_error` | Check tenant, client ID, redirect URI, and requested scopes |
526
- | Web popup blocked | Call `login()` from a user gesture such as a button press |
527
- | `operation_in_progress` | A provider flow is already active; wait for it to finish or sign out |
209
+ Async public APIs throw `AuthError` with a stable `code`, `provider`, `platform`,
210
+ and `message`. Use `instanceof AuthError` when branching in UI code.
528
211
 
529
- ## Production Notes
212
+ Error codes are `cancelled`, `timeout`, `popup_blocked`, `network_error`,
213
+ `configuration_error`, `not_signed_in`, `operation_in_progress`,
214
+ `unsupported_provider`, `invalid_state`, `invalid_nonce`, `token_error`,
215
+ `no_id_token`, `parse_error`, `refresh_failed`, and `unknown`.
530
216
 
531
- - Verify ID tokens on your backend. Client-side JWT parsing is for display and expiration hints only.
532
- - Store refresh tokens only in storage your app explicitly owns and secures.
533
- - Keep Google debug and release signing fingerprints in sync with your OAuth clients.
534
- - Add provider-specific redirect URIs for every environment.
535
- - Run the example app on iOS and Android before shipping provider config changes.
217
+ ## Platform Support
536
218
 
537
- ## Release Checks
219
+ | Platform | Status |
220
+ | -------- | ----------------------------------------------------------- |
221
+ | iOS | Google, Apple, Microsoft native flows. |
222
+ | Android | Google and Microsoft native flows. |
223
+ | Web | Google, Apple, and Microsoft OAuth through Expo web config. |
224
+ | Expo | Development builds with the config plugin. |
538
225
 
539
- ```sh
540
- bun run release:preflight
541
- ```
226
+ Validated baseline: Expo SDK 56, React Native 0.85.3, React 19.2.3, and Nitro
227
+ Modules 0.35.7.
542
228
 
543
- The release preflight runs core-version verification, codegen, build, lint, typecheck, Jest, C++ tests, Expo dependency validation, Expo Doctor, Expo config introspection, package docs sync, pack dry run, and `bun publish --dry-run --ignore-scripts`.
229
+ ## Troubleshooting
544
230
 
545
- CI runs the same preflight with registry publish dry-run disabled because GitHub pull-request jobs do not have npm publish credentials.
231
+ - **Expo Go error:** build a dev client; Expo Go cannot load Nitro modules.
232
+ - **Provider not configured:** verify plugin values, `expo.extra`, and that you
233
+ prebuilt after changing config.
234
+ - **Apple profile missing name/email:** Apple only sends those fields on the
235
+ first authorization.
236
+ - **Microsoft redirect mismatch:** confirm bundle ID, Android package,
237
+ `microsoftClientId`, and tenant/B2C settings match the provider console.
546
238
 
547
- For faster local iteration before the full release dry run:
239
+ ## Development
548
240
 
549
241
  ```sh
242
+ bun install
550
243
  bun run check
551
- bun run test:cpp
552
- bun run --cwd packages/react-native-nitro-auth test:coverage -- --runInBand
553
- bun run --cwd packages/react-native-nitro-auth test:cpp:coverage
554
- ```
555
-
556
- Before shipping provider or native config changes, also verify the example app:
557
-
558
- ```sh
559
- bun run example:prebuild
244
+ bun run release:preflight
560
245
  bun run example:android
561
246
  bun run example:ios
562
247
  ```
563
248
 
249
+ Run native example builds before release when changing plugin, native, Nitro, or
250
+ packaging files.
251
+
564
252
  ## License
565
253
 
566
254
  MIT