react-native-nitro-auth 0.4.0 → 0.5.1

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 (86) hide show
  1. package/README.md +288 -28
  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 +626 -78
  7. package/android/src/main/java/com/auth/GoogleSignInActivity.kt +3 -4
  8. package/android/src/main/java/com/auth/MicrosoftAuthActivity.kt +25 -0
  9. package/android/src/main/java/com/auth/NitroAuthPackage.kt +2 -0
  10. package/app.plugin.js +113 -3
  11. package/cpp/AuthCache.cpp +72 -19
  12. package/ios/AuthAdapter.swift +457 -52
  13. package/ios/KeychainStore.swift +43 -0
  14. package/ios/PlatformAuth+iOS.mm +29 -3
  15. package/lib/commonjs/Auth.web.js +246 -7
  16. package/lib/commonjs/Auth.web.js.map +1 -1
  17. package/lib/commonjs/index.js +7 -11
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/commonjs/service.js.map +1 -1
  20. package/lib/commonjs/service.web.js +0 -8
  21. package/lib/commonjs/service.web.js.map +1 -1
  22. package/lib/commonjs/ui/social-button.js +12 -2
  23. package/lib/commonjs/ui/social-button.js.map +1 -1
  24. package/lib/commonjs/ui/social-button.web.js +12 -2
  25. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  26. package/lib/commonjs/use-auth.js.map +1 -1
  27. package/lib/commonjs/utils/logger.js +1 -1
  28. package/lib/commonjs/utils/logger.js.map +1 -1
  29. package/lib/module/Auth.web.js +246 -7
  30. package/lib/module/Auth.web.js.map +1 -1
  31. package/lib/module/index.js +1 -1
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/service.js.map +1 -1
  34. package/lib/module/service.web.js +0 -8
  35. package/lib/module/service.web.js.map +1 -1
  36. package/lib/module/ui/social-button.js +12 -2
  37. package/lib/module/ui/social-button.js.map +1 -1
  38. package/lib/module/ui/social-button.web.js +12 -2
  39. package/lib/module/ui/social-button.web.js.map +1 -1
  40. package/lib/module/use-auth.js.map +1 -1
  41. package/lib/module/utils/logger.js +1 -1
  42. package/lib/module/utils/logger.js.map +1 -1
  43. package/lib/typescript/commonjs/Auth.nitro.d.ts +9 -2
  44. package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/Auth.web.d.ts +7 -0
  46. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/index.d.ts +1 -1
  48. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/service.web.d.ts +2 -6
  51. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/use-auth.d.ts +12 -8
  55. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/utils/logger.d.ts +5 -5
  57. package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
  58. package/lib/typescript/module/Auth.nitro.d.ts +9 -2
  59. package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
  60. package/lib/typescript/module/Auth.web.d.ts +7 -0
  61. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  62. package/lib/typescript/module/index.d.ts +1 -1
  63. package/lib/typescript/module/index.d.ts.map +1 -1
  64. package/lib/typescript/module/service.d.ts.map +1 -1
  65. package/lib/typescript/module/service.web.d.ts +2 -6
  66. package/lib/typescript/module/service.web.d.ts.map +1 -1
  67. package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
  68. package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
  69. package/lib/typescript/module/use-auth.d.ts +12 -8
  70. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  71. package/lib/typescript/module/utils/logger.d.ts +5 -5
  72. package/lib/typescript/module/utils/logger.d.ts.map +1 -1
  73. package/nitrogen/generated/shared/c++/AuthProvider.hpp +4 -0
  74. package/nitrogen/generated/shared/c++/LoginOptions.hpp +17 -3
  75. package/nitrogen/generated/shared/c++/MicrosoftPrompt.hpp +84 -0
  76. package/package.json +6 -4
  77. package/react-native-nitro-auth.podspec +4 -2
  78. package/src/Auth.nitro.ts +15 -1
  79. package/src/Auth.web.ts +350 -7
  80. package/src/index.ts +1 -1
  81. package/src/service.ts +4 -3
  82. package/src/service.web.ts +3 -12
  83. package/src/ui/social-button.tsx +10 -2
  84. package/src/ui/social-button.web.tsx +10 -2
  85. package/src/use-auth.ts +12 -1
  86. 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
 
@@ -50,6 +50,54 @@ For Expo projects, rebuild native code after installation:
50
50
  bunx expo prebuild
51
51
  ```
52
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
+
98
+ > [!TIP]
99
+ > In the example app on Android, you can toggle **Legacy Google Sign-In** to compare Credential Manager vs legacy GoogleSignIn (and to get `serverAuthCode`).
100
+
53
101
  ### Expo Setup
54
102
 
55
103
  Add the plugin to `app.json` or `app.config.js`:
@@ -63,17 +111,28 @@ Add the plugin to `app.json` or `app.config.js`:
63
111
  {
64
112
  "ios": {
65
113
  "googleClientId": "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com",
114
+ "googleServerClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
66
115
  "googleUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID",
67
- "appleSignIn": true
116
+ "appleSignIn": true,
117
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
118
+ "microsoftTenant": "common",
119
+ "microsoftB2cDomain": "your-tenant.b2clogin.com"
68
120
  },
69
121
  "android": {
70
- "googleClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com"
122
+ "googleClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
123
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
124
+ "microsoftTenant": "common",
125
+ "microsoftB2cDomain": "your-tenant.b2clogin.com"
71
126
  }
72
127
  }
73
128
  ]
74
129
  ],
75
130
  "extra": {
76
- "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com"
131
+ "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
132
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
133
+ "microsoftTenant": "common",
134
+ "microsoftB2cDomain": "your-tenant.b2clogin.com",
135
+ "appleWebClientId": "com.example.web"
77
136
  }
78
137
  }
79
138
  }
@@ -87,9 +146,18 @@ Create a `.env.local` file:
87
146
  # iOS Client ID
88
147
  GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com
89
148
  GOOGLE_IOS_URL_SCHEME=com.googleusercontent.apps.your-ios-client-id
149
+ GOOGLE_SERVER_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
90
150
 
91
151
  # Web Client ID (used for Android OAuth flow)
92
152
  GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
153
+
154
+ # Microsoft/Azure AD (optional)
155
+ MICROSOFT_CLIENT_ID=your-azure-ad-application-id
156
+ MICROSOFT_TENANT=common
157
+ MICROSOFT_B2C_DOMAIN=your-tenant.b2clogin.com
158
+
159
+ # Apple (web only)
160
+ APPLE_WEB_CLIENT_ID=com.example.web
93
161
  ```
94
162
 
95
163
  Then reference them in `app.config.js`:
@@ -105,17 +173,28 @@ export default {
105
173
  {
106
174
  ios: {
107
175
  googleClientId: process.env.GOOGLE_IOS_CLIENT_ID,
176
+ googleServerClientId: process.env.GOOGLE_SERVER_CLIENT_ID,
108
177
  googleUrlScheme: process.env.GOOGLE_IOS_URL_SCHEME,
109
178
  appleSignIn: true,
179
+ microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
180
+ microsoftTenant: process.env.MICROSOFT_TENANT,
181
+ microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
110
182
  },
111
183
  android: {
112
184
  googleClientId: process.env.GOOGLE_WEB_CLIENT_ID,
185
+ microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
186
+ microsoftTenant: process.env.MICROSOFT_TENANT,
187
+ microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
113
188
  },
114
189
  },
115
190
  ],
116
191
  ],
117
192
  extra: {
118
193
  googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID,
194
+ microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
195
+ microsoftTenant: process.env.MICROSOFT_TENANT,
196
+ microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
197
+ appleWebClientId: process.env.APPLE_WEB_CLIENT_ID,
119
198
  },
120
199
  },
121
200
  };
@@ -125,14 +204,106 @@ export default {
125
204
  >
126
205
  > - `appleSignIn` on iOS is `false` by default to avoid unnecessary entitlements. Set it to `true` to enable Apple Sign-In.
127
206
  > - For Android, use your **Web Client ID** (not Android Client ID) for proper OAuth flow.
207
+ > - If you need `serverAuthCode`, set `googleServerClientId` to your Web Client ID.
128
208
  > - Add `googleWebClientId` to `expo.extra` for web platform support.
129
209
  > - The `serverAuthCode` is automatically included in `AuthUser` when available (requires backend integration setup in Google Cloud Console).
210
+ > - For Microsoft Sign-In, use `common` tenant for multi-tenant apps, or specify your Azure AD tenant ID for single-tenant apps.
211
+ > - For Azure AD B2C, set `microsoftB2cDomain` and pass the B2C tenant in `microsoftTenant`.
212
+
213
+ ### Google OAuth Setup
214
+
215
+ 1. Create OAuth client IDs in Google Cloud Console:
216
+ - **iOS client ID** (used by iOS)
217
+ - **Web client ID** (used by Android and for `serverAuthCode`)
218
+ 2. Configure your app:
219
+ - Expo: set `googleClientId`, `googleServerClientId`, and `googleUrlScheme`
220
+ - Bare iOS: add `GIDClientID`, `GIDServerClientID`, and URL scheme in `Info.plist`
221
+ - Bare Android: set `nitro_auth_google_client_id` to your **Web client ID**
222
+ 3. If you use `serverAuthCode`, make sure OAuth consent screen is configured in Google Cloud.
223
+
224
+ ### Apple Sign-In Setup
225
+
226
+ 1. **iOS**: enable the “Sign in with Apple” capability in Xcode and in your Apple Developer account.
227
+ 2. **Web**: create a Service ID and configure the domain + return URL in Apple Developer.
228
+ 3. Configure your app:
229
+ - Expo: set `appleSignIn: true` for iOS.
230
+ - Web: set `appleWebClientId` in `expo.extra` (or `.env`).
231
+
232
+ ### Microsoft Azure AD Setup
233
+
234
+ To enable Microsoft Sign-In, you need to register an application in the Azure Portal:
235
+
236
+ 1. Go to [Azure Portal](https://portal.azure.com) > Azure Active Directory > App registrations
237
+ 2. Click "New registration"
238
+ 3. Set the redirect URIs:
239
+ - **iOS**: `msauth.{bundle-identifier}://auth` (e.g., `msauth.com.myapp://auth`)
240
+ - **Android**: `msauth://{package-name}/{client-id}` (e.g., `msauth://com.myapp/00000000-0000-0000-0000-000000000000`)
241
+ - **Web**: `https://your-domain.com` (the page that loads the app)
242
+ 4. Under "API permissions", add `openid`, `email`, `profile`, and `User.Read` (Microsoft Graph)
243
+ 5. Copy the Application (client) ID for use in your config
244
+
245
+ **Tenant Options:**
246
+
247
+ - `common` - Any Azure AD or personal Microsoft account
248
+ - `organizations` - Any Azure AD account (work/school)
249
+ - `consumers` - Personal Microsoft accounts only
250
+ - `{tenant-id}` - Specific Azure AD tenant
251
+ - **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).
130
252
 
131
253
  ### Bare React Native
132
254
 
133
- **iOS:** Add `GIDClientID` (and optionally `GIDServerClientID`) to `Info.plist` and enable "Sign in with Apple" capability.
255
+ **iOS**
256
+
257
+ - Add to `Info.plist`: `GIDClientID`, `GIDServerClientID` (optional), `MSALClientID`, `MSALTenant` (optional), `MSALB2cDomain` (optional).
258
+ - Add URL schemes in `Info.plist`:
259
+ - Google: `com.googleusercontent.apps.<YOUR_IOS_CLIENT_ID>`
260
+ - Microsoft: `msauth.<your.bundle.id>` (used for `msauth.<bundle.id>://auth`)
261
+ - Enable the “Sign in with Apple” capability if you use Apple Sign-In.
262
+
263
+ **Android**
264
+
265
+ - Add string resources in `res/values/strings.xml`:
266
+ - `nitro_auth_google_client_id` (Web client ID)
267
+ - `nitro_auth_microsoft_client_id`
268
+ - `nitro_auth_microsoft_tenant` (optional)
269
+ - `nitro_auth_microsoft_b2c_domain` (optional)
270
+ - Add the Microsoft redirect activity to `AndroidManifest.xml`:
271
+
272
+ ```xml
273
+ <activity
274
+ android:name="com.auth.MicrosoftAuthActivity"
275
+ android:exported="true">
276
+ <intent-filter>
277
+ <action android:name="android.intent.action.VIEW" />
278
+ <category android:name="android.intent.category.DEFAULT" />
279
+ <category android:name="android.intent.category.BROWSABLE" />
280
+ <data
281
+ android:scheme="msauth"
282
+ android:host="${applicationId}"
283
+ android:path="/YOUR_MICROSOFT_CLIENT_ID" />
284
+ </intent-filter>
285
+ </activity>
286
+ ```
287
+
288
+ ### Web Setup
289
+
290
+ Nitro Auth reads web configuration from `expo.extra`:
134
291
 
135
- **Android:** Add `nitro_auth_google_client_id` string resource in `res/values/strings.xml`.
292
+ ```json
293
+ {
294
+ "expo": {
295
+ "extra": {
296
+ "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
297
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
298
+ "microsoftTenant": "common",
299
+ "microsoftB2cDomain": "your-tenant.b2clogin.com",
300
+ "appleWebClientId": "com.example.web"
301
+ }
302
+ }
303
+ }
304
+ ```
305
+
306
+ 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.
136
307
 
137
308
  ## Quick Start
138
309
 
@@ -164,11 +335,42 @@ function LoginScreen() {
164
335
  onPress={() => login("google")}
165
336
  disabled={loading || !hasPlayServices}
166
337
  />
338
+ <SocialButton
339
+ provider="apple"
340
+ onPress={() => login("apple")}
341
+ disabled={loading}
342
+ />
343
+ <SocialButton
344
+ provider="microsoft"
345
+ onPress={() => login("microsoft")}
346
+ disabled={loading}
347
+ />
167
348
  </View>
168
349
  );
169
350
  }
170
351
  ```
171
352
 
353
+ ### Microsoft Login Options
354
+
355
+ ```tsx
356
+ // Login with specific tenant
357
+ await login("microsoft", {
358
+ tenant: "your-tenant-id",
359
+ prompt: "select_account", // 'login' | 'consent' | 'select_account' | 'none'
360
+ scopes: ["openid", "email", "profile", "User.Read"],
361
+ loginHint: "user@example.com",
362
+ });
363
+ ```
364
+
365
+ **B2C example:**
366
+
367
+ ```tsx
368
+ await login("microsoft", {
369
+ tenant: "your-tenant.onmicrosoft.com/B2C_1_signin",
370
+ scopes: ["openid", "email", "profile", "offline_access"],
371
+ });
372
+ ```
373
+
172
374
  ## Migration from @react-native-google-signin/google-signin
173
375
 
174
376
  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.
@@ -310,7 +512,7 @@ const handleCalendar = async () => {
310
512
 
311
513
  ### Pluggable Storage Adapters
312
514
 
313
- Nitro Auth persists the session automatically. By default, it uses simple file-based storage on native and `localStorage` on web.
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.
314
516
 
315
517
  #### 1) JS Storage (AsyncStorage, MMKV, etc.)
316
518
 
@@ -332,6 +534,9 @@ const mmkvAdapter: JSStorageAdapter = {
332
534
  AuthService.setJSStorageAdapter(mmkvAdapter);
333
535
  ```
334
536
 
537
+ > [!NOTE]
538
+ > Call `setJSStorageAdapter` before your first `useAuth()` or `AuthService` call so cached values are loaded before UI renders.
539
+
335
540
  #### 2) Native Storage (Keychain, etc.)
336
541
 
337
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.
@@ -344,6 +549,18 @@ import { KeychainStorage } from "./native/KeychainStorage";
344
549
  AuthService.setStorageAdapter(KeychainStorage);
345
550
  ```
346
551
 
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
+ ### Production Readiness
555
+
556
+ Nitro Auth is suitable for production use:
557
+
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.
561
+
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).
563
+
347
564
  ### Logging & Debugging
348
565
 
349
566
  Enable verbose logging to see detailed OAuth flow information in the console:
@@ -369,7 +586,7 @@ const freshToken = await AuthService.getAccessToken();
369
586
 
370
587
  ### Standardized Error Codes
371
588
 
372
- Handle failures reliably with predictable error strings:
589
+ Handle failures reliably with predictable error strings. Some flows can surface provider-specific codes (listed below):
373
590
 
374
591
  ```ts
375
592
  try {
@@ -390,6 +607,12 @@ try {
390
607
  | `network_error` | A network error occurred |
391
608
  | `configuration_error` | Missing client IDs or invalid setup |
392
609
  | `unsupported_provider` | The provider is not supported on this platform |
610
+ | `invalid_state` | PKCE state mismatch (possible CSRF) |
611
+ | `invalid_nonce` | Nonce mismatch in token response |
612
+ | `token_error` | Token exchange failed |
613
+ | `no_id_token` | No `id_token` in token response |
614
+ | `parse_error` | Failed to parse token response |
615
+ | `refresh_failed` | Refresh token flow failed |
393
616
  | `unknown` | An unknown error occurred |
394
617
 
395
618
  ### Native Error Metadata
@@ -412,6 +635,13 @@ try {
412
635
  }
413
636
  ```
414
637
 
638
+ ### Troubleshooting
639
+
640
+ - `configuration_error`: verify client IDs, URL schemes, and redirect URIs are set for the current platform.
641
+ - `invalid_state` or `invalid_nonce`: ensure the redirect URI in your provider console matches your app config exactly.
642
+ - `hasPlayServices` is false: prompt the user to install/update Google Play Services or disable One-Tap.
643
+ - Apple web login fails: confirm `appleWebClientId` is set and your domain is registered with Apple.
644
+
415
645
  ### Automatic Token Refresh
416
646
 
417
647
  The `getAccessToken()` method automatically checks if the current token is expired (or about to expire) and triggers a silent refresh if possible:
@@ -423,23 +653,6 @@ const { getAccessToken } = useAuth();
423
653
  const token = await getAccessToken();
424
654
  ```
425
655
 
426
- ### Incremental Authorization
427
-
428
- Add more scopes after initial login — no need to re-authenticate:
429
-
430
- ```tsx
431
- const { requestScopes, revokeScopes, scopes } = useAuth();
432
-
433
- // Request additional scope
434
- await requestScopes(["https://www.googleapis.com/auth/calendar.readonly"]);
435
-
436
- // Check granted scopes
437
- console.log("Granted:", scopes);
438
-
439
- // Revoke specific scopes
440
- await revokeScopes(["https://www.googleapis.com/auth/calendar.readonly"]);
441
- ```
442
-
443
656
  ### Offline Access (Server Auth Code)
444
657
 
445
658
  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:
@@ -455,7 +668,7 @@ if (user?.serverAuthCode) {
455
668
 
456
669
  ### Custom Storage Adapter
457
670
 
458
- By default, Nitro Auth uses standard local storage. You can provide a custom adapter for better security.
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.
459
672
 
460
673
  > [!IMPORTANT]
461
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.
@@ -512,6 +725,18 @@ await login("google", {
512
725
  });
513
726
  ```
514
727
 
728
+ > [!NOTE]
729
+ > One-Tap requires Google Play Services. You can check `hasPlayServices` from `useAuth()` and show a fallback UI if needed.
730
+
731
+ ### Android Legacy Google Sign-In (Server Auth Code)
732
+
733
+ Credential Manager is the recommended default on Android, but it **does not return** `serverAuthCode`.
734
+ If your backend requires `serverAuthCode`, opt into the legacy flow:
735
+
736
+ ```ts
737
+ await login("google", { useLegacyGoogleSignIn: true });
738
+ ```
739
+
515
740
  ### Force Account Picker
516
741
 
517
742
  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:
@@ -548,6 +773,38 @@ This is useful for scenarios where:
548
773
  | `getAccessToken` | `() => Promise<string?>` | Get current access token (auto-refreshes) |
549
774
  | `refreshToken` | `() => Promise<AuthTokens>` | Explicitly refresh and return new tokens |
550
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
+
551
808
  ### LoginOptions
552
809
 
553
810
  | Option | Type | Platform | Description |
@@ -557,13 +814,15 @@ This is useful for scenarios where:
557
814
  | `useOneTap` | `boolean` | Android | Enable Google One-Tap (Credential Manager) |
558
815
  | `useSheet` | `boolean` | iOS | Enable iOS Google Sign-In Sheet |
559
816
  | `forceAccountPicker` | `boolean` | All | Always show the account selection screen |
560
- | `webClientId` | `string` | Web | Override the default Google Web Client ID |
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`) |
561
820
 
562
821
  ### SocialButton Props
563
822
 
564
823
  | Prop | Type | Default | Description |
565
824
  | -------------- | ---------------------------------------------- | ----------- | --------------------------------------------- |
566
- | `provider` | `"google" \| "apple"` | required | Authentication provider |
825
+ | `provider` | `"google" \| "apple" \| "microsoft"` | required | Authentication provider |
567
826
  | `variant` | `"primary" \| "outline" \| "white" \| "black"` | `"primary"` | Button style variant |
568
827
  | `onPress` | `() => void` | — | Custom handler (disables default login) |
569
828
  | `onSuccess` | `(user: AuthUser) => void` | — | Called with user data on success (auto-login) |
@@ -579,6 +838,7 @@ This is useful for scenarios where:
579
838
  | ------------------------- | --- | ------- | --- |
580
839
  | Google Sign-In | ✅ | ✅ | ✅ |
581
840
  | Apple Sign-In | ✅ | ❌ | ✅ |
841
+ | Microsoft Sign-In | ✅ | ✅ | ✅ |
582
842
  | Custom OAuth Scopes | ✅ | ✅ | ✅ |
583
843
  | Incremental Authorization | ✅ | ✅ | ✅ |
584
844
  | Token Refresh | ✅ | ✅ | ✅ |
@@ -89,17 +89,23 @@ repositories {
89
89
 
90
90
  dependencies {
91
91
  implementation "com.facebook.react:react-native:+"
92
- implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.24"
92
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.0"
93
93
  implementation project(":react-native-nitro-modules")
94
94
 
95
95
  // Google Sign-In SDK (full scope support)
96
- implementation "com.google.android.gms:play-services-auth:21.5.0"
96
+ implementation "com.google.android.gms:play-services-auth:21.2.0"
97
97
 
98
98
  // Activity result APIs
99
99
  implementation "androidx.activity:activity-ktx:1.9.3"
100
100
 
101
+ // Custom Tabs (Microsoft OAuth in-app browser)
102
+ implementation "androidx.browser:browser:1.8.0"
103
+
101
104
  // Google Credential Manager (One-Tap / Passkeys)
102
105
  implementation "androidx.credentials:credentials:1.5.0"
103
106
  implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
104
107
  implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
108
+
109
+ // Secure storage (EncryptedSharedPreferences)
110
+ implementation "androidx.security:security-crypto:1.0.0"
105
111
  }
@@ -1,4 +1,4 @@
1
1
  NitroAuth_ndkVersion=27.1.12297006
2
- NitroAuth_compileSdkVersion=36
3
- NitroAuth_targetSdkVersion=36
2
+ NitroAuth_compileSdkVersion=35
3
+ NitroAuth_targetSdkVersion=35
4
4
  NitroAuth_minSdkVersion=24
@@ -3,5 +3,6 @@
3
3
  #include "NitroAuthOnLoad.hpp"
4
4
 
5
5
  extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
6
+ (void)reserved;
6
7
  return margelo::nitro::NitroAuth::initialize(vm);
7
8
  }
@@ -2,6 +2,7 @@
2
2
  #include "AuthUser.hpp"
3
3
  #include "AuthTokens.hpp"
4
4
  #include "AuthCache.hpp"
5
+ #include "MicrosoftPrompt.hpp"
5
6
  #include <fbjni/fbjni.h>
6
7
  #include <NitroModules/NitroLogger.hpp>
7
8
  #include <NitroModules/Promise.hpp>
@@ -37,17 +38,36 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
37
38
  gLoginPromise = promise;
38
39
  }
39
40
 
40
- std::string providerStr = (provider == AuthProvider::GOOGLE) ? "google" : "apple";
41
+ std::string providerStr;
42
+ switch (provider) {
43
+ case AuthProvider::GOOGLE: providerStr = "google"; break;
44
+ case AuthProvider::APPLE: providerStr = "apple"; break;
45
+ case AuthProvider::MICROSOFT: providerStr = "microsoft"; break;
46
+ }
47
+
41
48
  std::vector<std::string> scopes = {"email", "profile"};
42
49
  std::optional<std::string> loginHint;
50
+ std::optional<std::string> tenant;
51
+ std::optional<std::string> prompt;
43
52
  bool useOneTap = false;
44
53
  bool forceAccountPicker = false;
54
+ bool useLegacyGoogleSignIn = false;
45
55
 
46
56
  if (options) {
47
57
  if (options->scopes) scopes = *options->scopes;
48
58
  loginHint = options->loginHint;
59
+ tenant = options->tenant;
60
+ if (options->prompt.has_value()) {
61
+ switch (options->prompt.value()) {
62
+ case MicrosoftPrompt::LOGIN: prompt = "login"; break;
63
+ case MicrosoftPrompt::CONSENT: prompt = "consent"; break;
64
+ case MicrosoftPrompt::SELECT_ACCOUNT: prompt = "select_account"; break;
65
+ case MicrosoftPrompt::NONE: prompt = "none"; break;
66
+ }
67
+ }
49
68
  useOneTap = options->useOneTap.value_or(false);
50
69
  forceAccountPicker = options->forceAccountPicker.value_or(false);
70
+ useLegacyGoogleSignIn = options->useLegacyGoogleSignIn.value_or(false);
51
71
  }
52
72
 
53
73
  JNIEnv* env = Environment::current();
@@ -58,9 +78,12 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
58
78
  }
59
79
 
60
80
  jstring jLoginHint = loginHint.has_value() ? make_jstring(loginHint.value()).get() : nullptr;
81
+ jstring jTenant = tenant.has_value() ? make_jstring(tenant.value()).get() : nullptr;
82
+ jstring jPrompt = prompt.has_value() ? make_jstring(prompt.value()).get() : nullptr;
83
+
61
84
  jclass adapterClass = env->FindClass("com/auth/AuthAdapter");
62
85
  jmethodID loginMethod = env->GetStaticMethodID(adapterClass, "loginSync",
63
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;ZZ)V");
86
+ "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;ZZZLjava/lang/String;Ljava/lang/String;)V");
64
87
  env->CallStaticVoidMethod(adapterClass, loginMethod,
65
88
  contextPtr,
66
89
  make_jstring(providerStr).get(),
@@ -68,7 +91,10 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
68
91
  jScopes,
69
92
  jLoginHint,
70
93
  (jboolean)useOneTap,
71
- (jboolean)forceAccountPicker);
94
+ (jboolean)forceAccountPicker,
95
+ (jboolean)useLegacyGoogleSignIn,
96
+ jTenant,
97
+ jPrompt);
72
98
 
73
99
  return promise;
74
100
  }
@@ -179,7 +205,14 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess
179
205
 
180
206
  AuthUser user;
181
207
  const char* providerCStr = env->GetStringUTFChars(provider, nullptr);
182
- user.provider = (std::string(providerCStr) == "google") ? AuthProvider::GOOGLE : AuthProvider::APPLE;
208
+ std::string providerStr(providerCStr);
209
+ if (providerStr == "google") {
210
+ user.provider = AuthProvider::GOOGLE;
211
+ } else if (providerStr == "microsoft") {
212
+ user.provider = AuthProvider::MICROSOFT;
213
+ } else {
214
+ user.provider = AuthProvider::APPLE;
215
+ }
183
216
  env->ReleaseStringUTFChars(provider, providerCStr);
184
217
 
185
218
  if (email) {