react-native-nitro-auth 0.3.0 → 0.5.0

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 (100) hide show
  1. package/README.md +602 -57
  2. package/android/build.gradle +8 -2
  3. package/android/gradle.properties +2 -2
  4. package/android/src/main/cpp/JniOnLoad.cpp +1 -0
  5. package/android/src/main/cpp/PlatformAuth+Android.cpp +37 -4
  6. package/android/src/main/java/com/auth/AuthAdapter.kt +586 -69
  7. package/android/src/main/java/com/auth/GoogleSignInActivity.kt +17 -4
  8. package/android/src/main/java/com/auth/MicrosoftAuthActivity.kt +25 -0
  9. package/app.plugin.js +115 -5
  10. package/cpp/AuthCache.cpp +72 -19
  11. package/cpp/HybridAuth.cpp +20 -0
  12. package/cpp/HybridAuth.hpp +1 -0
  13. package/ios/AuthAdapter.swift +470 -53
  14. package/ios/KeychainStore.swift +43 -0
  15. package/ios/PlatformAuth+iOS.mm +34 -3
  16. package/lib/commonjs/Auth.web.js +262 -10
  17. package/lib/commonjs/Auth.web.js.map +1 -1
  18. package/lib/commonjs/index.js +7 -11
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/commonjs/service.js +130 -1
  21. package/lib/commonjs/service.js.map +1 -1
  22. package/lib/commonjs/service.web.js +32 -6
  23. package/lib/commonjs/service.web.js.map +1 -1
  24. package/lib/commonjs/ui/social-button.js +46 -8
  25. package/lib/commonjs/ui/social-button.js.map +1 -1
  26. package/lib/commonjs/ui/social-button.web.js +46 -8
  27. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  28. package/lib/commonjs/use-auth.js +27 -2
  29. package/lib/commonjs/use-auth.js.map +1 -1
  30. package/lib/commonjs/utils/logger.js +1 -1
  31. package/lib/commonjs/utils/logger.js.map +1 -1
  32. package/lib/module/Auth.web.js +262 -10
  33. package/lib/module/Auth.web.js.map +1 -1
  34. package/lib/module/index.js +1 -1
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/service.js +130 -1
  37. package/lib/module/service.js.map +1 -1
  38. package/lib/module/service.web.js +32 -1
  39. package/lib/module/service.web.js.map +1 -1
  40. package/lib/module/ui/social-button.js +47 -9
  41. package/lib/module/ui/social-button.js.map +1 -1
  42. package/lib/module/ui/social-button.web.js +47 -9
  43. package/lib/module/ui/social-button.web.js.map +1 -1
  44. package/lib/module/use-auth.js +27 -2
  45. package/lib/module/use-auth.js.map +1 -1
  46. package/lib/module/utils/logger.js +1 -1
  47. package/lib/module/utils/logger.js.map +1 -1
  48. package/lib/typescript/commonjs/Auth.nitro.d.ts +10 -2
  49. package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/Auth.web.d.ts +9 -1
  51. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/AuthStorage.nitro.d.ts +7 -0
  53. package/lib/typescript/commonjs/AuthStorage.nitro.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/index.d.ts +2 -2
  55. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/service.d.ts +8 -1
  57. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/service.web.d.ts +25 -1
  59. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/use-auth.d.ts +13 -8
  63. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/utils/logger.d.ts +5 -5
  65. package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
  66. package/lib/typescript/module/Auth.nitro.d.ts +10 -2
  67. package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
  68. package/lib/typescript/module/Auth.web.d.ts +9 -1
  69. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  70. package/lib/typescript/module/AuthStorage.nitro.d.ts +7 -0
  71. package/lib/typescript/module/AuthStorage.nitro.d.ts.map +1 -1
  72. package/lib/typescript/module/index.d.ts +2 -2
  73. package/lib/typescript/module/index.d.ts.map +1 -1
  74. package/lib/typescript/module/service.d.ts +8 -1
  75. package/lib/typescript/module/service.d.ts.map +1 -1
  76. package/lib/typescript/module/service.web.d.ts +25 -1
  77. package/lib/typescript/module/service.web.d.ts.map +1 -1
  78. package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
  79. package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
  80. package/lib/typescript/module/use-auth.d.ts +13 -8
  81. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  82. package/lib/typescript/module/utils/logger.d.ts +5 -5
  83. package/lib/typescript/module/utils/logger.d.ts.map +1 -1
  84. package/nitrogen/generated/shared/c++/AuthProvider.hpp +4 -0
  85. package/nitrogen/generated/shared/c++/HybridAuthSpec.cpp +1 -0
  86. package/nitrogen/generated/shared/c++/HybridAuthSpec.hpp +1 -0
  87. package/nitrogen/generated/shared/c++/LoginOptions.hpp +17 -3
  88. package/nitrogen/generated/shared/c++/MicrosoftPrompt.hpp +84 -0
  89. package/package.json +13 -10
  90. package/react-native-nitro-auth.podspec +4 -2
  91. package/src/Auth.nitro.ts +17 -2
  92. package/src/Auth.web.ts +388 -22
  93. package/src/AuthStorage.nitro.ts +11 -2
  94. package/src/index.ts +2 -2
  95. package/src/service.ts +168 -2
  96. package/src/service.web.ts +41 -1
  97. package/src/ui/social-button.tsx +34 -4
  98. package/src/ui/social-button.web.tsx +34 -4
  99. package/src/use-auth.ts +37 -3
  100. package/src/utils/logger.ts +5 -5
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  🚀 **High-performance, JSI-powered Authentication for React Native.**
8
8
 
9
- Nitro Auth is a modern authentication library for React Native built on top of [Nitro Modules](https://github.com/mrousavy/nitro). It provides a unified, type-safe API for Google and Apple Sign-In with zero-bridge overhead.
9
+ Nitro Auth is a modern authentication library for React Native built on top of [Nitro Modules](https://github.com/mrousavy/nitro). It provides a unified, type-safe API for Google, Apple, and Microsoft Sign-In with zero-bridge overhead.
10
10
 
11
11
  ## Why Nitro Auth?
12
12
 
@@ -29,19 +29,75 @@ Nitro Auth is designed to replace legacy modules like `@react-native-google-sign
29
29
  - **Auto-Refresh**: Synchronous access to tokens with automatic silent refresh.
30
30
  - **Google One-Tap / Sheet**: Modern login experience on Android (Credential Manager) and iOS (Sign-In Sheet).
31
31
  - **Error Metadata**: Detailed native error messages for easier debugging.
32
- - **Custom Storage**: Pluggable storage adapters for secure persistence (e.g., Keychain).
32
+ - **Custom Storage**: Pluggable storage adapters for secure persistence (e.g. Keychain, MMKV, AsyncStorage).
33
33
  - **Refresh Interceptors**: Listen to token updates globally.
34
34
 
35
35
  ## Installation
36
36
 
37
37
  ```bash
38
38
  bun add react-native-nitro-auth react-native-nitro-modules
39
- bun prebuild
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
40
45
  ```
41
46
 
47
+ For Expo projects, rebuild native code after installation:
48
+
49
+ ```bash
50
+ bunx expo prebuild
51
+ ```
52
+
53
+ ### Testing locally (example app + Microsoft login)
54
+
55
+ Fastest way to confirm the package and Microsoft login work:
56
+
57
+ 1. **Azure app (one-time)**
58
+ In [Azure Portal](https://portal.azure.com) → **Azure Active Directory** → **App registrations** → **New registration**:
59
+ - Name: e.g. `Nitro Auth Example`
60
+ - 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>`
63
+ - **iOS**: `msauth.com.auth.example://auth` (use your bundle id)
64
+ - 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**.
66
+
67
+ 2. **Env file**
68
+ From the repo root:
69
+ ```bash
70
+ cd apps/example
71
+ cp .env.example .env.local
72
+ ```
73
+ Edit `.env.local` and set at least:
74
+ ```bash
75
+ MICROSOFT_CLIENT_ID=<your-application-client-id>
76
+ MICROSOFT_TENANT=common
77
+ ```
78
+ (Google/Apple can stay placeholder if you only care about Microsoft.)
79
+
80
+ 3. **Run the app**
81
+ From the **monorepo root**:
82
+ ```bash
83
+ bun install
84
+ bun run start
85
+ ```
86
+ In a second terminal:
87
+ ```bash
88
+ bun run example:android
89
+ # or
90
+ bun run example:ios
91
+ ```
92
+ Wait for the app to install and open.
93
+
94
+ 4. **Test Microsoft**
95
+ 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
+ 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
+
42
98
  ### Expo Setup
43
99
 
44
- Add the plugin to `app.json`:
100
+ Add the plugin to `app.json` or `app.config.js`:
45
101
 
46
102
  ```json
47
103
  {
@@ -54,26 +110,197 @@ Add the plugin to `app.json`:
54
110
  "googleClientId": "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com",
55
111
  "googleServerClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
56
112
  "googleUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID",
57
- "appleSignIn": true
113
+ "appleSignIn": true,
114
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
115
+ "microsoftTenant": "common",
116
+ "microsoftB2cDomain": "your-tenant.b2clogin.com"
58
117
  },
59
118
  "android": {
60
- "googleClientId": "YOUR_ANDROID_CLIENT_ID.apps.googleusercontent.com"
119
+ "googleClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
120
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
121
+ "microsoftTenant": "common",
122
+ "microsoftB2cDomain": "your-tenant.b2clogin.com"
61
123
  }
62
124
  }
63
125
  ]
64
- ]
126
+ ],
127
+ "extra": {
128
+ "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
129
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
130
+ "microsoftTenant": "common",
131
+ "microsoftB2cDomain": "your-tenant.b2clogin.com",
132
+ "appleWebClientId": "com.example.web"
133
+ }
65
134
  }
66
135
  }
67
136
  ```
68
137
 
69
- > [!NOTE] > `appleSignIn` on iOS is `false` by default to avoid unnecessary entitlements. Set it to `true` to enable Apple Sign-In.
70
- > `googleServerClientId` is only required if you need a `serverAuthCode` for backend integration.
138
+ **Using environment variables (recommended):**
139
+
140
+ Create a `.env.local` file:
141
+
142
+ ```bash
143
+ # iOS Client ID
144
+ GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com
145
+ GOOGLE_IOS_URL_SCHEME=com.googleusercontent.apps.your-ios-client-id
146
+ GOOGLE_SERVER_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
147
+
148
+ # Web Client ID (used for Android OAuth flow)
149
+ GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
150
+
151
+ # Microsoft/Azure AD (optional)
152
+ MICROSOFT_CLIENT_ID=your-azure-ad-application-id
153
+ MICROSOFT_TENANT=common
154
+ MICROSOFT_B2C_DOMAIN=your-tenant.b2clogin.com
155
+
156
+ # Apple (web only)
157
+ APPLE_WEB_CLIENT_ID=com.example.web
158
+ ```
159
+
160
+ Then reference them in `app.config.js`:
161
+
162
+ ```javascript
163
+ import "dotenv/config";
164
+
165
+ export default {
166
+ expo: {
167
+ plugins: [
168
+ [
169
+ "react-native-nitro-auth",
170
+ {
171
+ ios: {
172
+ googleClientId: process.env.GOOGLE_IOS_CLIENT_ID,
173
+ googleServerClientId: process.env.GOOGLE_SERVER_CLIENT_ID,
174
+ googleUrlScheme: process.env.GOOGLE_IOS_URL_SCHEME,
175
+ appleSignIn: true,
176
+ microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
177
+ microsoftTenant: process.env.MICROSOFT_TENANT,
178
+ microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
179
+ },
180
+ android: {
181
+ googleClientId: process.env.GOOGLE_WEB_CLIENT_ID,
182
+ microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
183
+ microsoftTenant: process.env.MICROSOFT_TENANT,
184
+ microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
185
+ },
186
+ },
187
+ ],
188
+ ],
189
+ extra: {
190
+ googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID,
191
+ microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
192
+ microsoftTenant: process.env.MICROSOFT_TENANT,
193
+ microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
194
+ appleWebClientId: process.env.APPLE_WEB_CLIENT_ID,
195
+ },
196
+ },
197
+ };
198
+ ```
199
+
200
+ > [!NOTE]
201
+ >
202
+ > - `appleSignIn` on iOS is `false` by default to avoid unnecessary entitlements. Set it to `true` to enable Apple Sign-In.
203
+ > - For Android, use your **Web Client ID** (not Android Client ID) for proper OAuth flow.
204
+ > - If you need `serverAuthCode`, set `googleServerClientId` to your Web Client ID.
205
+ > - Add `googleWebClientId` to `expo.extra` for web platform support.
206
+ > - The `serverAuthCode` is automatically included in `AuthUser` when available (requires backend integration setup in Google Cloud Console).
207
+ > - For Microsoft Sign-In, use `common` tenant for multi-tenant apps, or specify your Azure AD tenant ID for single-tenant apps.
208
+ > - For Azure AD B2C, set `microsoftB2cDomain` and pass the B2C tenant in `microsoftTenant`.
209
+
210
+ ### Google OAuth Setup
211
+
212
+ 1. Create OAuth client IDs in Google Cloud Console:
213
+ - **iOS client ID** (used by iOS)
214
+ - **Web client ID** (used by Android and for `serverAuthCode`)
215
+ 2. Configure your app:
216
+ - Expo: set `googleClientId`, `googleServerClientId`, and `googleUrlScheme`
217
+ - Bare iOS: add `GIDClientID`, `GIDServerClientID`, and URL scheme in `Info.plist`
218
+ - Bare Android: set `nitro_auth_google_client_id` to your **Web client ID**
219
+ 3. If you use `serverAuthCode`, make sure OAuth consent screen is configured in Google Cloud.
220
+
221
+ ### Apple Sign-In Setup
222
+
223
+ 1. **iOS**: enable the “Sign in with Apple” capability in Xcode and in your Apple Developer account.
224
+ 2. **Web**: create a Service ID and configure the domain + return URL in Apple Developer.
225
+ 3. Configure your app:
226
+ - Expo: set `appleSignIn: true` for iOS.
227
+ - Web: set `appleWebClientId` in `expo.extra` (or `.env`).
228
+
229
+ ### Microsoft Azure AD Setup
230
+
231
+ To enable Microsoft Sign-In, you need to register an application in the Azure Portal:
232
+
233
+ 1. Go to [Azure Portal](https://portal.azure.com) > Azure Active Directory > App registrations
234
+ 2. Click "New registration"
235
+ 3. Set the redirect URIs:
236
+ - **iOS**: `msauth.{bundle-identifier}://auth` (e.g., `msauth.com.myapp://auth`)
237
+ - **Android**: `msauth://{package-name}/{client-id}` (e.g., `msauth://com.myapp/00000000-0000-0000-0000-000000000000`)
238
+ - **Web**: `https://your-domain.com` (the page that loads the app)
239
+ 4. Under "API permissions", add `openid`, `email`, `profile`, and `User.Read` (Microsoft Graph)
240
+ 5. Copy the Application (client) ID for use in your config
241
+
242
+ **Tenant Options:**
243
+
244
+ - `common` - Any Azure AD or personal Microsoft account
245
+ - `organizations` - Any Azure AD account (work/school)
246
+ - `consumers` - Personal Microsoft accounts only
247
+ - `{tenant-id}` - Specific Azure AD tenant
248
+ - **B2C**: set `microsoftB2cDomain` (e.g. `your-tenant.b2clogin.com`) and use a tenant value like `your-tenant.onmicrosoft.com/B2C_1_signin` (or pass a full `https://.../` authority URL).
71
249
 
72
250
  ### Bare React Native
73
251
 
74
- **iOS:** Add `GIDClientID` (and optionally `GIDServerClientID`) to `Info.plist` and enable "Sign in with Apple" capability.
252
+ **iOS**
253
+
254
+ - Add to `Info.plist`: `GIDClientID`, `GIDServerClientID` (optional), `MSALClientID`, `MSALTenant` (optional), `MSALB2cDomain` (optional).
255
+ - Add URL schemes in `Info.plist`:
256
+ - Google: `com.googleusercontent.apps.<YOUR_IOS_CLIENT_ID>`
257
+ - Microsoft: `msauth.<your.bundle.id>` (used for `msauth.<bundle.id>://auth`)
258
+ - Enable the “Sign in with Apple” capability if you use Apple Sign-In.
259
+
260
+ **Android**
261
+
262
+ - Add string resources in `res/values/strings.xml`:
263
+ - `nitro_auth_google_client_id` (Web client ID)
264
+ - `nitro_auth_microsoft_client_id`
265
+ - `nitro_auth_microsoft_tenant` (optional)
266
+ - `nitro_auth_microsoft_b2c_domain` (optional)
267
+ - Add the Microsoft redirect activity to `AndroidManifest.xml`:
268
+
269
+ ```xml
270
+ <activity
271
+ android:name="com.auth.MicrosoftAuthActivity"
272
+ android:exported="true">
273
+ <intent-filter>
274
+ <action android:name="android.intent.action.VIEW" />
275
+ <category android:name="android.intent.category.DEFAULT" />
276
+ <category android:name="android.intent.category.BROWSABLE" />
277
+ <data
278
+ android:scheme="msauth"
279
+ android:host="${applicationId}"
280
+ android:path="/YOUR_MICROSOFT_CLIENT_ID" />
281
+ </intent-filter>
282
+ </activity>
283
+ ```
284
+
285
+ ### Web Setup
286
+
287
+ Nitro Auth reads web configuration from `expo.extra`:
288
+
289
+ ```json
290
+ {
291
+ "expo": {
292
+ "extra": {
293
+ "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
294
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
295
+ "microsoftTenant": "common",
296
+ "microsoftB2cDomain": "your-tenant.b2clogin.com",
297
+ "appleWebClientId": "com.example.web"
298
+ }
299
+ }
300
+ }
301
+ ```
75
302
 
76
- **Android:** Add `nitro_auth_google_client_id` string resource in `res/values/strings.xml`.
303
+ 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.
77
304
 
78
305
  ## Quick Start
79
306
 
@@ -105,13 +332,132 @@ function LoginScreen() {
105
332
  onPress={() => login("google")}
106
333
  disabled={loading || !hasPlayServices}
107
334
  />
335
+ <SocialButton
336
+ provider="apple"
337
+ onPress={() => login("apple")}
338
+ disabled={loading}
339
+ />
340
+ <SocialButton
341
+ provider="microsoft"
342
+ onPress={() => login("microsoft")}
343
+ disabled={loading}
344
+ />
108
345
  </View>
109
346
  );
110
347
  }
111
348
  ```
112
349
 
350
+ ### Microsoft Login Options
351
+
352
+ ```tsx
353
+ // Login with specific tenant
354
+ await login("microsoft", {
355
+ tenant: "your-tenant-id",
356
+ prompt: "select_account", // 'login' | 'consent' | 'select_account' | 'none'
357
+ scopes: ["openid", "email", "profile", "User.Read"],
358
+ loginHint: "user@example.com",
359
+ });
360
+ ```
361
+
362
+ **B2C example:**
363
+
364
+ ```tsx
365
+ await login("microsoft", {
366
+ tenant: "your-tenant.onmicrosoft.com/B2C_1_signin",
367
+ scopes: ["openid", "email", "profile", "offline_access"],
368
+ });
369
+ ```
370
+
371
+ ## Migration from @react-native-google-signin/google-signin
372
+
373
+ If you are using `@react-native-google-signin/google-signin`, the migration to Nitro Auth is mostly a drop-in at the API level, but the setup is different because Nitro Auth uses a config plugin and JSI.
374
+
375
+ ### 1) Replace the dependency
376
+
377
+ ```bash
378
+ bun remove @react-native-google-signin/google-signin
379
+ bun add react-native-nitro-auth react-native-nitro-modules
380
+ ```
381
+
382
+ ### 2) Move configuration to the Nitro Auth plugin
383
+
384
+ Nitro Auth does not use `GoogleSignin.configure(...)`. Instead, set your client IDs via the config plugin (Expo) or native config (bare).
385
+
386
+ **Expo** (recommended):
387
+
388
+ ```json
389
+ {
390
+ "expo": {
391
+ "plugins": [
392
+ [
393
+ "react-native-nitro-auth",
394
+ {
395
+ "ios": {
396
+ "googleClientId": "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com",
397
+ "googleUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID"
398
+ },
399
+ "android": {
400
+ "googleClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com"
401
+ }
402
+ }
403
+ ]
404
+ ],
405
+ "extra": {
406
+ "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com"
407
+ }
408
+ }
409
+ }
410
+ ```
411
+
412
+ **Bare React Native:**
413
+
414
+ - iOS: add `GIDClientID` (and optionally `GIDServerClientID`) to `Info.plist` and set the URL scheme.
415
+ - Android: add `nitro_auth_google_client_id` string resource in `res/values/strings.xml` (use your Web Client ID).
416
+
417
+ ### 3) Update API usage
418
+
419
+ | @react-native-google-signin/google-signin | Nitro Auth |
420
+ | ----------------------------------------- | --------------------------------------------------------- |
421
+ | `GoogleSignin.configure({...})` | Configure in plugin / native config |
422
+ | `GoogleSignin.signIn()` | `login("google")` or `<SocialButton provider="google" />` |
423
+ | `GoogleSignin.signOut()` | `logout()` |
424
+ | `GoogleSignin.getTokens()` | `getAccessToken()` or `refreshToken()` |
425
+ | `GoogleSignin.hasPlayServices()` | `hasPlayServices` from `useAuth()` |
426
+
427
+ **Example migration:**
428
+
429
+ ```tsx
430
+ // Before
431
+ import { GoogleSignin } from "@react-native-google-signin/google-signin";
432
+
433
+ await GoogleSignin.signIn();
434
+ const tokens = await GoogleSignin.getTokens();
435
+
436
+ // After
437
+ import { useAuth } from "react-native-nitro-auth";
438
+
439
+ const { login, getAccessToken } = useAuth();
440
+
441
+ await login("google");
442
+ const accessToken = await getAccessToken();
443
+ ```
444
+
445
+ ### 4) Remove manual init
446
+
447
+ If you previously called `GoogleSignin.configure()` at app startup, remove it. Nitro Auth loads configuration from the plugin/native settings at runtime.
448
+
113
449
  ## Advanced Features
114
450
 
451
+ ### Silent Restore
452
+
453
+ Automatically restore the user session on app startup. This is faster than a full login and works offline if the session is cached.
454
+
455
+ ```tsx
456
+ useEffect(() => {
457
+ AuthService.silentRestore();
458
+ }, []);
459
+ ```
460
+
115
461
  ### Global Auth State Listener
116
462
 
117
463
  Subscribe to authentication changes outside of React components:
@@ -131,40 +477,168 @@ const unsubscribe = AuthService.onAuthStateChanged((user) => {
131
477
  unsubscribe();
132
478
  ```
133
479
 
480
+ ### Global Token Refresh Listener
481
+
482
+ Be notified whenever tokens are refreshed automatically (or manually):
483
+
484
+ ```ts
485
+ import { AuthService } from "react-native-nitro-auth";
486
+
487
+ const unsubscribe = AuthService.onTokensRefreshed((tokens) => {
488
+ console.log("New tokens:", tokens.accessToken);
489
+ // Update your API client / Apollo links
490
+ });
491
+ ```
492
+
493
+ ### Incremental Authorization
494
+
495
+ Request new scopes when you need them without logging the user out:
496
+
497
+ ```tsx
498
+ const { requestScopes, revokeScopes, scopes } = useAuth();
499
+
500
+ const handleCalendar = async () => {
501
+ try {
502
+ await requestScopes(["https://www.googleapis.com/auth/calendar.readonly"]);
503
+ console.log("Got calendar access!");
504
+ } catch (e) {
505
+ console.error("Scope request failed");
506
+ }
507
+ };
508
+ ```
509
+
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.
513
+
514
+ #### 1) JS Storage (AsyncStorage, MMKV, etc.)
515
+
516
+ Easily swap the default storage with your preferred library from the JS layer:
517
+
518
+ ```ts
519
+ import { AuthService, type JSStorageAdapter } from "react-native-nitro-auth";
520
+ import { MMKV } from "react-native-mmkv";
521
+
522
+ const storage = new MMKV();
523
+
524
+ const mmkvAdapter: JSStorageAdapter = {
525
+ save: (key, value) => storage.set(key, value),
526
+ load: (key) => storage.getString(key),
527
+ remove: (key) => storage.delete(key),
528
+ };
529
+
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.
536
+
537
+ #### 2) Native Storage (Keychain, etc.)
538
+
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.
540
+
541
+ ```ts
542
+ import { AuthService } from "react-native-nitro-auth";
543
+ // Import your native Nitro module
544
+ import { KeychainStorage } from "./native/KeychainStorage";
545
+
546
+ AuthService.setStorageAdapter(KeychainStorage);
547
+ ```
548
+
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
+ ### Production Readiness
552
+
553
+ Nitro Auth is suitable for production use:
554
+
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.
558
+
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).
560
+
561
+ ### Logging & Debugging
562
+
563
+ Enable verbose logging to see detailed OAuth flow information in the console:
564
+
565
+ ```ts
566
+ import { AuthService } from "react-native-nitro-auth";
567
+
568
+ AuthService.setLoggingEnabled(true);
569
+ ```
570
+
571
+ ### Sync Access to Tokens
572
+
573
+ Nitro Auth provides synchronous access to the current state, while still supporting silent refresh:
574
+
575
+ ```ts
576
+ // Quick access to what we have in memory
577
+ const user = AuthService.currentUser;
578
+ const scopes = AuthService.grantedScopes;
579
+
580
+ // Async access ensures fresh tokens (will refresh if expired)
581
+ const freshToken = await AuthService.getAccessToken();
582
+ ```
583
+
134
584
  ### Standardized Error Codes
135
585
 
136
- Handle failures reliably with predictable error strings:
586
+ Handle failures reliably with predictable error strings. Some flows can surface provider-specific codes (listed below):
137
587
 
138
588
  ```ts
139
589
  try {
140
590
  await login("google");
141
591
  } catch (e) {
142
- if (e.message === "cancelled") {
592
+ const error = e as Error;
593
+ if (error.message === "cancelled") {
143
594
  // User closed the popup/picker
144
- } else if (e.message === "network_error") {
595
+ } else if (error.message === "network_error") {
145
596
  // Connection issues
146
597
  }
147
598
  }
148
599
  ```
149
600
 
150
- | `cancelled` | The user cancelled the sign-in flow |
151
- | `network_error` | A network error occurred |
152
- | `configuration_error` | Missing client IDs or invalid setup |
601
+ | Error Code | Description |
602
+ | ---------------------- | ---------------------------------------------- |
603
+ | `cancelled` | The user cancelled the sign-in flow |
604
+ | `network_error` | A network error occurred |
605
+ | `configuration_error` | Missing client IDs or invalid setup |
153
606
  | `unsupported_provider` | The provider is not supported on this platform |
607
+ | `invalid_state` | PKCE state mismatch (possible CSRF) |
608
+ | `invalid_nonce` | Nonce mismatch in token response |
609
+ | `token_error` | Token exchange failed |
610
+ | `no_id_token` | No `id_token` in token response |
611
+ | `parse_error` | Failed to parse token response |
612
+ | `refresh_failed` | Refresh token flow failed |
613
+ | `unknown` | An unknown error occurred |
154
614
 
155
615
  ### Native Error Metadata
156
616
 
157
- For more detailed debugging, Nitro Auth captures the raw native error message in the `underlyingError` property of the `AuthUser` (on success) or surfaces it in the caught `Error` object:
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:
158
618
 
159
619
  ```ts
620
+ // From authenticated user (on success)
621
+ const { user } = useAuth();
622
+ if (user?.underlyingError) {
623
+ console.warn("Auth warning:", user.underlyingError);
624
+ }
625
+
626
+ // From error (on failure)
160
627
  try {
161
628
  await login("google");
162
629
  } catch (e) {
163
- // Access raw native error message
164
- console.log(e.underlyingError);
630
+ const error = e as Error & { underlyingError?: string };
631
+ console.log("Native error:", error.underlyingError);
165
632
  }
166
633
  ```
167
634
 
635
+ ### Troubleshooting
636
+
637
+ - `configuration_error`: verify client IDs, URL schemes, and redirect URIs are set for the current platform.
638
+ - `invalid_state` or `invalid_nonce`: ensure the redirect URI in your provider console matches your app config exactly.
639
+ - `hasPlayServices` is false: prompt the user to install/update Google Play Services or disable One-Tap.
640
+ - Apple web login fails: confirm `appleWebClientId` is set and your domain is registered with Apple.
641
+
168
642
  ### Automatic Token Refresh
169
643
 
170
644
  The `getAccessToken()` method automatically checks if the current token is expired (or about to expire) and triggers a silent refresh if possible:
@@ -176,20 +650,6 @@ const { getAccessToken } = useAuth();
176
650
  const token = await getAccessToken();
177
651
  ```
178
652
 
179
- ### Incremental Authorization
180
-
181
- Add more scopes after initial login — no need to re-authenticate:
182
-
183
- ```tsx
184
- const { requestScopes, revokeScopes, scopes } = useAuth();
185
-
186
- // Request additional scope
187
- await requestScopes(["https://www.googleapis.com/auth/calendar.readonly"]);
188
-
189
- // Check granted scopes
190
- console.log("Granted:", scopes);
191
- ```
192
-
193
653
  ### Offline Access (Server Auth Code)
194
654
 
195
655
  If you need to access Google APIs from your backend (e.g., Google Calendar integration), you can use the `serverAuthCode`. This code is returned during login and can be exchanged for tokens on your server:
@@ -205,24 +665,38 @@ if (user?.serverAuthCode) {
205
665
 
206
666
  ### Custom Storage Adapter
207
667
 
208
- By default, Nitro Auth uses standard local storage. You can provide a custom adapter for better security (e.g., using `react-native-keychain`):
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):**
209
692
 
210
693
  ```ts
694
+ import { NitroModules } from "react-native-nitro-modules";
211
695
  import { AuthService, AuthStorageAdapter } from "react-native-nitro-auth";
212
696
 
213
- const myStorage: AuthStorageAdapter = {
214
- save: (key, value) => {
215
- /* Save to Keychain */
216
- },
217
- load: (key) => {
218
- /* Load from Keychain */
219
- },
220
- remove: (key) => {
221
- /* Clear from Keychain */
222
- },
223
- };
224
-
225
- AuthService.setStorageAdapter(myStorage);
697
+ const keychainStorage =
698
+ NitroModules.createHybridObject<AuthStorageAdapter>("KeychainStorage");
699
+ AuthService.setStorageAdapter(keychainStorage);
226
700
  ```
227
701
 
228
702
  ### Token Refresh Listeners
@@ -232,9 +706,8 @@ Perfect for updating your API client (e.g., Axios/Fetch) whenever tokens are ref
232
706
  ```ts
233
707
  AuthService.onTokensRefreshed((tokens) => {
234
708
  console.log("Tokens were updated!", tokens.accessToken);
235
- apiClient.defaults.headers.common[
236
- "Authorization"
237
- ] = `Bearer ${tokens.accessToken}`;
709
+ apiClient.defaults.headers.common["Authorization"] =
710
+ `Bearer ${tokens.accessToken}`;
238
711
  });
239
712
  ```
240
713
 
@@ -249,6 +722,26 @@ await login("google", {
249
722
  });
250
723
  ```
251
724
 
725
+ > [!NOTE]
726
+ > One-Tap requires Google Play Services. You can check `hasPlayServices` from `useAuth()` and show a fallback UI if needed.
727
+
728
+ ### Force Account Picker
729
+
730
+ 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:
731
+
732
+ ```ts
733
+ await login("google", {
734
+ scopes: ["https://www.googleapis.com/auth/calendar.readonly"],
735
+ forceAccountPicker: true, // Always show account picker
736
+ });
737
+ ```
738
+
739
+ This is useful for scenarios where:
740
+
741
+ - Users want to connect a different Google account for calendar integration
742
+ - You need to ensure the user can select any account they've added to their device
743
+ - The cached session is interfering with the expected account selection UX
744
+
252
745
  ## API Reference
253
746
 
254
747
  ### useAuth Hook
@@ -262,18 +755,69 @@ await login("google", {
262
755
  | `hasPlayServices` | `boolean` | (Android) True if Play Services available |
263
756
  | `login` | `(provider, options?) => Promise` | Start login flow |
264
757
  | `logout` | `() => void` | Clear session (synchronous) |
758
+ | `silentRestore` | `() => Promise<void>` | Restore session automatically on startup |
265
759
  | `requestScopes` | `(scopes) => Promise` | Request additional OAuth scopes |
760
+ | `revokeScopes` | `(scopes) => Promise` | Revoke previously granted scopes |
266
761
  | `getAccessToken` | `() => Promise<string?>` | Get current access token (auto-refreshes) |
267
762
  | `refreshToken` | `() => Promise<AuthTokens>` | Explicitly refresh and return new tokens |
268
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
+
269
808
  ### SocialButton Props
270
809
 
271
- | Prop | Type | Default | Description |
272
- | ----------- | ---------------------------------------------- | ----------- | -------------------------------- |
273
- | `provider` | `"google" \| "apple"` | required | Authentication provider |
274
- | `variant` | `"primary" \| "outline" \| "white" \| "black"` | `"primary"` | Button style variant |
275
- | `onSuccess` | `(user: AuthUser) => void` | — | Called with user data on success |
276
- | `onError` | `(error: Error) => void` | — | Called on failure |
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 |
277
821
 
278
822
  ## Platform Support
279
823
 
@@ -281,6 +825,7 @@ await login("google", {
281
825
  | ------------------------- | --- | ------- | --- |
282
826
  | Google Sign-In | ✅ | ✅ | ✅ |
283
827
  | Apple Sign-In | ✅ | ❌ | ✅ |
828
+ | Microsoft Sign-In | ✅ | ✅ | ✅ |
284
829
  | Custom OAuth Scopes | ✅ | ✅ | ✅ |
285
830
  | Incremental Authorization | ✅ | ✅ | ✅ |
286
831
  | Token Refresh | ✅ | ✅ | ✅ |