react-native-nitro-auth 0.5.1 → 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 (121) hide show
  1. package/README.md +362 -190
  2. package/android/src/main/java/com/auth/AuthAdapter.kt +55 -169
  3. package/android/src/main/java/com/auth/NitroAuthPackage.kt +1 -1
  4. package/app.plugin.js +2 -9
  5. package/cpp/AuthCache.cpp +12 -102
  6. package/cpp/HybridAuth.cpp +37 -61
  7. package/cpp/HybridAuth.hpp +2 -4
  8. package/ios/AuthAdapter.swift +21 -25
  9. package/lib/commonjs/Auth.web.js +433 -164
  10. package/lib/commonjs/Auth.web.js.map +1 -1
  11. package/lib/commonjs/index.js +0 -12
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/index.web.js +0 -12
  14. package/lib/commonjs/index.web.js.map +1 -1
  15. package/lib/commonjs/js-storage-adapter.js +2 -0
  16. package/lib/commonjs/js-storage-adapter.js.map +1 -0
  17. package/lib/commonjs/service.js +7 -84
  18. package/lib/commonjs/service.js.map +1 -1
  19. package/lib/commonjs/service.web.js +1 -5
  20. package/lib/commonjs/service.web.js.map +1 -1
  21. package/lib/commonjs/ui/social-button.js +44 -29
  22. package/lib/commonjs/ui/social-button.js.map +1 -1
  23. package/lib/commonjs/ui/social-button.web.js +44 -29
  24. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  25. package/lib/commonjs/use-auth.js +8 -2
  26. package/lib/commonjs/use-auth.js.map +1 -1
  27. package/lib/commonjs/utils/logger.js +12 -4
  28. package/lib/commonjs/utils/logger.js.map +1 -1
  29. package/lib/module/Auth.web.js +433 -164
  30. package/lib/module/Auth.web.js.map +1 -1
  31. package/lib/module/index.js +0 -1
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/index.web.js +0 -1
  34. package/lib/module/index.web.js.map +1 -1
  35. package/lib/module/js-storage-adapter.js +2 -0
  36. package/lib/module/js-storage-adapter.js.map +1 -0
  37. package/lib/module/service.js +7 -84
  38. package/lib/module/service.js.map +1 -1
  39. package/lib/module/service.web.js +1 -5
  40. package/lib/module/service.web.js.map +1 -1
  41. package/lib/module/ui/social-button.js +44 -29
  42. package/lib/module/ui/social-button.js.map +1 -1
  43. package/lib/module/ui/social-button.web.js +44 -29
  44. package/lib/module/ui/social-button.web.js.map +1 -1
  45. package/lib/module/use-auth.js +8 -2
  46. package/lib/module/use-auth.js.map +1 -1
  47. package/lib/module/utils/logger.js +12 -4
  48. package/lib/module/utils/logger.js.map +1 -1
  49. package/lib/typescript/commonjs/Auth.nitro.d.ts +3 -3
  50. package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/Auth.web.d.ts +18 -6
  52. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/index.d.ts +1 -2
  54. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/index.web.d.ts +0 -1
  56. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/js-storage-adapter.d.ts +6 -0
  58. package/lib/typescript/commonjs/js-storage-adapter.d.ts.map +1 -0
  59. package/lib/typescript/commonjs/service.d.ts +1 -8
  60. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/service.web.d.ts +1 -8
  62. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/ui/social-button.d.ts +6 -6
  64. package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
  65. package/lib/typescript/commonjs/ui/social-button.web.d.ts +6 -6
  66. package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
  67. package/lib/typescript/commonjs/use-auth.d.ts +4 -4
  68. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  69. package/lib/typescript/commonjs/utils/logger.d.ts +4 -4
  70. package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
  71. package/lib/typescript/module/Auth.nitro.d.ts +3 -3
  72. package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
  73. package/lib/typescript/module/Auth.web.d.ts +18 -6
  74. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  75. package/lib/typescript/module/index.d.ts +1 -2
  76. package/lib/typescript/module/index.d.ts.map +1 -1
  77. package/lib/typescript/module/index.web.d.ts +0 -1
  78. package/lib/typescript/module/index.web.d.ts.map +1 -1
  79. package/lib/typescript/module/js-storage-adapter.d.ts +6 -0
  80. package/lib/typescript/module/js-storage-adapter.d.ts.map +1 -0
  81. package/lib/typescript/module/service.d.ts +1 -8
  82. package/lib/typescript/module/service.d.ts.map +1 -1
  83. package/lib/typescript/module/service.web.d.ts +1 -8
  84. package/lib/typescript/module/service.web.d.ts.map +1 -1
  85. package/lib/typescript/module/ui/social-button.d.ts +6 -6
  86. package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
  87. package/lib/typescript/module/ui/social-button.web.d.ts +6 -6
  88. package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
  89. package/lib/typescript/module/use-auth.d.ts +4 -4
  90. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  91. package/lib/typescript/module/utils/logger.d.ts +4 -4
  92. package/lib/typescript/module/utils/logger.d.ts.map +1 -1
  93. package/nitrogen/generated/android/NitroAuth+autolinking.cmake +0 -1
  94. package/nitrogen/generated/shared/c++/AuthTokens.hpp +5 -1
  95. package/nitrogen/generated/shared/c++/AuthUser.hpp +5 -1
  96. package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +0 -1
  97. package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +0 -5
  98. package/package.json +11 -8
  99. package/src/Auth.nitro.ts +4 -3
  100. package/src/Auth.web.ts +582 -202
  101. package/src/global.d.ts +0 -1
  102. package/src/index.ts +1 -2
  103. package/src/index.web.ts +0 -1
  104. package/src/js-storage-adapter.ts +5 -0
  105. package/src/service.ts +11 -104
  106. package/src/service.web.ts +0 -7
  107. package/src/ui/social-button.tsx +66 -43
  108. package/src/ui/social-button.web.tsx +67 -44
  109. package/src/use-auth.ts +18 -6
  110. package/src/utils/logger.ts +12 -4
  111. package/lib/commonjs/AuthStorage.nitro.js +0 -6
  112. package/lib/commonjs/AuthStorage.nitro.js.map +0 -1
  113. package/lib/module/AuthStorage.nitro.js +0 -4
  114. package/lib/module/AuthStorage.nitro.js.map +0 -1
  115. package/lib/typescript/commonjs/AuthStorage.nitro.d.ts +0 -26
  116. package/lib/typescript/commonjs/AuthStorage.nitro.d.ts.map +0 -1
  117. package/lib/typescript/module/AuthStorage.nitro.d.ts +0 -26
  118. package/lib/typescript/module/AuthStorage.nitro.d.ts.map +0 -1
  119. package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.cpp +0 -23
  120. package/nitrogen/generated/shared/c++/HybridAuthStorageAdapterSpec.hpp +0 -65
  121. 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,37 +91,45 @@ 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**
@@ -297,7 +338,9 @@ Nitro Auth reads web configuration from `expo.extra`:
297
338
  "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
298
339
  "microsoftTenant": "common",
299
340
  "microsoftB2cDomain": "your-tenant.b2clogin.com",
300
- "appleWebClientId": "com.example.web"
341
+ "appleWebClientId": "com.example.web",
342
+ "nitroAuthWebStorage": "session",
343
+ "nitroAuthPersistTokensOnWeb": false
301
344
  }
302
345
  }
303
346
  }
@@ -305,6 +348,9 @@ Nitro Auth reads web configuration from `expo.extra`:
305
348
 
306
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.
307
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
+
308
354
  ## Quick Start
309
355
 
310
356
  ### Using the Hook
@@ -453,7 +499,11 @@ If you previously called `GoogleSignin.configure()` at app startup, remove it. N
453
499
 
454
500
  ### Silent Restore
455
501
 
456
- 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.
457
507
 
458
508
  ```tsx
459
509
  useEffect(() => {
@@ -510,56 +560,90 @@ const handleCalendar = async () => {
510
560
  };
511
561
  ```
512
562
 
513
- ### Pluggable Storage Adapters
514
-
515
- 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
516
564
 
517
- #### 1) JS Storage (AsyncStorage, MMKV, etc.)
565
+ Nitro Auth is intentionally stateless in-process. Persist only what your app needs.
518
566
 
519
- Easily swap the default storage with your preferred library from the JS layer:
567
+ #### Using react-native-nitro-storage (Recommended)
520
568
 
521
569
  ```ts
522
- import { AuthService, type JSStorageAdapter } from "react-native-nitro-auth";
523
- import { MMKV } from "react-native-mmkv";
570
+ import { AuthService, type AuthUser } from "react-native-nitro-auth";
571
+ import { createStorageItem, StorageScope } from "react-native-nitro-storage";
524
572
 
525
- const storage = new MMKV();
526
-
527
- const mmkvAdapter: JSStorageAdapter = {
528
- save: (key, value) => storage.set(key, value),
529
- load: (key) => storage.getString(key),
530
- remove: (key) => storage.delete(key),
573
+ type AuthSnapshot = {
574
+ user: AuthUser | undefined;
575
+ scopes: string[];
576
+ updatedAt: number | undefined;
531
577
  };
532
578
 
533
- // Set it once at app startup
534
- AuthService.setJSStorageAdapter(mmkvAdapter);
535
- ```
536
-
537
- > [!NOTE]
538
- > 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
+ });
539
588
 
540
- #### 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;
541
592
 
542
- 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
+ });
543
599
 
544
- ```ts
545
- import { AuthService } from "react-native-nitro-auth";
546
- // Import your native Nitro module
547
- 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
+ });
548
618
 
549
- 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
+ }
550
628
  ```
551
629
 
552
- **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.
553
-
554
630
  ### Production Readiness
555
631
 
556
- Nitro Auth is suitable for production use:
632
+ Nitro Auth is suitable for production use when configured with provider-accurate expectations:
557
633
 
558
- - **Google Sign-In**: Full support including One-Tap / Sheet, incremental scopes, and token refresh on iOS, Android, and Web.
559
- - **Apple Sign-In**: Supported on iOS and Web (not available on Android).
560
- - **Microsoft (Azure AD / B2C)**: Login, incremental scopes, and token refresh are supported on all platforms. Uses PKCE, state, and nonce for security.
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.
561
637
 
562
- **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).
638
+ Production checklist:
639
+
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.
563
647
 
564
648
  ### Logging & Debugging
565
649
 
@@ -571,9 +655,9 @@ import { AuthService } from "react-native-nitro-auth";
571
655
  AuthService.setLoggingEnabled(true);
572
656
  ```
573
657
 
574
- ### Sync Access to Tokens
658
+ ### Sync State + Async Tokens
575
659
 
576
- 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:
577
661
 
578
662
  ```ts
579
663
  // Quick access to what we have in memory
@@ -617,7 +701,7 @@ try {
617
701
 
618
702
  ### Native Error Metadata
619
703
 
620
- 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:
621
705
 
622
706
  ```ts
623
707
  // From authenticated user (on success)
@@ -666,54 +750,6 @@ if (user?.serverAuthCode) {
666
750
  }
667
751
  ```
668
752
 
669
- ### Custom Storage Adapter
670
-
671
- 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.
672
-
673
- > [!IMPORTANT]
674
- > `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.
675
-
676
- **Example (Swift):**
677
-
678
- ```swift
679
- class HybridKeychainStorage: HybridAuthStorageAdapterSpec {
680
- func save(key: String, value: String) {
681
- // Save to Keychain
682
- }
683
-
684
- func load(key: String) -> String? {
685
- // Load from Keychain
686
- }
687
-
688
- func remove(key: String) {
689
- // Remove from Keychain
690
- }
691
- }
692
- ```
693
-
694
- **Usage (TypeScript):**
695
-
696
- ```ts
697
- import { NitroModules } from "react-native-nitro-modules";
698
- import { AuthService, AuthStorageAdapter } from "react-native-nitro-auth";
699
-
700
- const keychainStorage =
701
- NitroModules.createHybridObject<AuthStorageAdapter>("KeychainStorage");
702
- AuthService.setStorageAdapter(keychainStorage);
703
- ```
704
-
705
- ### Token Refresh Listeners
706
-
707
- Perfect for updating your API client (e.g., Axios/Fetch) whenever tokens are refreshed in the background:
708
-
709
- ```ts
710
- AuthService.onTokensRefreshed((tokens) => {
711
- console.log("Tokens were updated!", tokens.accessToken);
712
- apiClient.defaults.headers.common["Authorization"] =
713
- `Bearer ${tokens.accessToken}`;
714
- });
715
- ```
716
-
717
753
  ### Google One-Tap & Sheet
718
754
 
719
755
  Explicitly enable the modern One-Tap flow on Android or the Sign-In Sheet on iOS:
@@ -756,95 +792,231 @@ This is useful for scenarios where:
756
792
 
757
793
  ## API Reference
758
794
 
759
- ### useAuth Hook
760
-
761
- | Property | Type | Description |
762
- | ----------------- | --------------------------------- | ------------------------------------------------------ |
763
- | `user` | `AuthUser \| undefined` | Current authenticated user (includes `serverAuthCode`) |
764
- | `scopes` | `string[]` | Currently granted OAuth scopes |
765
- | `loading` | `boolean` | True during auth operations |
766
- | `error` | `Error \| undefined` | Last error that occurred |
767
- | `hasPlayServices` | `boolean` | (Android) True if Play Services available |
768
- | `login` | `(provider, options?) => Promise` | Start login flow |
769
- | `logout` | `() => void` | Clear session (synchronous) |
770
- | `silentRestore` | `() => Promise<void>` | Restore session automatically on startup |
771
- | `requestScopes` | `(scopes) => Promise` | Request additional OAuth scopes |
772
- | `revokeScopes` | `(scopes) => Promise` | Revoke previously granted scopes |
773
- | `getAccessToken` | `() => Promise<string?>` | Get current access token (auto-refreshes) |
774
- | `refreshToken` | `() => Promise<AuthTokens>` | Explicitly refresh and return new tokens |
775
-
776
- ### AuthService
777
-
778
- | Method | Type | Description |
779
- | -------------------------- | ----------------------------------------- | -------------------------------------------------- |
780
- | `login` | `(provider, options?) => Promise<void>` | Start login flow |
781
- | `logout` | `() => void` | Clear session |
782
- | `silentRestore` | `() => Promise<void>` | Restore session on startup |
783
- | `requestScopes` | `(scopes) => Promise<void>` | Request additional OAuth scopes |
784
- | `revokeScopes` | `(scopes) => Promise<void>` | Revoke previously granted scopes |
785
- | `getAccessToken` | `() => Promise<string \| undefined>` | Get current access token (auto-refreshes) |
786
- | `refreshToken` | `() => Promise<AuthTokens>` | Explicitly refresh and return new tokens |
787
- | `onAuthStateChanged` | `(callback) => () => void` | Subscribe to auth state changes |
788
- | `onTokensRefreshed` | `(callback) => () => void` | Subscribe to token refresh events |
789
- | `setLoggingEnabled` | `(enabled: boolean) => void` | Enable or disable verbose logging |
790
- | `setStorageAdapter` | `(adapter?: AuthStorageAdapter) => void` | Set native storage adapter |
791
- | `setJSStorageAdapter` | `(adapter?: JSStorageAdapter) => Promise<void>` | Set JS storage adapter |
792
-
793
- ### AuthUser
794
-
795
- | Field | Type | Description |
796
- | --------------- | -------------------- | ------------------------------------------------ |
797
- | `provider` | `"google" \| "apple" \| "microsoft"` | Provider that authenticated the user |
798
- | `email` | `string?` | User email (if provided) |
799
- | `name` | `string?` | User display name |
800
- | `photo` | `string?` | Profile image URL (Google only) |
801
- | `idToken` | `string?` | OIDC ID token |
802
- | `accessToken` | `string?` | Access token (if available) |
803
- | `serverAuthCode`| `string?` | Google server auth code (if configured) |
804
- | `scopes` | `string[]?` | Granted OAuth scopes |
805
- | `expirationTime`| `number?` | Token expiration time (ms since epoch) |
806
- | `underlyingError` | `string?` | Raw native error message |
807
-
808
- ### LoginOptions
809
-
810
- | Option | Type | Platform | Description |
811
- | -------------------- | ---------- | -------- | ----------------------------------------------- |
812
- | `scopes` | `string[]` | All | Required OAuth scopes (default: email, profile) |
813
- | `loginHint` | `string` | All | Pre-fill email address in the login picker |
814
- | `useOneTap` | `boolean` | Android | Enable Google One-Tap (Credential Manager) |
815
- | `useSheet` | `boolean` | iOS | Enable iOS Google Sign-In Sheet |
816
- | `forceAccountPicker` | `boolean` | All | Always show the account selection screen |
817
- | `useLegacyGoogleSignIn` | `boolean` | Android | Use legacy Google Sign-In (supports `serverAuthCode`) |
818
- | `tenant` | `string` | Microsoft | Azure AD tenant (`common`, `organizations`, etc.) |
819
- | `prompt` | `string` | Microsoft | Prompt behavior (`login`, `consent`, `select_account`, `none`) |
820
-
821
- ### SocialButton Props
822
-
823
- | Prop | Type | Default | Description |
824
- | -------------- | ---------------------------------------------- | ----------- | --------------------------------------------- |
825
- | `provider` | `"google" \| "apple" \| "microsoft"` | required | Authentication provider |
826
- | `variant` | `"primary" \| "outline" \| "white" \| "black"` | `"primary"` | Button style variant |
827
- | `onPress` | `() => void` | — | Custom handler (disables default login) |
828
- | `onSuccess` | `(user: AuthUser) => void` | — | Called with user data on success (auto-login) |
829
- | `onError` | `(error: unknown) => void` | — | Called on failure (auto-login) |
830
- | `disabled` | `boolean` | `false` | Disable button interaction |
831
- | `style` | `ViewStyle` | | Custom container styles |
832
- | `textStyle` | `TextStyle` | | Custom text styles |
833
- | `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
+ ```
834
1004
 
835
1005
  ## Platform Support
836
1006
 
837
- | Feature | iOS | Android | Web |
838
- | ------------------------- | --- | ------- | --- |
839
- | Google Sign-In | ✅ | ✅ | ✅ |
840
- | Apple Sign-In | ✅ | ❌ | ✅ |
841
- | Microsoft Sign-In | ✅ | ✅ | ✅ |
842
- | Custom OAuth Scopes | ✅ | ✅ | ✅ |
843
- | Incremental Authorization | ✅ | ✅ | ✅ |
844
- | Token Refresh | ✅ | ✅ | ✅ |
845
- | Session Persistence | | | |
846
- | Auto-Refresh | | | ✅ |
847
- | 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 | ✅ | ✅ | — |
848
1020
 
849
1021
  ## Architecture
850
1022