react-native-nitro-auth 0.4.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 (85) hide show
  1. package/README.md +275 -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 +34 -4
  6. package/android/src/main/java/com/auth/AuthAdapter.kt +584 -67
  7. package/android/src/main/java/com/auth/GoogleSignInActivity.kt +1 -3
  8. package/android/src/main/java/com/auth/MicrosoftAuthActivity.kt +25 -0
  9. package/app.plugin.js +113 -3
  10. package/cpp/AuthCache.cpp +72 -19
  11. package/ios/AuthAdapter.swift +457 -52
  12. package/ios/KeychainStore.swift +43 -0
  13. package/ios/PlatformAuth+iOS.mm +29 -3
  14. package/lib/commonjs/Auth.web.js +246 -7
  15. package/lib/commonjs/Auth.web.js.map +1 -1
  16. package/lib/commonjs/index.js +7 -11
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/service.js.map +1 -1
  19. package/lib/commonjs/service.web.js +0 -8
  20. package/lib/commonjs/service.web.js.map +1 -1
  21. package/lib/commonjs/ui/social-button.js +12 -2
  22. package/lib/commonjs/ui/social-button.js.map +1 -1
  23. package/lib/commonjs/ui/social-button.web.js +12 -2
  24. package/lib/commonjs/ui/social-button.web.js.map +1 -1
  25. package/lib/commonjs/use-auth.js.map +1 -1
  26. package/lib/commonjs/utils/logger.js +1 -1
  27. package/lib/commonjs/utils/logger.js.map +1 -1
  28. package/lib/module/Auth.web.js +246 -7
  29. package/lib/module/Auth.web.js.map +1 -1
  30. package/lib/module/index.js +1 -1
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/service.js.map +1 -1
  33. package/lib/module/service.web.js +0 -8
  34. package/lib/module/service.web.js.map +1 -1
  35. package/lib/module/ui/social-button.js +12 -2
  36. package/lib/module/ui/social-button.js.map +1 -1
  37. package/lib/module/ui/social-button.web.js +12 -2
  38. package/lib/module/ui/social-button.web.js.map +1 -1
  39. package/lib/module/use-auth.js.map +1 -1
  40. package/lib/module/utils/logger.js +1 -1
  41. package/lib/module/utils/logger.js.map +1 -1
  42. package/lib/typescript/commonjs/Auth.nitro.d.ts +7 -2
  43. package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/Auth.web.d.ts +7 -0
  45. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/index.d.ts +1 -1
  47. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/service.web.d.ts +2 -6
  50. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/ui/social-button.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/ui/social-button.web.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/use-auth.d.ts +12 -8
  54. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/utils/logger.d.ts +5 -5
  56. package/lib/typescript/commonjs/utils/logger.d.ts.map +1 -1
  57. package/lib/typescript/module/Auth.nitro.d.ts +7 -2
  58. package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
  59. package/lib/typescript/module/Auth.web.d.ts +7 -0
  60. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  61. package/lib/typescript/module/index.d.ts +1 -1
  62. package/lib/typescript/module/index.d.ts.map +1 -1
  63. package/lib/typescript/module/service.d.ts.map +1 -1
  64. package/lib/typescript/module/service.web.d.ts +2 -6
  65. package/lib/typescript/module/service.web.d.ts.map +1 -1
  66. package/lib/typescript/module/ui/social-button.d.ts.map +1 -1
  67. package/lib/typescript/module/ui/social-button.web.d.ts.map +1 -1
  68. package/lib/typescript/module/use-auth.d.ts +12 -8
  69. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  70. package/lib/typescript/module/utils/logger.d.ts +5 -5
  71. package/lib/typescript/module/utils/logger.d.ts.map +1 -1
  72. package/nitrogen/generated/shared/c++/AuthProvider.hpp +4 -0
  73. package/nitrogen/generated/shared/c++/LoginOptions.hpp +13 -3
  74. package/nitrogen/generated/shared/c++/MicrosoftPrompt.hpp +84 -0
  75. package/package.json +11 -9
  76. package/react-native-nitro-auth.podspec +4 -2
  77. package/src/Auth.nitro.ts +13 -1
  78. package/src/Auth.web.ts +350 -7
  79. package/src/index.ts +1 -1
  80. package/src/service.ts +4 -3
  81. package/src/service.web.ts +3 -12
  82. package/src/ui/social-button.tsx +10 -2
  83. package/src/ui/social-button.web.tsx +10 -2
  84. package/src/use-auth.ts +12 -1
  85. 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,51 @@ 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
+
53
98
  ### Expo Setup
54
99
 
55
100
  Add the plugin to `app.json` or `app.config.js`:
@@ -63,17 +108,28 @@ Add the plugin to `app.json` or `app.config.js`:
63
108
  {
64
109
  "ios": {
65
110
  "googleClientId": "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com",
111
+ "googleServerClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
66
112
  "googleUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID",
67
- "appleSignIn": true
113
+ "appleSignIn": true,
114
+ "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
115
+ "microsoftTenant": "common",
116
+ "microsoftB2cDomain": "your-tenant.b2clogin.com"
68
117
  },
69
118
  "android": {
70
- "googleClientId": "YOUR_WEB_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"
71
123
  }
72
124
  }
73
125
  ]
74
126
  ],
75
127
  "extra": {
76
- "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com"
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"
77
133
  }
78
134
  }
79
135
  }
@@ -87,9 +143,18 @@ Create a `.env.local` file:
87
143
  # iOS Client ID
88
144
  GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com
89
145
  GOOGLE_IOS_URL_SCHEME=com.googleusercontent.apps.your-ios-client-id
146
+ GOOGLE_SERVER_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
90
147
 
91
148
  # Web Client ID (used for Android OAuth flow)
92
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
93
158
  ```
94
159
 
95
160
  Then reference them in `app.config.js`:
@@ -105,17 +170,28 @@ export default {
105
170
  {
106
171
  ios: {
107
172
  googleClientId: process.env.GOOGLE_IOS_CLIENT_ID,
173
+ googleServerClientId: process.env.GOOGLE_SERVER_CLIENT_ID,
108
174
  googleUrlScheme: process.env.GOOGLE_IOS_URL_SCHEME,
109
175
  appleSignIn: true,
176
+ microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
177
+ microsoftTenant: process.env.MICROSOFT_TENANT,
178
+ microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
110
179
  },
111
180
  android: {
112
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,
113
185
  },
114
186
  },
115
187
  ],
116
188
  ],
117
189
  extra: {
118
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,
119
195
  },
120
196
  },
121
197
  };
@@ -125,14 +201,106 @@ export default {
125
201
  >
126
202
  > - `appleSignIn` on iOS is `false` by default to avoid unnecessary entitlements. Set it to `true` to enable Apple Sign-In.
127
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.
128
205
  > - Add `googleWebClientId` to `expo.extra` for web platform support.
129
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).
130
249
 
131
250
  ### Bare React Native
132
251
 
133
- **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
134
286
 
135
- **Android:** Add `nitro_auth_google_client_id` string resource in `res/values/strings.xml`.
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
+ ```
302
+
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.
136
304
 
137
305
  ## Quick Start
138
306
 
@@ -164,11 +332,42 @@ function LoginScreen() {
164
332
  onPress={() => login("google")}
165
333
  disabled={loading || !hasPlayServices}
166
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
+ />
167
345
  </View>
168
346
  );
169
347
  }
170
348
  ```
171
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
+
172
371
  ## Migration from @react-native-google-signin/google-signin
173
372
 
174
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.
@@ -310,7 +509,7 @@ const handleCalendar = async () => {
310
509
 
311
510
  ### Pluggable Storage Adapters
312
511
 
313
- Nitro Auth persists the session automatically. By default, it uses simple file-based storage on native and `localStorage` on web.
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.
314
513
 
315
514
  #### 1) JS Storage (AsyncStorage, MMKV, etc.)
316
515
 
@@ -332,6 +531,9 @@ const mmkvAdapter: JSStorageAdapter = {
332
531
  AuthService.setJSStorageAdapter(mmkvAdapter);
333
532
  ```
334
533
 
534
+ > [!NOTE]
535
+ > Call `setJSStorageAdapter` before your first `useAuth()` or `AuthService` call so cached values are loaded before UI renders.
536
+
335
537
  #### 2) Native Storage (Keychain, etc.)
336
538
 
337
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.
@@ -344,6 +546,18 @@ import { KeychainStorage } from "./native/KeychainStorage";
344
546
  AuthService.setStorageAdapter(KeychainStorage);
345
547
  ```
346
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
+
347
561
  ### Logging & Debugging
348
562
 
349
563
  Enable verbose logging to see detailed OAuth flow information in the console:
@@ -369,7 +583,7 @@ const freshToken = await AuthService.getAccessToken();
369
583
 
370
584
  ### Standardized Error Codes
371
585
 
372
- Handle failures reliably with predictable error strings:
586
+ Handle failures reliably with predictable error strings. Some flows can surface provider-specific codes (listed below):
373
587
 
374
588
  ```ts
375
589
  try {
@@ -390,6 +604,12 @@ try {
390
604
  | `network_error` | A network error occurred |
391
605
  | `configuration_error` | Missing client IDs or invalid setup |
392
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 |
393
613
  | `unknown` | An unknown error occurred |
394
614
 
395
615
  ### Native Error Metadata
@@ -412,6 +632,13 @@ try {
412
632
  }
413
633
  ```
414
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
+
415
642
  ### Automatic Token Refresh
416
643
 
417
644
  The `getAccessToken()` method automatically checks if the current token is expired (or about to expire) and triggers a silent refresh if possible:
@@ -423,23 +650,6 @@ const { getAccessToken } = useAuth();
423
650
  const token = await getAccessToken();
424
651
  ```
425
652
 
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
653
  ### Offline Access (Server Auth Code)
444
654
 
445
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:
@@ -455,7 +665,7 @@ if (user?.serverAuthCode) {
455
665
 
456
666
  ### Custom Storage Adapter
457
667
 
458
- By default, Nitro Auth uses standard local storage. You can provide a custom adapter for better security.
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.
459
669
 
460
670
  > [!IMPORTANT]
461
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.
@@ -512,6 +722,9 @@ await login("google", {
512
722
  });
513
723
  ```
514
724
 
725
+ > [!NOTE]
726
+ > One-Tap requires Google Play Services. You can check `hasPlayServices` from `useAuth()` and show a fallback UI if needed.
727
+
515
728
  ### Force Account Picker
516
729
 
517
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:
@@ -548,6 +761,38 @@ This is useful for scenarios where:
548
761
  | `getAccessToken` | `() => Promise<string?>` | Get current access token (auto-refreshes) |
549
762
  | `refreshToken` | `() => Promise<AuthTokens>` | Explicitly refresh and return new tokens |
550
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
+
551
796
  ### LoginOptions
552
797
 
553
798
  | Option | Type | Platform | Description |
@@ -557,13 +802,14 @@ This is useful for scenarios where:
557
802
  | `useOneTap` | `boolean` | Android | Enable Google One-Tap (Credential Manager) |
558
803
  | `useSheet` | `boolean` | iOS | Enable iOS Google Sign-In Sheet |
559
804
  | `forceAccountPicker` | `boolean` | All | Always show the account selection screen |
560
- | `webClientId` | `string` | Web | Override the default Google Web Client ID |
805
+ | `tenant` | `string` | Microsoft | Azure AD tenant (`common`, `organizations`, etc.) |
806
+ | `prompt` | `string` | Microsoft | Prompt behavior (`login`, `consent`, `select_account`, `none`) |
561
807
 
562
808
  ### SocialButton Props
563
809
 
564
810
  | Prop | Type | Default | Description |
565
811
  | -------------- | ---------------------------------------------- | ----------- | --------------------------------------------- |
566
- | `provider` | `"google" \| "apple"` | required | Authentication provider |
812
+ | `provider` | `"google" \| "apple" \| "microsoft"` | required | Authentication provider |
567
813
  | `variant` | `"primary" \| "outline" \| "white" \| "black"` | `"primary"` | Button style variant |
568
814
  | `onPress` | `() => void` | — | Custom handler (disables default login) |
569
815
  | `onSuccess` | `(user: AuthUser) => void` | — | Called with user data on success (auto-login) |
@@ -579,6 +825,7 @@ This is useful for scenarios where:
579
825
  | ------------------------- | --- | ------- | --- |
580
826
  | Google Sign-In | ✅ | ✅ | ✅ |
581
827
  | Apple Sign-In | ✅ | ❌ | ✅ |
828
+ | Microsoft Sign-In | ✅ | ✅ | ✅ |
582
829
  | Custom OAuth Scopes | ✅ | ✅ | ✅ |
583
830
  | Incremental Authorization | ✅ | ✅ | ✅ |
584
831
  | 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,15 +38,32 @@ 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;
45
54
 
46
55
  if (options) {
47
56
  if (options->scopes) scopes = *options->scopes;
48
57
  loginHint = options->loginHint;
58
+ tenant = options->tenant;
59
+ if (options->prompt.has_value()) {
60
+ switch (options->prompt.value()) {
61
+ case MicrosoftPrompt::LOGIN: prompt = "login"; break;
62
+ case MicrosoftPrompt::CONSENT: prompt = "consent"; break;
63
+ case MicrosoftPrompt::SELECT_ACCOUNT: prompt = "select_account"; break;
64
+ case MicrosoftPrompt::NONE: prompt = "none"; break;
65
+ }
66
+ }
49
67
  useOneTap = options->useOneTap.value_or(false);
50
68
  forceAccountPicker = options->forceAccountPicker.value_or(false);
51
69
  }
@@ -58,9 +76,12 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
58
76
  }
59
77
 
60
78
  jstring jLoginHint = loginHint.has_value() ? make_jstring(loginHint.value()).get() : nullptr;
79
+ jstring jTenant = tenant.has_value() ? make_jstring(tenant.value()).get() : nullptr;
80
+ jstring jPrompt = prompt.has_value() ? make_jstring(prompt.value()).get() : nullptr;
81
+
61
82
  jclass adapterClass = env->FindClass("com/auth/AuthAdapter");
62
83
  jmethodID loginMethod = env->GetStaticMethodID(adapterClass, "loginSync",
63
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;ZZ)V");
84
+ "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;ZZLjava/lang/String;Ljava/lang/String;)V");
64
85
  env->CallStaticVoidMethod(adapterClass, loginMethod,
65
86
  contextPtr,
66
87
  make_jstring(providerStr).get(),
@@ -68,7 +89,9 @@ std::shared_ptr<Promise<AuthUser>> PlatformAuth::login(AuthProvider provider, co
68
89
  jScopes,
69
90
  jLoginHint,
70
91
  (jboolean)useOneTap,
71
- (jboolean)forceAccountPicker);
92
+ (jboolean)forceAccountPicker,
93
+ jTenant,
94
+ jPrompt);
72
95
 
73
96
  return promise;
74
97
  }
@@ -179,7 +202,14 @@ extern "C" JNIEXPORT void JNICALL Java_com_auth_AuthAdapter_nativeOnLoginSuccess
179
202
 
180
203
  AuthUser user;
181
204
  const char* providerCStr = env->GetStringUTFChars(provider, nullptr);
182
- user.provider = (std::string(providerCStr) == "google") ? AuthProvider::GOOGLE : AuthProvider::APPLE;
205
+ std::string providerStr(providerCStr);
206
+ if (providerStr == "google") {
207
+ user.provider = AuthProvider::GOOGLE;
208
+ } else if (providerStr == "microsoft") {
209
+ user.provider = AuthProvider::MICROSOFT;
210
+ } else {
211
+ user.provider = AuthProvider::APPLE;
212
+ }
183
213
  env->ReleaseStringUTFChars(provider, providerCStr);
184
214
 
185
215
  if (email) {