react-native-nitro-auth 0.5.0 → 0.5.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.
Files changed (124) hide show
  1. package/README.md +374 -189
  2. package/android/src/main/cpp/PlatformAuth+Android.cpp +4 -1
  3. package/android/src/main/java/com/auth/AuthAdapter.kt +99 -182
  4. package/android/src/main/java/com/auth/GoogleSignInActivity.kt +2 -1
  5. package/android/src/main/java/com/auth/NitroAuthPackage.kt +2 -0
  6. package/app.plugin.js +2 -9
  7. package/cpp/AuthCache.cpp +12 -102
  8. package/cpp/HybridAuth.cpp +37 -61
  9. package/cpp/HybridAuth.hpp +2 -4
  10. package/ios/AuthAdapter.swift +21 -25
  11. package/lib/commonjs/Auth.web.js +433 -164
  12. package/lib/commonjs/Auth.web.js.map +1 -1
  13. package/lib/commonjs/index.js +0 -12
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/index.web.js +0 -12
  16. package/lib/commonjs/index.web.js.map +1 -1
  17. package/lib/commonjs/js-storage-adapter.js +2 -0
  18. package/lib/commonjs/js-storage-adapter.js.map +1 -0
  19. package/lib/commonjs/service.js +7 -84
  20. package/lib/commonjs/service.js.map +1 -1
  21. package/lib/commonjs/service.web.js +1 -5
  22. package/lib/commonjs/service.web.js.map +1 -1
  23. package/lib/commonjs/ui/social-button.js +44 -29
  24. package/lib/commonjs/ui/social-button.js.map +1 -1
  25. package/lib/commonjs/ui/social-button.web.js +44 -29
  26. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  27. package/lib/commonjs/use-auth.js +8 -2
  28. package/lib/commonjs/use-auth.js.map +1 -1
  29. package/lib/commonjs/utils/logger.js +12 -4
  30. package/lib/commonjs/utils/logger.js.map +1 -1
  31. package/lib/module/Auth.web.js +433 -164
  32. package/lib/module/Auth.web.js.map +1 -1
  33. package/lib/module/index.js +0 -1
  34. package/lib/module/index.js.map +1 -1
  35. package/lib/module/index.web.js +0 -1
  36. package/lib/module/index.web.js.map +1 -1
  37. package/lib/module/js-storage-adapter.js +2 -0
  38. package/lib/module/js-storage-adapter.js.map +1 -0
  39. package/lib/module/service.js +7 -84
  40. package/lib/module/service.js.map +1 -1
  41. package/lib/module/service.web.js +1 -5
  42. package/lib/module/service.web.js.map +1 -1
  43. package/lib/module/ui/social-button.js +44 -29
  44. package/lib/module/ui/social-button.js.map +1 -1
  45. package/lib/module/ui/social-button.web.js +44 -29
  46. package/lib/module/ui/social-button.web.js.map +1 -1
  47. package/lib/module/use-auth.js +8 -2
  48. package/lib/module/use-auth.js.map +1 -1
  49. package/lib/module/utils/logger.js +12 -4
  50. package/lib/module/utils/logger.js.map +1 -1
  51. package/lib/typescript/commonjs/Auth.nitro.d.ts +5 -3
  52. package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/Auth.web.d.ts +18 -6
  54. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/index.d.ts +1 -2
  56. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/index.web.d.ts +0 -1
  58. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/js-storage-adapter.d.ts +6 -0
  60. package/lib/typescript/commonjs/js-storage-adapter.d.ts.map +1 -0
  61. package/lib/typescript/commonjs/service.d.ts +1 -8
  62. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/service.web.d.ts +1 -8
  64. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/ui/social-button.d.ts +6 -6
  66. package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/ui/social-button.web.d.ts +6 -6
  68. package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/use-auth.d.ts +4 -4
  70. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  71. package/lib/typescript/commonjs/utils/logger.d.ts +4 -4
  72. package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
  73. package/lib/typescript/module/Auth.nitro.d.ts +5 -3
  74. package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
  75. package/lib/typescript/module/Auth.web.d.ts +18 -6
  76. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  77. package/lib/typescript/module/index.d.ts +1 -2
  78. package/lib/typescript/module/index.d.ts.map +1 -1
  79. package/lib/typescript/module/index.web.d.ts +0 -1
  80. package/lib/typescript/module/index.web.d.ts.map +1 -1
  81. package/lib/typescript/module/js-storage-adapter.d.ts +6 -0
  82. package/lib/typescript/module/js-storage-adapter.d.ts.map +1 -0
  83. package/lib/typescript/module/service.d.ts +1 -8
  84. package/lib/typescript/module/service.d.ts.map +1 -1
  85. package/lib/typescript/module/service.web.d.ts +1 -8
  86. package/lib/typescript/module/service.web.d.ts.map +1 -1
  87. package/lib/typescript/module/ui/social-button.d.ts +6 -6
  88. package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
  89. package/lib/typescript/module/ui/social-button.web.d.ts +6 -6
  90. package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
  91. package/lib/typescript/module/use-auth.d.ts +4 -4
  92. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  93. package/lib/typescript/module/utils/logger.d.ts +4 -4
  94. package/lib/typescript/module/utils/logger.d.ts.map +1 -1
  95. package/nitrogen/generated/android/NitroAuth+autolinking.cmake +0 -1
  96. package/nitrogen/generated/shared/c++/AuthTokens.hpp +5 -1
  97. package/nitrogen/generated/shared/c++/AuthUser.hpp +5 -1
  98. package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +0 -1
  99. package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +0 -5
  100. package/nitrogen/generated/shared/c++/LoginOptions.hpp +5 -1
  101. package/package.json +13 -10
  102. package/src/Auth.nitro.ts +6 -3
  103. package/src/Auth.web.ts +582 -202
  104. package/src/global.d.ts +0 -1
  105. package/src/index.ts +1 -2
  106. package/src/index.web.ts +0 -1
  107. package/src/js-storage-adapter.ts +5 -0
  108. package/src/service.ts +11 -104
  109. package/src/service.web.ts +0 -7
  110. package/src/ui/social-button.tsx +66 -43
  111. package/src/ui/social-button.web.tsx +67 -44
  112. package/src/use-auth.ts +18 -6
  113. package/src/utils/logger.ts +12 -4
  114. package/lib/commonjs/AuthStorage.nitro.js +0 -6
  115. package/lib/commonjs/AuthStorage.nitro.js.map +0 -1
  116. package/lib/module/AuthStorage.nitro.js +0 -4
  117. package/lib/module/AuthStorage.nitro.js.map +0 -1
  118. package/lib/typescript/commonjs/AuthStorage.nitro.d.ts +0 -26
  119. package/lib/typescript/commonjs/AuthStorage.nitro.d.ts.map +0 -1
  120. package/lib/typescript/module/AuthStorage.nitro.d.ts +0 -26
  121. package/lib/typescript/module/AuthStorage.nitro.d.ts.map +0 -1
  122. package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.cpp +0 -23
  123. package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.hpp +0 -65
  124. package/src/AuthStorage.nitro.ts +0 -26
package/README.md CHANGED
@@ -12,12 +12,13 @@ Nitro Auth is a modern authentication library for React Native built on top of [
12
12
 
13
13
  Nitro Auth is designed to replace legacy modules like `@react-native-google-signin/google-signin` with a modern, high-performance architecture.
14
14
 
15
- | Feature | Legacy Modules | Nitro Auth |
16
- | :-------------- | :--------------------------- | :----------------------------- |
17
- | **Performance** | Async bridge overhead (JSON) | **Direct JSI C++ (Zero-copy)** |
18
- | **Persistence** | Varies / Manual | **Built-in & Automatic** |
19
- | **Setup** | Manual async initialization | **Sync & declarative plugins** |
20
- | **Types** | Manual / Brittle | **Fully Generated (Nitrogen)** |
15
+ | Feature | Legacy Modules | Nitro Auth |
16
+ | :---------------- | :--------------------------- | :----------------------------------------------------------------------------- |
17
+ | **Performance** | Async bridge overhead (JSON) | **Direct JSI C++ (Zero-copy)** |
18
+ | **Storage** | Varies / Hidden defaults | **Native: in-memory only; Web: session cache (tokens memory-only by default)** |
19
+ | **Setup** | Manual async initialization | **Sync & declarative plugins** |
20
+ | **Types** | Manual / Brittle | **Fully Generated (Nitrogen)** |
21
+ | **Provider Data** | Varies | **Normalized auth payload** |
21
22
 
22
23
  ## Features
23
24
 
@@ -26,24 +27,56 @@ Nitro Auth is designed to replace legacy modules like `@react-native-google-sign
26
27
  - **Incremental Auth**: Request additional OAuth scopes on the fly.
27
28
  - **Expo Ready**: Comes with a powerful Config Plugin for zero-config setup.
28
29
  - **Cross-Platform**: Unified API for iOS, Android, and Web.
29
- - **Auto-Refresh**: Synchronous access to tokens with automatic silent refresh.
30
+ - **Token Lifecycle Helpers**: `getAccessToken()` refreshes near-expiry tokens; `refreshToken()` allows explicit refresh control.
30
31
  - **Google One-Tap / Sheet**: Modern login experience on Android (Credential Manager) and iOS (Sign-In Sheet).
31
32
  - **Error Metadata**: Detailed native error messages for easier debugging.
32
- - **Custom Storage**: Pluggable storage adapters for secure persistence (e.g. Keychain, MMKV, AsyncStorage).
33
- - **Refresh Interceptors**: Listen to token updates globally.
33
+ - **Normalized Provider Payload**: Exposes provider/user/token fields in a consistent cross-platform shape.
34
+ - **App-Owned Persistence**: Native is stateless by default. Web keeps a non-sensitive session snapshot; apps decide long-term persistence strategy.
35
+
36
+ ## Design Philosophy
37
+
38
+ This is an **auth-only package** - on **native** (iOS/Android) it does NOT store auth data by default.
39
+ On **web**, Nitro Auth stores a non-sensitive auth snapshot in `sessionStorage` by default; sensitive tokens stay memory-only unless explicitly enabled.
40
+
41
+ The package provides:
42
+
43
+ - Login/logout functionality for Google, Apple, and Microsoft
44
+ - Token management (access token, refresh token, ID token)
45
+ - Scope management (request/revoke scopes)
46
+ - Consistent provider/user/token field exposure across iOS, Android, and Web
47
+
48
+ **Storage is the responsibility of the app using this package.** Use your own storage layer (for example [react-native-nitro-storage](https://github.com/JoaoPauloCMarra/react-native-nitro-storage)) to persist app-level auth snapshots/tokens when needed.
49
+
50
+ ### Provider Data Availability
51
+
52
+ Token/data shape is normalized, but provider SDKs do not always return all fields at login time:
53
+
54
+ | Provider + Flow | `idToken` | `accessToken` | `serverAuthCode` | `expirationTime` |
55
+ | --------------------------- | ---------- | ---------------------------- | ---------------------------- | ----------------------------------- |
56
+ | Google (iOS) | Usually ✅ | Provider-dependent | Optional (if configured) | Optional |
57
+ | Google (Android One-Tap) | ✅ | Usually `undefined` at login | `undefined` | Derived from ID token when possible |
58
+ | Google (Android Legacy) | ✅ | Usually `undefined` at login | ✅ (if server client is set) | Derived from ID token when possible |
59
+ | Google (Web) | ✅ | ✅ | Optional (`code`) | Usually ✅ |
60
+ | Microsoft (iOS/Android/Web) | ✅ | Usually ✅ | `undefined` | Usually ✅ |
61
+ | Apple (iOS/Web) | ✅ | `undefined` | `undefined` | `undefined` |
62
+
63
+ > [!NOTE]
64
+ > On Apple, email/name can be limited after first consent depending on Apple policy.
34
65
 
35
66
  ## Installation
36
67
 
37
68
  ```bash
38
69
  bun add react-native-nitro-auth react-native-nitro-modules
39
- # or
40
- npm install react-native-nitro-auth react-native-nitro-modules
41
- # or
42
- yarn add react-native-nitro-auth react-native-nitro-modules
43
- # or
44
- pnpm add react-native-nitro-auth react-native-nitro-modules
45
70
  ```
46
71
 
72
+ ### Requirements
73
+
74
+ | Dependency | Version |
75
+ | ---------------------------- | ----------- |
76
+ | `react-native` | `>= 0.75.0` |
77
+ | `react-native-nitro-modules` | `>= 0.33.9` |
78
+ | `react` | `*` |
79
+
47
80
  For Expo projects, rebuild native code after installation:
48
81
 
49
82
  ```bash
@@ -58,43 +91,54 @@ Fastest way to confirm the package and Microsoft login work:
58
91
  In [Azure Portal](https://portal.azure.com) → **Azure Active Directory** → **App registrations** → **New registration**:
59
92
  - Name: e.g. `Nitro Auth Example`
60
93
  - Supported account types: **Accounts in any organizational directory and personal Microsoft accounts**
61
- - Redirect URI (add after creation):
62
- - **Android**: `msauth://com.auth.example/<client-id>`
94
+ - Redirect URI (add after creation):
95
+ - **Android**: `msauth://com.auth.example/<client-id>`
63
96
  - **iOS**: `msauth.com.auth.example://auth` (use your bundle id)
64
97
  - Under **Authentication** → **Platform configurations** → add **Mobile and desktop applications** with the Android redirect URI above and the iOS one if testing on iOS.
65
- Copy the **Application (client) ID**.
98
+ Copy the **Application (client) ID**.
66
99
 
67
100
  2. **Env file**
68
101
  From the repo root:
102
+
69
103
  ```bash
70
104
  cd apps/example
71
105
  cp .env.example .env.local
72
106
  ```
107
+
73
108
  Edit `.env.local` and set at least:
109
+
74
110
  ```bash
75
111
  MICROSOFT_CLIENT_ID=<your-application-client-id>
76
112
  MICROSOFT_TENANT=common
77
113
  ```
114
+
78
115
  (Google/Apple can stay placeholder if you only care about Microsoft.)
79
116
 
80
117
  3. **Run the app**
81
118
  From the **monorepo root**:
119
+
82
120
  ```bash
83
121
  bun install
84
122
  bun run start
85
123
  ```
124
+
86
125
  In a second terminal:
126
+
87
127
  ```bash
88
128
  bun run example:android
89
129
  # or
90
130
  bun run example:ios
91
131
  ```
132
+
92
133
  Wait for the app to install and open.
93
134
 
94
135
  4. **Test Microsoft**
95
136
  In the app, tap **Sign in with Microsoft**. A browser or in-app tab opens; sign in with a Microsoft/personal account, then you should return to the app with the user shown (email, name, provider MICROSOFT).
96
137
  If you see "configuration_error", check `MICROSOFT_CLIENT_ID` and that the redirect URI in Azure matches your app (e.g. `msauth://com.auth.example/<client-id>` for the example app).
97
138
 
139
+ > [!TIP]
140
+ > In the example app on Android, you can toggle **Legacy Google Sign-In** to compare Credential Manager vs legacy GoogleSignIn (and to get `serverAuthCode`).
141
+
98
142
  ### Expo Setup
99
143
 
100
144
  Add the plugin to `app.json` or `app.config.js`:
@@ -294,7 +338,9 @@ Nitro Auth reads web configuration from `expo.extra`:
294
338
  "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
295
339
  "microsoftTenant": "common",
296
340
  "microsoftB2cDomain": "your-tenant.b2clogin.com",
297
- "appleWebClientId": "com.example.web"
341
+ "appleWebClientId": "com.example.web",
342
+ "nitroAuthWebStorage": "session",
343
+ "nitroAuthPersistTokensOnWeb": false
298
344
  }
299
345
  }
300
346
  }
@@ -302,6 +348,9 @@ Nitro Auth reads web configuration from `expo.extra`:
302
348
 
303
349
  For Apple web sign-in, `appleWebClientId` must be your Apple Service ID. For Microsoft web, make sure your Azure app includes a Web redirect URI matching your site.
304
350
 
351
+ - `nitroAuthWebStorage`: `"session"` (default), `"local"`, or `"memory"`.
352
+ - `nitroAuthPersistTokensOnWeb`: `false` by default (recommended). Set `true` only if you need cross-reload token persistence.
353
+
305
354
  ## Quick Start
306
355
 
307
356
  ### Using the Hook
@@ -450,7 +499,11 @@ If you previously called `GoogleSignin.configure()` at app startup, remove it. N
450
499
 
451
500
  ### Silent Restore
452
501
 
453
- Automatically restore the user session on app startup. This is faster than a full login and works offline if the session is cached.
502
+ Attempts to restore provider SDK sessions on app startup.
503
+
504
+ - Google: restore is supported via provider SDK session state.
505
+ - Apple: provider credentials are re-requested by OS flow.
506
+ - Microsoft: no internal persistence; restore requires your app/backend session strategy.
454
507
 
455
508
  ```tsx
456
509
  useEffect(() => {
@@ -507,56 +560,90 @@ const handleCalendar = async () => {
507
560
  };
508
561
  ```
509
562
 
510
- ### Pluggable Storage Adapters
511
-
512
- Nitro Auth persists the session automatically. By default, it uses secure storage on native (Keychain on iOS, EncryptedSharedPreferences on Android) and `localStorage` on web.
563
+ ### App-Owned Persistence
513
564
 
514
- #### 1) JS Storage (AsyncStorage, MMKV, etc.)
565
+ Nitro Auth is intentionally stateless in-process. Persist only what your app needs.
515
566
 
516
- Easily swap the default storage with your preferred library from the JS layer:
567
+ #### Using react-native-nitro-storage (Recommended)
517
568
 
518
569
  ```ts
519
- import { AuthService, type JSStorageAdapter } from "react-native-nitro-auth";
520
- import { MMKV } from "react-native-mmkv";
521
-
522
- const storage = new MMKV();
570
+ import { AuthService, type AuthUser } from "react-native-nitro-auth";
571
+ import { createStorageItem, StorageScope } from "react-native-nitro-storage";
523
572
 
524
- const mmkvAdapter: JSStorageAdapter = {
525
- save: (key, value) => storage.set(key, value),
526
- load: (key) => storage.getString(key),
527
- remove: (key) => storage.delete(key),
573
+ type AuthSnapshot = {
574
+ user: AuthUser | undefined;
575
+ scopes: string[];
576
+ updatedAt: number | undefined;
528
577
  };
529
578
 
530
- // Set it once at app startup
531
- AuthService.setJSStorageAdapter(mmkvAdapter);
532
- ```
533
-
534
- > [!NOTE]
535
- > Call `setJSStorageAdapter` before your first `useAuth()` or `AuthService` call so cached values are loaded before UI renders.
579
+ const authSnapshotItem = createStorageItem<AuthSnapshot>({
580
+ key: "auth_snapshot",
581
+ scope: StorageScope.Disk,
582
+ defaultValue: {
583
+ user: undefined,
584
+ scopes: [],
585
+ updatedAt: undefined,
586
+ },
587
+ });
536
588
 
537
- #### 2) Native Storage (Keychain, etc.)
589
+ // Save on auth changes (do not overwrite snapshot with empty user on app refresh)
590
+ AuthService.onAuthStateChanged((user) => {
591
+ if (!user) return;
538
592
 
539
- For maximum security, you can implement a native HybridObject (C++, Swift, or Kotlin) and pass it to Nitro. This runs directly in memory at the C++ layer.
593
+ authSnapshotItem.set({
594
+ user,
595
+ scopes: AuthService.grantedScopes,
596
+ updatedAt: Date.now(),
597
+ });
598
+ });
540
599
 
541
- ```ts
542
- import { AuthService } from "react-native-nitro-auth";
543
- // Import your native Nitro module
544
- import { KeychainStorage } from "./native/KeychainStorage";
600
+ // Keep token/expiration fields fresh in persisted snapshot
601
+ AuthService.onTokensRefreshed((tokens) => {
602
+ authSnapshotItem.set((prev) => {
603
+ if (!prev.user) return prev;
604
+
605
+ return {
606
+ ...prev,
607
+ user: {
608
+ ...prev.user,
609
+ accessToken: tokens.accessToken ?? prev.user.accessToken,
610
+ idToken: tokens.idToken ?? prev.user.idToken,
611
+ refreshToken: tokens.refreshToken ?? prev.user.refreshToken,
612
+ expirationTime: tokens.expirationTime ?? prev.user.expirationTime,
613
+ },
614
+ updatedAt: Date.now(),
615
+ };
616
+ });
617
+ });
545
618
 
546
- AuthService.setStorageAdapter(KeychainStorage);
619
+ // Clear on logout
620
+ function logout() {
621
+ AuthService.logout();
622
+ authSnapshotItem.set({
623
+ user: undefined,
624
+ scopes: [],
625
+ updatedAt: undefined,
626
+ });
627
+ }
547
628
  ```
548
629
 
549
- **Production recommendation:** If you need custom storage policies, auditability, or a different encryption model, provide your own adapter (Keychain, EncryptedSharedPreferences, or a secure JS store). See [Pluggable Storage Adapters](#pluggable-storage-adapters) above.
550
-
551
630
  ### Production Readiness
552
631
 
553
- Nitro Auth is suitable for production use:
632
+ Nitro Auth is suitable for production use when configured with provider-accurate expectations:
633
+
634
+ - **Google Sign-In**: One-Tap + legacy on Android, native on iOS, popup on web.
635
+ - **Apple Sign-In**: iOS + web only.
636
+ - **Microsoft (Azure AD / B2C)**: iOS, Android, and web with PKCE/state/nonce protections.
554
637
 
555
- - **Google Sign-In**: Full support including One-Tap / Sheet, incremental scopes, and token refresh on iOS, Android, and Web.
556
- - **Apple Sign-In**: Supported on iOS and Web (not available on Android).
557
- - **Microsoft (Azure AD / B2C)**: Login, incremental scopes, and token refresh are supported on all platforms. Uses PKCE, state, and nonce for security.
638
+ Production checklist:
558
639
 
559
- **Token storage:** By default, tokens are stored in secure platform storage on native (Keychain / EncryptedSharedPreferences) and in `localStorage` on web. On Android API < 23, storage falls back to unencrypted `SharedPreferences`. For high-security web requirements or custom storage needs, configure a [custom storage adapter](#pluggable-storage-adapters).
640
+ 1. Configure client IDs, URL schemes, and redirect URIs per platform/provider.
641
+ 2. Call `silentRestore()` during app startup to rehydrate provider session state when available.
642
+ 3. Persist only app-owned snapshots/tokens (for example with `react-native-nitro-storage`), then clear them on logout.
643
+ 4. On Android Google, use `useLegacyGoogleSignIn: true` when backend flows require `serverAuthCode`.
644
+ 5. Treat token fields as optional and branch by provider/flow.
645
+ 6. Keep web sensitive tokens memory-only unless you explicitly require persistent web tokens (`nitroAuthPersistTokensOnWeb: true`).
646
+ 7. Enable logging only in development, and monitor normalized error codes in production.
560
647
 
561
648
  ### Logging & Debugging
562
649
 
@@ -568,9 +655,9 @@ import { AuthService } from "react-native-nitro-auth";
568
655
  AuthService.setLoggingEnabled(true);
569
656
  ```
570
657
 
571
- ### Sync Access to Tokens
658
+ ### Sync State + Async Tokens
572
659
 
573
- Nitro Auth provides synchronous access to the current state, while still supporting silent refresh:
660
+ Nitro Auth provides synchronous access to in-memory state, while token retrieval remains async:
574
661
 
575
662
  ```ts
576
663
  // Quick access to what we have in memory
@@ -614,7 +701,7 @@ try {
614
701
 
615
702
  ### Native Error Metadata
616
703
 
617
- For more detailed debugging, Nitro Auth captures the raw native error message. You can access it from the authenticated user or cast the error:
704
+ For more detailed debugging, Nitro Auth captures raw provider/native details in `underlyingError` where available:
618
705
 
619
706
  ```ts
620
707
  // From authenticated user (on success)
@@ -663,54 +750,6 @@ if (user?.serverAuthCode) {
663
750
  }
664
751
  ```
665
752
 
666
- ### Custom Storage Adapter
667
-
668
- By default, Nitro Auth uses secure native storage on iOS/Android and `localStorage` on web. You can provide a custom adapter for different security or storage requirements.
669
-
670
- > [!IMPORTANT]
671
- > `AuthStorageAdapter` must be implemented as a **native Nitro HybridObject** in C++, Swift, or Kotlin. Plain JavaScript objects are not supported due to Nitro's type system. See [Nitro Hybrid Objects documentation](https://nitro.margelo.com/docs/hybrid-objects) for implementation details.
672
-
673
- **Example (Swift):**
674
-
675
- ```swift
676
- class HybridKeychainStorage: HybridAuthStorageAdapterSpec {
677
- func save(key: String, value: String) {
678
- // Save to Keychain
679
- }
680
-
681
- func load(key: String) -> String? {
682
- // Load from Keychain
683
- }
684
-
685
- func remove(key: String) {
686
- // Remove from Keychain
687
- }
688
- }
689
- ```
690
-
691
- **Usage (TypeScript):**
692
-
693
- ```ts
694
- import { NitroModules } from "react-native-nitro-modules";
695
- import { AuthService, AuthStorageAdapter } from "react-native-nitro-auth";
696
-
697
- const keychainStorage =
698
- NitroModules.createHybridObject<AuthStorageAdapter>("KeychainStorage");
699
- AuthService.setStorageAdapter(keychainStorage);
700
- ```
701
-
702
- ### Token Refresh Listeners
703
-
704
- Perfect for updating your API client (e.g., Axios/Fetch) whenever tokens are refreshed in the background:
705
-
706
- ```ts
707
- AuthService.onTokensRefreshed((tokens) => {
708
- console.log("Tokens were updated!", tokens.accessToken);
709
- apiClient.defaults.headers.common["Authorization"] =
710
- `Bearer ${tokens.accessToken}`;
711
- });
712
- ```
713
-
714
753
  ### Google One-Tap & Sheet
715
754
 
716
755
  Explicitly enable the modern One-Tap flow on Android or the Sign-In Sheet on iOS:
@@ -725,6 +764,15 @@ await login("google", {
725
764
  > [!NOTE]
726
765
  > One-Tap requires Google Play Services. You can check `hasPlayServices` from `useAuth()` and show a fallback UI if needed.
727
766
 
767
+ ### Android Legacy Google Sign-In (Server Auth Code)
768
+
769
+ Credential Manager is the recommended default on Android, but it **does not return** `serverAuthCode`.
770
+ If your backend requires `serverAuthCode`, opt into the legacy flow:
771
+
772
+ ```ts
773
+ await login("google", { useLegacyGoogleSignIn: true });
774
+ ```
775
+
728
776
  ### Force Account Picker
729
777
 
730
778
  When connecting additional services (like Google Calendar), you may want to let users pick a different account than the one they signed in with. Use `forceAccountPicker` to clear any cached session and show the account picker:
@@ -744,94 +792,231 @@ This is useful for scenarios where:
744
792
 
745
793
  ## API Reference
746
794
 
747
- ### useAuth Hook
748
-
749
- | Property | Type | Description |
750
- | ----------------- | --------------------------------- | ------------------------------------------------------ |
751
- | `user` | `AuthUser \| undefined` | Current authenticated user (includes `serverAuthCode`) |
752
- | `scopes` | `string[]` | Currently granted OAuth scopes |
753
- | `loading` | `boolean` | True during auth operations |
754
- | `error` | `Error \| undefined` | Last error that occurred |
755
- | `hasPlayServices` | `boolean` | (Android) True if Play Services available |
756
- | `login` | `(provider, options?) => Promise` | Start login flow |
757
- | `logout` | `() => void` | Clear session (synchronous) |
758
- | `silentRestore` | `() => Promise<void>` | Restore session automatically on startup |
759
- | `requestScopes` | `(scopes) => Promise` | Request additional OAuth scopes |
760
- | `revokeScopes` | `(scopes) => Promise` | Revoke previously granted scopes |
761
- | `getAccessToken` | `() => Promise<string?>` | Get current access token (auto-refreshes) |
762
- | `refreshToken` | `() => Promise<AuthTokens>` | Explicitly refresh and return new tokens |
763
-
764
- ### AuthService
765
-
766
- | Method | Type | Description |
767
- | -------------------------- | ----------------------------------------- | -------------------------------------------------- |
768
- | `login` | `(provider, options?) => Promise<void>` | Start login flow |
769
- | `logout` | `() => void` | Clear session |
770
- | `silentRestore` | `() => Promise<void>` | Restore session on startup |
771
- | `requestScopes` | `(scopes) => Promise<void>` | Request additional OAuth scopes |
772
- | `revokeScopes` | `(scopes) => Promise<void>` | Revoke previously granted scopes |
773
- | `getAccessToken` | `() => Promise<string \| undefined>` | Get current access token (auto-refreshes) |
774
- | `refreshToken` | `() => Promise<AuthTokens>` | Explicitly refresh and return new tokens |
775
- | `onAuthStateChanged` | `(callback) => () => void` | Subscribe to auth state changes |
776
- | `onTokensRefreshed` | `(callback) => () => void` | Subscribe to token refresh events |
777
- | `setLoggingEnabled` | `(enabled: boolean) => void` | Enable or disable verbose logging |
778
- | `setStorageAdapter` | `(adapter?: AuthStorageAdapter) => void` | Set native storage adapter |
779
- | `setJSStorageAdapter` | `(adapter?: JSStorageAdapter) => Promise<void>` | Set JS storage adapter |
780
-
781
- ### AuthUser
782
-
783
- | Field | Type | Description |
784
- | --------------- | -------------------- | ------------------------------------------------ |
785
- | `provider` | `"google" \| "apple" \| "microsoft"` | Provider that authenticated the user |
786
- | `email` | `string?` | User email (if provided) |
787
- | `name` | `string?` | User display name |
788
- | `photo` | `string?` | Profile image URL (Google only) |
789
- | `idToken` | `string?` | OIDC ID token |
790
- | `accessToken` | `string?` | Access token (if available) |
791
- | `serverAuthCode`| `string?` | Google server auth code (if configured) |
792
- | `scopes` | `string[]?` | Granted OAuth scopes |
793
- | `expirationTime`| `number?` | Token expiration time (ms since epoch) |
794
- | `underlyingError` | `string?` | Raw native error message |
795
-
796
- ### LoginOptions
797
-
798
- | Option | Type | Platform | Description |
799
- | -------------------- | ---------- | -------- | ----------------------------------------------- |
800
- | `scopes` | `string[]` | All | Required OAuth scopes (default: email, profile) |
801
- | `loginHint` | `string` | All | Pre-fill email address in the login picker |
802
- | `useOneTap` | `boolean` | Android | Enable Google One-Tap (Credential Manager) |
803
- | `useSheet` | `boolean` | iOS | Enable iOS Google Sign-In Sheet |
804
- | `forceAccountPicker` | `boolean` | All | Always show the account selection screen |
805
- | `tenant` | `string` | Microsoft | Azure AD tenant (`common`, `organizations`, etc.) |
806
- | `prompt` | `string` | Microsoft | Prompt behavior (`login`, `consent`, `select_account`, `none`) |
807
-
808
- ### SocialButton Props
809
-
810
- | Prop | Type | Default | Description |
811
- | -------------- | ---------------------------------------------- | ----------- | --------------------------------------------- |
812
- | `provider` | `"google" \| "apple" \| "microsoft"` | required | Authentication provider |
813
- | `variant` | `"primary" \| "outline" \| "white" \| "black"` | `"primary"` | Button style variant |
814
- | `onPress` | `() => void` | — | Custom handler (disables default login) |
815
- | `onSuccess` | `(user: AuthUser) => void` | — | Called with user data on success (auto-login) |
816
- | `onError` | `(error: unknown) => void` | — | Called on failure (auto-login) |
817
- | `disabled` | `boolean` | `false` | Disable button interaction |
818
- | `style` | `ViewStyle` | — | Custom container styles |
819
- | `textStyle` | `TextStyle` | | Custom text styles |
820
- | `borderRadius` | `number` | `8` | Button border radius |
795
+ ### Package Exports
796
+
797
+ ```ts
798
+ import {
799
+ AuthService,
800
+ SocialButton,
801
+ useAuth,
802
+ type UseAuthReturn,
803
+ type Auth,
804
+ type AuthUser,
805
+ type AuthTokens,
806
+ type AuthProvider,
807
+ type AuthErrorCode,
808
+ type LoginOptions,
809
+ } from "react-native-nitro-auth";
810
+ ```
811
+
812
+ ### Core Types
813
+
814
+ | Type | Definition |
815
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
816
+ | `AuthProvider` | `"google" \| "apple" \| "microsoft"` |
817
+ | `AuthErrorCode` | `"cancelled" \| "timeout" \| "popup_blocked" \| "network_error" \| "configuration_error" \| "unsupported_provider" \| "invalid_state" \| "invalid_nonce" \| "token_error" \| "no_id_token" \| "parse_error" \| "refresh_failed" \| "unknown"` |
818
+ | `MicrosoftPrompt` | `"login" \| "consent" \| "select_account" \| "none"` |
819
+
820
+ ### `AuthUser`
821
+
822
+ | Field | Type | Description |
823
+ | ----------------- | ----------------------- | -------------------------------------------------------------------------- |
824
+ | `provider` | `AuthProvider` | Provider that authenticated the user |
825
+ | `email` | `string \| undefined` | User email |
826
+ | `name` | `string \| undefined` | Display name |
827
+ | `photo` | `string \| undefined` | Profile image URL (Google) |
828
+ | `idToken` | `string \| undefined` | OIDC ID token |
829
+ | `accessToken` | `string \| undefined` | OAuth access token |
830
+ | `refreshToken` | `string \| undefined` | OAuth refresh token |
831
+ | `serverAuthCode` | `string \| undefined` | Google server auth code (legacy Android flow + backend exchange scenarios) |
832
+ | `scopes` | `string[] \| undefined` | Granted scopes for current session |
833
+ | `expirationTime` | `number \| undefined` | Expiration timestamp in milliseconds since epoch |
834
+ | `underlyingError` | `string \| undefined` | Raw provider/native error message |
835
+
836
+ > [!NOTE]
837
+ > On Android Google One-Tap/Credential Manager, `idToken` is typically available immediately, while `accessToken` and `expirationTime` may be `undefined` until refresh/exchange flow provides them.
838
+
839
+ ### `AuthTokens`
840
+
841
+ | Field | Type | Description |
842
+ | ---------------- | --------------------- | ----------------------------- |
843
+ | `accessToken` | `string \| undefined` | Refreshed access token |
844
+ | `idToken` | `string \| undefined` | Refreshed ID token |
845
+ | `refreshToken` | `string \| undefined` | Refresh token (if available) |
846
+ | `expirationTime` | `number \| undefined` | Optional expiration timestamp |
847
+
848
+ ### `LoginOptions`
849
+
850
+ | Option | Type | Platform | Description |
851
+ | ----------------------- | ----------------- | --------- | --------------------------------------------------------------------------------- |
852
+ | `scopes` | `string[]` | All | Requested scopes (defaults are provider-specific) |
853
+ | `loginHint` | `string` | All | Prefills account identifier |
854
+ | `useOneTap` | `boolean` | Android | Use Credential Manager/One-Tap flow |
855
+ | `useSheet` | `boolean` | iOS | Use native Google Sign-In sheet |
856
+ | `forceAccountPicker` | `boolean` | All | Always show account chooser |
857
+ | `useLegacyGoogleSignIn` | `boolean` | Android | Use legacy Google Sign-In (required when you need `serverAuthCode`) |
858
+ | `tenant` | `string` | Microsoft | Tenant (`common`, `organizations`, `consumers`, tenant id, or full authority URL) |
859
+ | `prompt` | `MicrosoftPrompt` | Microsoft | Prompt behavior |
860
+
861
+ ### `useAuth()`
862
+
863
+ ```ts
864
+ declare function useAuth(): UseAuthReturn;
865
+ ```
866
+
867
+ | Property | Type | Description |
868
+ | ----------------- | ------------------------------------------------------------------- | ---------------------------------------------------- |
869
+ | `user` | `AuthUser \| undefined` | Current in-memory user |
870
+ | `scopes` | `string[]` | Current granted scopes |
871
+ | `loading` | `boolean` | `true` while an auth operation is in-flight |
872
+ | `error` | `Error \| undefined` | Last operation error |
873
+ | `hasPlayServices` | `boolean` | Android Play Services availability |
874
+ | `login` | `(provider: AuthProvider, options?: LoginOptions) => Promise<void>` | Starts provider login |
875
+ | `logout` | `() => void` | Clears current session |
876
+ | `requestScopes` | `(scopes: string[]) => Promise<void>` | Requests additional scopes |
877
+ | `revokeScopes` | `(scopes: string[]) => Promise<void>` | Revokes scopes in current session |
878
+ | `getAccessToken` | `() => Promise<string \| undefined>` | Returns access token, auto-refreshing when supported |
879
+ | `refreshToken` | `() => Promise<AuthTokens>` | Explicit refresh |
880
+ | `silentRestore` | `() => Promise<void>` | Restores provider SDK session (if available) |
881
+
882
+ ### `AuthService`
883
+
884
+ Synchronous state + async operations (useful outside React trees).
885
+
886
+ #### Readonly state
887
+
888
+ | Property | Type | Description |
889
+ | ----------------- | ----------------------- | ---------------------------------- |
890
+ | `name` | `string` | Hybrid object name (`"Auth"`) |
891
+ | `currentUser` | `AuthUser \| undefined` | Current user snapshot |
892
+ | `grantedScopes` | `string[]` | Current scope snapshot |
893
+ | `hasPlayServices` | `boolean` | Android Play Services availability |
894
+
895
+ #### Methods
896
+
897
+ | Method | Signature | Description |
898
+ | -------------------- | -------------------------------------------------------- | -------------------------------- |
899
+ | `login` | `(provider, options?) => Promise<void>` | Starts login |
900
+ | `logout` | `() => void` | Clears session |
901
+ | `requestScopes` | `(scopes) => Promise<void>` | Incremental auth |
902
+ | `revokeScopes` | `(scopes) => Promise<void>` | Scope revoke |
903
+ | `getAccessToken` | `() => Promise<string \| undefined>` | Access token getter with refresh |
904
+ | `refreshToken` | `() => Promise<AuthTokens>` | Explicit token refresh |
905
+ | `silentRestore` | `() => Promise<void>` | Restore provider SDK session |
906
+ | `onAuthStateChanged` | `(callback: (user?: AuthUser) => void) => () => void` | Auth change listener |
907
+ | `onTokensRefreshed` | `(callback: (tokens: AuthTokens) => void) => () => void` | Token refresh listener |
908
+ | `setLoggingEnabled` | `(enabled: boolean) => void` | Debug logging toggle |
909
+ | `dispose` | `() => void` | Disposes hybrid object |
910
+ | `equals` | `(other: unknown) => boolean` | Hybrid object identity check |
911
+
912
+ ### Storage Contract
913
+
914
+ Nitro Auth does not provide persistence APIs. Persist the auth data you need in your app layer (for example with `react-native-nitro-storage`, MMKV, Keychain wrappers, or backend-issued sessions).
915
+ There is no public `setStorageAdapter`/`setJSStorageAdapter` API in this package.
916
+
917
+ ### `SocialButton`
918
+
919
+ | Prop | Type | Default | Description |
920
+ | -------------- | ---------------------------------------------- | ----------- | ------------------------------------------- |
921
+ | `provider` | `AuthProvider` | required | Provider |
922
+ | `variant` | `"primary" \| "outline" \| "white" \| "black"` | `"primary"` | Visual style |
923
+ | `borderRadius` | `number` | `8` | Border radius |
924
+ | `style` | `ViewStyle` | `undefined` | Container style override |
925
+ | `textStyle` | `TextStyle` | `undefined` | Text style override |
926
+ | `disabled` | `boolean` | `false` | Disabled state |
927
+ | `onPress` | `() => void` | `undefined` | Custom press handler (skips built-in login) |
928
+ | `onSuccess` | `(user: AuthUser) => void` | `undefined` | Called after successful default login |
929
+ | `onError` | `(error: unknown) => void` | `undefined` | Called when default login fails |
930
+
931
+ ### Config Plugin API (`app.json` / `app.config.js`)
932
+
933
+ `plugins: [["react-native-nitro-auth", { ios: {...}, android: {...} }]]`
934
+
935
+ #### iOS plugin options
936
+
937
+ | Option | Type | Description |
938
+ | ---------------------- | --------- | -------------------------------------------- |
939
+ | `googleClientId` | `string` | Writes `GIDClientID` |
940
+ | `googleServerClientId` | `string` | Writes `GIDServerClientID` |
941
+ | `googleUrlScheme` | `string` | Adds Google URL scheme |
942
+ | `appleSignIn` | `boolean` | Enables Apple Sign-In entitlement |
943
+ | `microsoftClientId` | `string` | Writes `MSALClientID` + MSAL redirect scheme |
944
+ | `microsoftTenant` | `string` | Writes `MSALTenant` |
945
+ | `microsoftB2cDomain` | `string` | Writes `MSALB2cDomain` |
946
+
947
+ #### Android plugin options
948
+
949
+ | Option | Type | Description |
950
+ | -------------------- | -------- | ---------------------------------------------------------------- |
951
+ | `googleClientId` | `string` | Writes `nitro_auth_google_client_id` string |
952
+ | `microsoftClientId` | `string` | Writes `nitro_auth_microsoft_client_id` + redirect intent filter |
953
+ | `microsoftTenant` | `string` | Writes `nitro_auth_microsoft_tenant` |
954
+ | `microsoftB2cDomain` | `string` | Writes `nitro_auth_microsoft_b2c_domain` |
955
+
956
+ ### Web runtime config (`expo.extra`)
957
+
958
+ | Key | Type | Default | Description |
959
+ | ----------------------------- | ---------------------------------- | ----------- | --------------------------------------------------------- |
960
+ | `googleWebClientId` | `string` | `undefined` | Google web OAuth client id |
961
+ | `microsoftClientId` | `string` | `undefined` | Microsoft app client id |
962
+ | `microsoftTenant` | `string` | `"common"` | Microsoft tenant/authority |
963
+ | `microsoftB2cDomain` | `string` | `undefined` | B2C domain when applicable |
964
+ | `appleWebClientId` | `string` | `undefined` | Apple Service ID |
965
+ | `nitroAuthWebStorage` | `"session" \| "local" \| "memory"` | `"session"` | Storage for non-sensitive web cache |
966
+ | `nitroAuthPersistTokensOnWeb` | `boolean` | `false` | Persist sensitive tokens on web storage instead of memory |
967
+
968
+ ### Error semantics
969
+
970
+ Errors are surfaced as `Error` with `message` as a normalized code when possible, and `underlyingError` with provider/native details.
971
+
972
+ | Normalized message | Meaning |
973
+ | --------------------- | ---------------------------------------------- |
974
+ | `cancelled` | User cancelled popup/login flow |
975
+ | `timeout` | Provider popup did not complete before timeout |
976
+ | `popup_blocked` | Browser blocked popup opening |
977
+ | `network_error` | Network failure |
978
+ | `configuration_error` | Missing/invalid provider configuration |
979
+
980
+ ## Quality Gates
981
+
982
+ From monorepo root:
983
+
984
+ ```bash
985
+ bun run format:check
986
+ bun run lint
987
+ bun run typecheck
988
+ ```
989
+
990
+ Workspace-local commands:
991
+
992
+ ```bash
993
+ # apps/example
994
+ bun run format
995
+ bun run lint
996
+ bun run typecheck
997
+
998
+ # packages/react-native-nitro-auth
999
+ bun run format
1000
+ bun run lint
1001
+ bun run typecheck
1002
+ bun run test
1003
+ ```
821
1004
 
822
1005
  ## Platform Support
823
1006
 
824
- | Feature | iOS | Android | Web |
825
- | ------------------------- | --- | ------- | --- |
826
- | Google Sign-In | ✅ | ✅ | ✅ |
827
- | Apple Sign-In | ✅ | ❌ | ✅ |
828
- | Microsoft Sign-In | ✅ | ✅ | ✅ |
829
- | Custom OAuth Scopes | ✅ | ✅ | ✅ |
830
- | Incremental Authorization | ✅ | ✅ | ✅ |
831
- | Token Refresh | ✅ | ✅ | ✅ |
832
- | Session Persistence | | | |
833
- | Auto-Refresh | | | ✅ |
834
- | Native C++ Performance | ✅ | ✅ | |
1007
+ | Feature | iOS | Android | Web |
1008
+ | -------------------------- | --- | ------- | --- |
1009
+ | Google Sign-In | ✅ | ✅ | ✅ |
1010
+ | Apple Sign-In | ✅ | ❌ | ✅ |
1011
+ | Microsoft Sign-In | ✅ | ✅ | ✅ |
1012
+ | Custom OAuth Scopes | ✅ | ✅ | ✅ |
1013
+ | Incremental Authorization | ✅ | ✅ | ✅ |
1014
+ | Token Refresh | ✅ | ✅ | ✅ |
1015
+ | Native Session Persistence | | | |
1016
+ | Web Auth Snapshot Cache | | | ✅ |
1017
+ | App-Owned Persistence | ✅ | ✅ | |
1018
+ | Auto-Refresh | ✅ | ✅ | ✅ |
1019
+ | Native C++ Performance | ✅ | ✅ | — |
835
1020
 
836
1021
  ## Architecture
837
1022