react-native-nitro-auth 0.5.6 → 0.5.8

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 (36) hide show
  1. package/README.md +288 -907
  2. package/android/src/main/cpp/PlatformAuth+Android.cpp +1 -1
  3. package/android/src/main/java/com/auth/AuthAdapter.kt +65 -27
  4. package/cpp/HybridAuth.cpp +58 -7
  5. package/cpp/HybridAuth.hpp +1 -0
  6. package/ios/AuthAdapter.swift +45 -16
  7. package/ios/PlatformAuth+iOS.mm +20 -1
  8. package/lib/commonjs/service.js +21 -17
  9. package/lib/commonjs/service.js.map +1 -1
  10. package/lib/commonjs/use-auth.js +26 -10
  11. package/lib/commonjs/use-auth.js.map +1 -1
  12. package/lib/commonjs/utils/auth-error.js +9 -2
  13. package/lib/commonjs/utils/auth-error.js.map +1 -1
  14. package/lib/module/service.js +21 -17
  15. package/lib/module/service.js.map +1 -1
  16. package/lib/module/use-auth.js +26 -10
  17. package/lib/module/use-auth.js.map +1 -1
  18. package/lib/module/utils/auth-error.js +9 -2
  19. package/lib/module/utils/auth-error.js.map +1 -1
  20. package/lib/typescript/commonjs/Auth.nitro.d.ts +2 -2
  21. package/lib/typescript/commonjs/Auth.nitro.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  23. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/utils/auth-error.d.ts.map +1 -1
  25. package/lib/typescript/module/Auth.nitro.d.ts +2 -2
  26. package/lib/typescript/module/Auth.nitro.d.ts.map +1 -1
  27. package/lib/typescript/module/service.d.ts.map +1 -1
  28. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  29. package/lib/typescript/module/utils/auth-error.d.ts.map +1 -1
  30. package/nitro.json +4 -1
  31. package/nitrogen/generated/ios/NitroAuth+autolinking.rb +2 -0
  32. package/package.json +10 -8
  33. package/src/Auth.nitro.ts +3 -1
  34. package/src/service.ts +22 -17
  35. package/src/use-auth.ts +35 -6
  36. package/src/utils/auth-error.ts +12 -1
package/README.md CHANGED
@@ -1,213 +1,70 @@
1
1
  # react-native-nitro-auth
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/react-native-nitro-auth?style=flat-square)](https://www.npmjs.com/package/react-native-nitro-auth)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
- [![Nitro Modules](https://img.shields.io/badge/Powered%20by-Nitro%20Modules-blueviolet?style=flat-square)](https://nitro.margelo.com)
3
+ Fast React Native authentication for Google Sign-In, Apple Sign-In, and Microsoft Entra ID, built on Nitro Modules and JSI.
6
4
 
7
- 🚀 **High-performance, JSI-powered Authentication for React Native.**
5
+ `react-native-nitro-auth` gives Expo and React Native apps one typed API for native social login, web OAuth, token refresh, incremental scopes, and auth state listeners without owning your app's long-term token storage.
8
6
 
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.
7
+ ## Why Use It?
10
8
 
11
- ## Why Nitro Auth?
9
+ - One package for Google, Apple, and Microsoft authentication on React Native.
10
+ - Native iOS and Android bridges powered by `react-native-nitro-modules`.
11
+ - Expo config plugin for client IDs, URL schemes, entitlements, and Android resources.
12
+ - Web implementation for Expo web with Google, Apple, and Microsoft OAuth.
13
+ - Typed `useAuth()` hook, `AuthService`, `SocialButton`, and `AuthError`.
14
+ - App-owned persistence model: tokens stay in memory unless your app stores a snapshot.
15
+ - Built-in flows for silent restore, token refresh, account picker, login hints, and incremental Google scopes.
12
16
 
13
- Nitro Auth is designed to replace legacy modules like `@react-native-google-signin/google-signin` with a modern, high-performance architecture.
17
+ ## Choose Your Path
14
18
 
15
- | Feature | Legacy Modules | Nitro Auth |
16
- | :---------------- | :--------------------------- | :----------------------------------------------------------------------------- |
17
- | **Performance** | Async bridge overhead (JSON) | **Direct JSI C++ (Zero-copy)** |
18
- | **Storage** | Varies / Hidden defaults | **Native: in-memory only; Web: session cache (tokens memory-only by default)** |
19
- | **Setup** | Manual async initialization | **Sync & declarative plugins** |
20
- | **Types** | Manual / Brittle | **Fully Generated (Nitrogen)** |
21
- | **Provider Data** | Varies | **Normalized auth payload** |
19
+ | Need | Use |
20
+ | --- | --- |
21
+ | Google, Apple, or Microsoft sign-in in an Expo or React Native app | `react-native-nitro-auth` |
22
+ | Generic OAuth or OIDC provider not covered by this package | `expo-auth-session` or `react-native-app-auth` |
23
+ | Firebase user management, password auth, MFA, and hosted auth platform | `@react-native-firebase/auth`, Auth0, Authgear, or your IDaaS SDK |
24
+ | Server-side session validation | Your backend; client JWT decode is display-only |
22
25
 
23
- ## Features
26
+ ## Install
24
27
 
25
- - **Ultra-fast**: Direct C++ calls using JSI (no JSON serialization).
26
- - **Fully Type-Safe**: Shared types between TypeScript, C++, Swift, and Kotlin.
27
- - **Incremental Auth**: Request additional OAuth scopes on the fly.
28
- - **Expo Ready**: Comes with a powerful Config Plugin for zero-config setup.
29
- - **Cross-Platform**: Unified API for iOS, Android, and Web.
30
- - **Token Lifecycle Helpers**: `getAccessToken()` refreshes near-expiry tokens; `refreshToken()` allows explicit refresh control.
31
- - **Google One-Tap / Sheet**: Modern login experience on Android (Credential Manager) and iOS (Sign-In Sheet).
32
- - **Error Metadata**: Detailed native error messages for easier debugging.
33
- - **Normalized Provider Payload**: Exposes provider/user/token fields in a consistent cross-platform shape.
34
- - **App-Owned Persistence**: Native is stateless by default. Web keeps a non-sensitive session snapshot; apps decide long-term persistence strategy.
35
-
36
- ## Design Philosophy
37
-
38
- This is an **auth-only package** - on **native** (iOS/Android) it does NOT store auth data by default.
39
- On **web**, Nitro Auth stores a non-sensitive auth snapshot in `sessionStorage` by default; sensitive tokens stay memory-only unless explicitly enabled.
40
-
41
- The package provides:
42
-
43
- - Login/logout functionality for Google, Apple, and Microsoft
44
- - Token management (access token, refresh token, ID token)
45
- - Scope management (request/revoke scopes)
46
- - Consistent provider/user/token field exposure across iOS, Android, and Web
47
-
48
- **Storage is the responsibility of the app using this package.** Use your own storage layer (for example [react-native-nitro-storage](https://github.com/JoaoPauloCMarra/react-native-nitro-storage)) to persist app-level auth snapshots/tokens when needed.
49
-
50
- ### Provider Data Availability
51
-
52
- Token/data shape is normalized, but provider SDKs do not always return all fields at login time:
53
-
54
- | Provider + Flow | `idToken` | `accessToken` | `serverAuthCode` | `expirationTime` |
55
- | --------------------------- | ---------- | ---------------------------- | ---------------------------- | ----------------------------------- |
56
- | Google (iOS) | Usually ✅ | Provider-dependent | Optional (if configured) | Optional |
57
- | Google (Android One-Tap) | ✅ | Usually `undefined` at login | `undefined` | Derived from ID token when possible |
58
- | Google (Android Legacy) | ✅ | Usually `undefined` at login | ✅ (if server client is set) | Derived from ID token when possible |
59
- | Google (Web) | ✅ | ✅ | Optional (`code`) | Usually ✅ |
60
- | Microsoft (iOS/Android/Web) | ✅ | Usually ✅ | `undefined` | Usually ✅ |
61
- | Apple (iOS/Web) | ✅ | `undefined` | `undefined` | `undefined` |
62
-
63
- > [!NOTE]
64
- > On Apple, email/name can be limited after first consent depending on Apple policy.
65
-
66
- ## Installation
67
-
68
- ```bash
28
+ ```sh
69
29
  bun add react-native-nitro-auth react-native-nitro-modules
70
30
  ```
71
31
 
72
- ### Requirements
73
-
74
- | Dependency | Version |
75
- | ---------------------------- | ------------ |
76
- | `react-native` | `>= 0.75.0` |
77
- | `react-native-nitro-modules` | `>= 0.35.0` |
78
- | `react` | `*` |
79
-
80
- For Expo projects, rebuild native code after installation:
32
+ For Expo projects, prebuild after adding the config plugin:
81
33
 
82
- ```bash
83
- bunx expo prebuild
34
+ ```sh
35
+ bunx expo prebuild --clean
84
36
  ```
85
37
 
86
- ### Testing locally (example app + Microsoft login)
87
-
88
- Fastest way to confirm the package and Microsoft login work:
89
-
90
- 1. **Azure app (one-time)**
91
- In [Azure Portal](https://portal.azure.com) → **Azure Active Directory** → **App registrations** → **New registration**:
92
- - Name: e.g. `Nitro Auth Example`
93
- - Supported account types: **Accounts in any organizational directory and personal Microsoft accounts**
94
- - Redirect URI (add after creation):
95
- - **Android**: `msauth://com.auth.example/<client-id>`
96
- - **iOS**: `msauth.com.auth.example://auth` (use your bundle id)
97
- - Under **Authentication** → **Platform configurations** → add **Mobile and desktop applications** with the Android redirect URI above and the iOS one if testing on iOS.
98
- Copy the **Application (client) ID**.
99
-
100
- 2. **Env file**
101
- From the repo root:
102
-
103
- ```bash
104
- cd apps/example
105
- cp .env.example .env.local
106
- ```
107
-
108
- Edit `.env.local` and set at least:
109
-
110
- ```bash
111
- MICROSOFT_CLIENT_ID=<your-application-client-id>
112
- MICROSOFT_TENANT=common
113
- ```
114
-
115
- (Google/Apple can stay placeholder if you only care about Microsoft.)
116
-
117
- 3. **Run the app**
118
- From the **monorepo root**:
119
-
120
- ```bash
121
- bun install
122
- bun run start
123
- ```
124
-
125
- In a second terminal:
126
-
127
- ```bash
128
- bun run example:android
129
- # or
130
- bun run example:ios
131
- ```
132
-
133
- Wait for the app to install and open.
38
+ For bare React Native projects, install pods after installing the package:
134
39
 
135
- 4. **Test Microsoft**
136
- In the app, tap **Sign in with Microsoft**. A browser or in-app tab opens; sign in with a Microsoft/personal account, then you should return to the app with the user shown (email, name, provider MICROSOFT).
137
- If you see "configuration_error", check `MICROSOFT_CLIENT_ID` and that the redirect URI in Azure matches your app (e.g. `msauth://com.auth.example/<client-id>` for the example app).
138
-
139
- > [!TIP]
140
- > In the example app on Android, you can toggle **Legacy Google Sign-In** to compare Credential Manager vs legacy GoogleSignIn (and to get `serverAuthCode`).
141
-
142
- ### Expo Setup
143
-
144
- Add the plugin to `app.json` or `app.config.js`:
145
-
146
- ```json
147
- {
148
- "expo": {
149
- "plugins": [
150
- [
151
- "react-native-nitro-auth",
152
- {
153
- "ios": {
154
- "googleClientId": "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com",
155
- "googleServerClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
156
- "googleUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID",
157
- "appleSignIn": true,
158
- "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
159
- "microsoftTenant": "common",
160
- "microsoftB2cDomain": "your-tenant.b2clogin.com"
161
- },
162
- "android": {
163
- "googleClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
164
- "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
165
- "microsoftTenant": "common",
166
- "microsoftB2cDomain": "your-tenant.b2clogin.com"
167
- }
168
- }
169
- ]
170
- ],
171
- "extra": {
172
- "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
173
- "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
174
- "microsoftTenant": "common",
175
- "microsoftB2cDomain": "your-tenant.b2clogin.com",
176
- "appleWebClientId": "com.example.web"
177
- }
178
- }
179
- }
40
+ ```sh
41
+ cd ios && pod install
180
42
  ```
181
43
 
182
- **Using environment variables (recommended):**
183
-
184
- Create a `.env.local` file:
44
+ ## Requirements
185
45
 
186
- ```bash
187
- # iOS Client ID
188
- GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com
189
- GOOGLE_IOS_URL_SCHEME=com.googleusercontent.apps.your-ios-client-id
190
- GOOGLE_SERVER_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
46
+ | Runtime | Requirement |
47
+ | --- | --- |
48
+ | React Native | `>=0.75` |
49
+ | Nitro Modules | `>=0.35` |
50
+ | iOS | 15.1+ recommended |
51
+ | Android | min SDK 24+ recommended |
52
+ | Expo example baseline | Expo SDK 55, React Native 0.83, React 19 |
191
53
 
192
- # Web Client ID (used for Android OAuth flow)
193
- GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
54
+ ## Expo Setup
194
55
 
195
- # Microsoft/Azure AD (optional)
196
- MICROSOFT_CLIENT_ID=your-azure-ad-application-id
197
- MICROSOFT_TENANT=common
198
- MICROSOFT_B2C_DOMAIN=your-tenant.b2clogin.com
199
-
200
- # Apple (web only)
201
- APPLE_WEB_CLIENT_ID=com.example.web
202
- ```
203
-
204
- Then reference them in `app.config.js`:
205
-
206
- ```javascript
207
- import "dotenv/config";
56
+ Add the plugin to `app.json` or `app.config.js`.
208
57
 
58
+ ```js
209
59
  export default {
210
60
  expo: {
61
+ scheme: "myapp",
62
+ ios: {
63
+ bundleIdentifier: "com.company.myapp",
64
+ },
65
+ android: {
66
+ package: "com.company.myapp",
67
+ },
211
68
  plugins: [
212
69
  [
213
70
  "react-native-nitro-auth",
@@ -232,835 +89,359 @@ export default {
232
89
  ],
233
90
  extra: {
234
91
  googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID,
92
+ appleWebClientId: process.env.APPLE_WEB_CLIENT_ID,
235
93
  microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
236
94
  microsoftTenant: process.env.MICROSOFT_TENANT,
237
95
  microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
238
- appleWebClientId: process.env.APPLE_WEB_CLIENT_ID,
96
+ nitroAuthWebStorage: "memory",
97
+ nitroAuthPersistTokensOnWeb: false,
239
98
  },
240
99
  },
241
100
  };
242
101
  ```
243
102
 
244
- > [!NOTE]
245
- >
246
- > - `appleSignIn` on iOS is `false` by default to avoid unnecessary entitlements. Set it to `true` to enable Apple Sign-In.
247
- > - For Android, use your **Web Client ID** (not Android Client ID) for proper OAuth flow.
248
- > - If you need `serverAuthCode`, set `googleServerClientId` to your Web Client ID.
249
- > - Add `googleWebClientId` to `expo.extra` for web platform support.
250
- > - The `serverAuthCode` is automatically included in `AuthUser` when available (requires backend integration setup in Google Cloud Console).
251
- > - For Microsoft Sign-In, use `common` tenant for multi-tenant apps, or specify your Azure AD tenant ID for single-tenant apps.
252
- > - For Azure AD B2C, set `microsoftB2cDomain` and pass the B2C tenant in `microsoftTenant`.
253
-
254
- ### Google OAuth Setup
255
-
256
- 1. Create OAuth client IDs in Google Cloud Console:
257
- - **iOS client ID** (used by iOS)
258
- - **Web client ID** (used by Android and for `serverAuthCode`)
259
- 2. Configure your app:
260
- - Expo: set `googleClientId`, `googleServerClientId`, and `googleUrlScheme`
261
- - Bare iOS: add `GIDClientID`, `GIDServerClientID`, and URL scheme in `Info.plist`
262
- - Bare Android: set `nitro_auth_google_client_id` to your **Web client ID**
263
- 3. If you use `serverAuthCode`, make sure OAuth consent screen is configured in Google Cloud.
264
-
265
- ### Apple Sign-In Setup
266
-
267
- 1. **iOS**: enable the “Sign in with Apple” capability in Xcode and in your Apple Developer account.
268
- 2. **Web**: create a Service ID and configure the domain + return URL in Apple Developer.
269
- 3. Configure your app:
270
- - Expo: set `appleSignIn: true` for iOS.
271
- - Web: set `appleWebClientId` in `expo.extra` (or `.env`).
272
-
273
- ### Microsoft Azure AD Setup
274
-
275
- To enable Microsoft Sign-In, you need to register an application in the Azure Portal:
276
-
277
- 1. Go to [Azure Portal](https://portal.azure.com) > Azure Active Directory > App registrations
278
- 2. Click "New registration"
279
- 3. Set the redirect URIs:
280
- - **iOS**: `msauth.{bundle-identifier}://auth` (e.g., `msauth.com.myapp://auth`)
281
- - **Android**: `msauth://{package-name}/{client-id}` (e.g., `msauth://com.myapp/00000000-0000-0000-0000-000000000000`)
282
- - **Web**: `https://your-domain.com` (the page that loads the app)
283
- 4. Under "API permissions", add `openid`, `email`, `profile`, and `User.Read` (Microsoft Graph)
284
- 5. Copy the Application (client) ID for use in your config
285
-
286
- **Tenant Options:**
287
-
288
- - `common` - Any Azure AD or personal Microsoft account
289
- - `organizations` - Any Azure AD account (work/school)
290
- - `consumers` - Personal Microsoft accounts only
291
- - `{tenant-id}` - Specific Azure AD tenant
292
- - **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).
293
-
294
- ### Bare React Native
295
-
296
- **iOS**
297
-
298
- - Add to `Info.plist`: `GIDClientID`, `GIDServerClientID` (optional), `MSALClientID`, `MSALTenant` (optional), `MSALB2cDomain` (optional).
299
- - Add URL schemes in `Info.plist`:
300
- - Google: `com.googleusercontent.apps.<YOUR_IOS_CLIENT_ID>`
301
- - Microsoft: `msauth.<your.bundle.id>` (used for `msauth.<bundle.id>://auth`)
302
- - Enable the “Sign in with Apple” capability if you use Apple Sign-In.
303
-
304
- **Android**
305
-
306
- - Add string resources in `res/values/strings.xml`:
307
- - `nitro_auth_google_client_id` (Web client ID)
308
- - `nitro_auth_microsoft_client_id`
309
- - `nitro_auth_microsoft_tenant` (optional)
310
- - `nitro_auth_microsoft_b2c_domain` (optional)
311
- - `MicrosoftAuthActivity` is **automatically declared** by the library manifest — no manual `AndroidManifest.xml` entry is required for basic Microsoft OAuth redirect handling. If you need to customize the intent-filter (e.g., restrict the host/path to your specific package and client ID), you can override it in your app manifest:
312
-
313
- ```xml
314
- <activity
315
- android:name="com.auth.MicrosoftAuthActivity"
316
- android:exported="true"
317
- android:launchMode="singleTask">
318
- <intent-filter>
319
- <action android:name="android.intent.action.VIEW" />
320
- <category android:name="android.intent.category.DEFAULT" />
321
- <category android:name="android.intent.category.BROWSABLE" />
322
- <data
323
- android:scheme="msauth"
324
- android:host="${applicationId}"
325
- android:path="/YOUR_MICROSOFT_CLIENT_ID" />
326
- </intent-filter>
327
- </activity>
328
- ```
103
+ ### Plugin Options
329
104
 
330
- ### Web Setup
331
-
332
- Nitro Auth reads web configuration from `expo.extra`:
333
-
334
- ```json
335
- {
336
- "expo": {
337
- "extra": {
338
- "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
339
- "microsoftClientId": "YOUR_AZURE_AD_CLIENT_ID",
340
- "microsoftTenant": "common",
341
- "microsoftB2cDomain": "your-tenant.b2clogin.com",
342
- "appleWebClientId": "com.example.web",
343
- "nitroAuthWebStorage": "session",
344
- "nitroAuthPersistTokensOnWeb": false
345
- }
346
- }
347
- }
348
- ```
105
+ | Option | Platform | Purpose |
106
+ | --- | --- | --- |
107
+ | `ios.googleClientId` | iOS | Google iOS OAuth client ID |
108
+ | `ios.googleServerClientId` | iOS | Google web/server client ID for server auth code flows |
109
+ | `ios.googleUrlScheme` | iOS | Reversed iOS client ID URL scheme |
110
+ | `ios.appleSignIn` | iOS | Adds Apple Sign-In entitlement when `true` |
111
+ | `ios.microsoftClientId` | iOS | Microsoft app/client ID |
112
+ | `ios.microsoftTenant` | iOS | Microsoft tenant, `common`, `organizations`, `consumers`, or tenant ID |
113
+ | `ios.microsoftB2cDomain` | iOS | Azure AD B2C domain |
114
+ | `android.googleClientId` | Android | Google web OAuth client ID |
115
+ | `android.microsoftClientId` | Android | Microsoft app/client ID |
116
+ | `android.microsoftTenant` | Android | Microsoft tenant |
117
+ | `android.microsoftB2cDomain` | Android | Azure AD B2C domain |
349
118
 
350
- 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.
119
+ ## Provider Setup
351
120
 
352
- - `nitroAuthWebStorage`: `"session"` (default), `"local"`, or `"memory"`.
353
- - `nitroAuthPersistTokensOnWeb`: `false` by default (recommended). Set `true` only if you need cross-reload token persistence.
121
+ ### Google Sign-In
354
122
 
355
- ## Quick Start
123
+ Create OAuth clients in Google Cloud Console:
356
124
 
357
- ### Using the Hook
125
+ - iOS client ID for your bundle identifier.
126
+ - Web client ID for Android, web, and server auth code flows.
127
+ - Android SHA-1/SHA-256 entries for local debug and release signing.
358
128
 
359
- ```tsx
360
- import { useAuth, SocialButton } from "react-native-nitro-auth";
361
-
362
- function LoginScreen() {
363
- const { user, loading, error, login, logout, hasPlayServices } = useAuth();
364
-
365
- if (user) {
366
- return (
367
- <View>
368
- <Image source={{ uri: user.photo }} />
369
- <Text>{user.name}</Text>
370
- <Button title="Sign Out" onPress={logout} />
371
- </View>
372
- );
373
- }
129
+ Use the iOS reversed client ID as `GOOGLE_IOS_URL_SCHEME`.
374
130
 
375
- return (
376
- <View>
377
- {error && <Text style={{ color: "red" }}>{error.message}</Text>}
378
- {!hasPlayServices && <Text>Please install Google Play Services</Text>}
131
+ ### Apple Sign-In
379
132
 
380
- <SocialButton
381
- provider="google"
382
- onPress={() => login("google")}
383
- disabled={loading || !hasPlayServices}
384
- />
385
- <SocialButton
386
- provider="apple"
387
- onPress={() => login("apple")}
388
- disabled={loading}
389
- />
390
- <SocialButton
391
- provider="microsoft"
392
- onPress={() => login("microsoft")}
393
- disabled={loading}
394
- />
395
- </View>
396
- );
397
- }
398
- ```
133
+ Set `ios.appleSignIn: true` in the config plugin. Apple returns name and email only on the first authorization for a user. Store any profile fields you need in your own backend or app state.
399
134
 
400
- ### Microsoft Login Options
135
+ Apple Sign-In is supported on iOS and web. It is intentionally reported as `unsupported_provider` on Android.
401
136
 
402
- ```tsx
403
- // Login with specific tenant
404
- await login("microsoft", {
405
- tenant: "your-tenant-id",
406
- prompt: "select_account", // 'login' | 'consent' | 'select_account' | 'none'
407
- scopes: ["openid", "email", "profile", "User.Read"],
408
- loginHint: "user@example.com",
409
- });
410
- ```
411
-
412
- **B2C example:**
137
+ ### Microsoft Entra ID
413
138
 
414
- ```tsx
415
- await login("microsoft", {
416
- tenant: "your-tenant.onmicrosoft.com/B2C_1_signin",
417
- scopes: ["openid", "email", "profile", "offline_access"],
418
- });
419
- ```
420
-
421
- ## Migration from @react-native-google-signin/google-signin
139
+ Create an app registration in Microsoft Entra ID and add redirect URIs:
422
140
 
423
- 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.
141
+ - iOS: `msauth.<bundleIdentifier>://auth`
142
+ - Android: `msauth://<androidPackage>/<clientId>`
143
+ - Web: your web origin, for example `https://app.example.com`
424
144
 
425
- ### 1) Replace the dependency
145
+ Use `microsoftTenant` for `common`, `organizations`, `consumers`, a tenant ID, or a B2C policy path. Use `microsoftB2cDomain` for Azure AD B2C.
426
146
 
427
- ```bash
428
- bun remove @react-native-google-signin/google-signin
429
- bun add react-native-nitro-auth react-native-nitro-modules
430
- ```
431
-
432
- ### 2) Move configuration to the Nitro Auth plugin
433
-
434
- Nitro Auth does not use `GoogleSignin.configure(...)`. Instead, set your client IDs via the config plugin (Expo) or native config (bare).
435
-
436
- **Expo** (recommended):
147
+ ## Quick Start
437
148
 
438
- ```json
439
- {
440
- "expo": {
441
- "plugins": [
442
- [
443
- "react-native-nitro-auth",
444
- {
445
- "ios": {
446
- "googleClientId": "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com",
447
- "googleUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID"
448
- },
449
- "android": {
450
- "googleClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com"
451
- }
452
- }
453
- ]
454
- ],
455
- "extra": {
456
- "googleWebClientId": "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com"
149
+ ```tsx
150
+ import { Button, Text, View } from "react-native";
151
+ import { AuthError, useAuth } from "react-native-nitro-auth";
152
+
153
+ export function SignInScreen() {
154
+ const { user, loading, login, logout, getAccessToken } = useAuth();
155
+
156
+ async function signInWithGoogle() {
157
+ try {
158
+ await login("google", {
159
+ scopes: ["email", "profile"],
160
+ });
161
+ } catch (e) {
162
+ const error = AuthError.from(e);
163
+ console.warn(error.code, error.underlyingMessage);
457
164
  }
458
165
  }
459
- }
460
- ```
461
-
462
- **Bare React Native:**
463
-
464
- - iOS: add `GIDClientID` (and optionally `GIDServerClientID`) to `Info.plist` and set the URL scheme.
465
- - Android: add `nitro_auth_google_client_id` string resource in `res/values/strings.xml` (use your Web Client ID).
466
-
467
- ### 3) Update API usage
468
-
469
- | @react-native-google-signin/google-signin | Nitro Auth |
470
- | ----------------------------------------- | --------------------------------------------------------- |
471
- | `GoogleSignin.configure({...})` | Configure in plugin / native config |
472
- | `GoogleSignin.signIn()` | `login("google")` or `<SocialButton provider="google" />` |
473
- | `GoogleSignin.signOut()` | `logout()` |
474
- | `GoogleSignin.getTokens()` | `getAccessToken()` or `refreshToken()` |
475
- | `GoogleSignin.hasPlayServices()` | `hasPlayServices` from `useAuth()` |
476
-
477
- **Example migration:**
478
166
 
479
- ```tsx
480
- // Before
481
- import { GoogleSignin } from "@react-native-google-signin/google-signin";
482
-
483
- await GoogleSignin.signIn();
484
- const tokens = await GoogleSignin.getTokens();
485
-
486
- // After
487
- import { useAuth } from "react-native-nitro-auth";
488
-
489
- const { login, getAccessToken } = useAuth();
167
+ async function readToken() {
168
+ const token = await getAccessToken();
169
+ console.log(token);
170
+ }
490
171
 
491
- await login("google");
492
- const accessToken = await getAccessToken();
172
+ return (
173
+ <View>
174
+ <Text>{user?.email ?? "Signed out"}</Text>
175
+ <Button
176
+ title={loading ? "Signing in..." : "Sign in with Google"}
177
+ onPress={signInWithGoogle}
178
+ />
179
+ <Button title="Get access token" onPress={readToken} />
180
+ <Button title="Sign out" onPress={logout} />
181
+ </View>
182
+ );
183
+ }
493
184
  ```
494
185
 
495
- ### 4) Remove manual init
496
-
497
- If you previously called `GoogleSignin.configure()` at app startup, remove it. Nitro Auth loads configuration from the plugin/native settings at runtime.
498
-
499
- ## Advanced Features
500
-
501
- ### Silent Restore
502
-
503
- Attempts to restore provider SDK sessions on app startup.
504
-
505
- - Google: restore is supported via provider SDK session state.
506
- - Apple: provider credentials are re-requested by OS flow.
507
- - Microsoft: no internal persistence; restore requires your app/backend session strategy.
186
+ ## SocialButton
508
187
 
509
188
  ```tsx
510
- useEffect(() => {
511
- AuthService.silentRestore();
512
- }, []);
189
+ import { SocialButton } from "react-native-nitro-auth";
190
+
191
+ export function AuthButtons() {
192
+ return (
193
+ <>
194
+ <SocialButton provider="google" />
195
+ <SocialButton provider="apple" variant="black" />
196
+ <SocialButton provider="microsoft" variant="outline" />
197
+ </>
198
+ );
199
+ }
513
200
  ```
514
201
 
515
- ### Global Auth State Listener
202
+ ## AuthService
516
203
 
517
- Subscribe to authentication changes outside of React components:
204
+ Use `AuthService` when you need auth outside React components.
518
205
 
519
206
  ```ts
520
207
  import { AuthService } from "react-native-nitro-auth";
521
208
 
209
+ await AuthService.silentRestore();
210
+
522
211
  const unsubscribe = AuthService.onAuthStateChanged((user) => {
523
- if (user) {
524
- console.log("Logged in:", user.email);
525
- } else {
526
- console.log("Logged out");
527
- }
212
+ console.log(user?.email);
213
+ });
214
+
215
+ const tokensUnsubscribe = AuthService.onTokensRefreshed((tokens) => {
216
+ console.log(tokens.expirationTime);
528
217
  });
529
218
 
530
- // Later...
531
219
  unsubscribe();
220
+ tokensUnsubscribe();
532
221
  ```
533
222
 
534
- ### Global Token Refresh Listener
535
-
536
- Be notified whenever tokens are refreshed automatically (or manually):
223
+ ## Login Options
537
224
 
538
225
  ```ts
539
- import { AuthService } from "react-native-nitro-auth";
540
-
541
- const unsubscribe = AuthService.onTokensRefreshed((tokens) => {
542
- console.log("New tokens:", tokens.accessToken);
543
- // Update your API client / Apollo links
226
+ await login("google", {
227
+ scopes: ["email", "profile"],
228
+ loginHint: "user@example.com",
229
+ useOneTap: true,
230
+ useSheet: true,
231
+ forceAccountPicker: true,
232
+ useLegacyGoogleSignIn: true,
544
233
  });
545
- ```
546
-
547
- ### Incremental Authorization
548
234
 
549
- Request new scopes when you need them without logging the user out:
550
-
551
- ```tsx
552
- const { requestScopes, revokeScopes, scopes } = useAuth();
553
-
554
- const handleCalendar = async () => {
555
- try {
556
- await requestScopes(["https://www.googleapis.com/auth/calendar.readonly"]);
557
- console.log("Got calendar access!");
558
- } catch (e) {
559
- console.error("Scope request failed");
560
- }
561
- };
235
+ await login("microsoft", {
236
+ scopes: ["openid", "profile", "email", "offline_access", "User.Read"],
237
+ tenant: "organizations",
238
+ prompt: "select_account",
239
+ });
562
240
  ```
563
241
 
564
- ### App-Owned Persistence
565
-
566
- Nitro Auth is intentionally stateless in-process. Persist only what your app needs.
242
+ | Option | Applies to | Notes |
243
+ | --- | --- | --- |
244
+ | `scopes` | Google, Microsoft | Requested OAuth scopes |
245
+ | `loginHint` | Google, Microsoft | Prefills account selection when supported |
246
+ | `useOneTap` | Android Google | Enables Credential Manager auto-select |
247
+ | `useSheet` | iOS Google | Uses native sign-in sheet behavior |
248
+ | `forceAccountPicker` | Google | Forces account picker |
249
+ | `useLegacyGoogleSignIn` | Android Google | Uses legacy Google Sign-In path for server auth code |
250
+ | `tenant` | Microsoft | Overrides configured tenant |
251
+ | `prompt` | Microsoft | `login`, `consent`, `select_account`, or `none` |
567
252
 
568
- #### Using react-native-nitro-storage (Recommended)
253
+ ## Incremental Scopes
569
254
 
570
255
  ```ts
571
- import { AuthService, type AuthUser } from "react-native-nitro-auth";
572
- import { createStorageItem, StorageScope } from "react-native-nitro-storage";
256
+ const calendarScope = "https://www.googleapis.com/auth/calendar.readonly";
573
257
 
574
- type AuthSnapshot = {
575
- user: AuthUser | undefined;
576
- scopes: string[];
577
- updatedAt: number | undefined;
578
- };
579
-
580
- const authSnapshotItem = createStorageItem<AuthSnapshot>({
581
- key: "auth_snapshot",
582
- scope: StorageScope.Disk,
583
- defaultValue: {
584
- user: undefined,
585
- scopes: [],
586
- updatedAt: undefined,
587
- },
588
- });
589
-
590
- // Save on auth changes (do not overwrite snapshot with empty user on app refresh)
591
- AuthService.onAuthStateChanged((user) => {
592
- if (!user) return;
593
-
594
- authSnapshotItem.set({
595
- user,
596
- scopes: AuthService.grantedScopes,
597
- updatedAt: Date.now(),
598
- });
599
- });
600
-
601
- // Keep token/expiration fields fresh in persisted snapshot
602
- AuthService.onTokensRefreshed((tokens) => {
603
- authSnapshotItem.set((prev) => {
604
- if (!prev.user) return prev;
605
-
606
- return {
607
- ...prev,
608
- user: {
609
- ...prev.user,
610
- accessToken: tokens.accessToken ?? prev.user.accessToken,
611
- idToken: tokens.idToken ?? prev.user.idToken,
612
- refreshToken: tokens.refreshToken ?? prev.user.refreshToken,
613
- expirationTime: tokens.expirationTime ?? prev.user.expirationTime,
614
- },
615
- updatedAt: Date.now(),
616
- };
617
- });
618
- });
619
-
620
- // Clear on logout
621
- function logout() {
622
- AuthService.logout();
623
- authSnapshotItem.set({
624
- user: undefined,
625
- scopes: [],
626
- updatedAt: undefined,
627
- });
628
- }
258
+ await requestScopes([calendarScope]);
259
+ await revokeScopes([calendarScope]);
629
260
  ```
630
261
 
631
- ### Production Readiness
632
-
633
- Nitro Auth is suitable for production use when configured with provider-accurate expectations:
634
-
635
- - **Google Sign-In**: One-Tap + legacy on Android, native on iOS, popup on web.
636
- - **Apple Sign-In**: iOS + web only.
637
- - **Microsoft (Azure AD / B2C)**: iOS, Android, and web with PKCE/state/nonce protections.
262
+ On Android, incremental Google scope requests use the legacy Google Sign-In APIs because Credential Manager does not expose an equivalent existing-account scope query.
638
263
 
639
- Production checklist:
264
+ ## Storage Model
640
265
 
641
- 1. Configure client IDs, URL schemes, and redirect URIs per platform/provider.
642
- 2. Call `silentRestore()` during app startup to rehydrate provider session state when available.
643
- 3. Persist only app-owned snapshots/tokens (for example with `react-native-nitro-storage`), then clear them on logout.
644
- 4. On Android Google, use `useLegacyGoogleSignIn: true` when backend flows require `serverAuthCode`.
645
- 5. Treat token fields as optional and branch by provider/flow.
646
- 6. Keep web sensitive tokens memory-only unless you explicitly require persistent web tokens (`nitroAuthPersistTokensOnWeb: true`).
647
- 7. Enable logging only in development, and monitor normalized error codes in production.
266
+ Native tokens are kept in memory by design. The package does not persist Microsoft refresh tokens or provider tokens to disk. Your app owns persistence and secure storage policy.
648
267
 
649
- ### Logging & Debugging
650
-
651
- Enable verbose logging to see detailed OAuth flow information in the console:
268
+ For app-managed persistence, store only the minimum state your product needs:
652
269
 
653
270
  ```ts
654
271
  import { AuthService } from "react-native-nitro-auth";
655
272
 
656
- AuthService.setLoggingEnabled(true);
657
- ```
658
-
659
- ### Sync State + Async Tokens
660
-
661
- Nitro Auth provides synchronous access to in-memory state, while token retrieval remains async:
662
-
663
- ```ts
664
- // Quick access to what we have in memory
665
- const user = AuthService.currentUser;
666
- const scopes = AuthService.grantedScopes;
667
-
668
- // Async access ensures fresh tokens (will refresh if expired)
669
- const freshToken = await AuthService.getAccessToken();
273
+ const snapshot = {
274
+ user: AuthService.currentUser,
275
+ scopes: AuthService.grantedScopes,
276
+ updatedAt: Date.now(),
277
+ };
670
278
  ```
671
279
 
672
- ### Standardized Error Codes
673
-
674
- All errors thrown by `AuthService` and `useAuth` are `AuthError` instances with a type-safe `code` field (always a valid `AuthErrorCode`) and an optional `underlyingMessage` with the raw provider string when it differs from the code.
280
+ On web, the default is also memory storage. You can opt into browser storage with:
675
281
 
676
- ```ts
677
- import { AuthError } from "react-native-nitro-auth";
678
-
679
- try {
680
- await login("google");
681
- } catch (e) {
682
- if (e instanceof AuthError) {
683
- switch (e.code) {
684
- case "cancelled":
685
- // User closed the popup/picker
686
- break;
687
- case "network_error":
688
- // Connection issues
689
- break;
690
- default:
691
- console.error(e.code, e.underlyingMessage);
692
- }
693
- }
282
+ ```js
283
+ extra: {
284
+ nitroAuthWebStorage: "session", // "session", "local", or "memory"
285
+ nitroAuthPersistTokensOnWeb: true,
694
286
  }
695
287
  ```
696
288
 
697
- | Error Code | Description |
698
- | ---------------------- | --------------------------------------------------------------- |
699
- | `cancelled` | The user cancelled the sign-in flow or dismissed the popup |
700
- | `timeout` | The login popup/flow timed out |
701
- | `popup_blocked` | The browser blocked the popup window |
702
- | `network_error` | A network or connectivity error occurred |
703
- | `configuration_error` | Missing client IDs, invalid tenant, or misconfigured setup |
704
- | `unsupported_provider` | The provider is not supported on this platform |
705
- | `invalid_state` | PKCE state mismatch — possible CSRF attack |
706
- | `invalid_nonce` | Nonce mismatch in token response — possible replay attack |
707
- | `token_error` | Token exchange or storage failed |
708
- | `no_id_token` | No `id_token` in token response |
709
- | `parse_error` | Failed to parse token response |
710
- | `refresh_failed` | Refresh token flow failed (token may be expired or revoked) |
711
- | `unknown` | An unknown or unmapped error occurred |
712
-
713
- ### Native Error Metadata
714
-
715
- `AuthError` carries the raw provider/native message in `underlyingMessage` when the platform error didn't map to a known code:
289
+ ## Error Contract
716
290
 
717
- ```ts
718
- import { AuthError } from "react-native-nitro-auth";
291
+ All public async APIs throw `AuthError`.
719
292
 
293
+ ```ts
720
294
  try {
721
- await login("google");
295
+ await AuthService.login("microsoft");
722
296
  } catch (e) {
723
- if (e instanceof AuthError) {
724
- // e.code is always a valid AuthErrorCode
725
- // e.underlyingMessage is the original native string (or undefined if it equalled the code)
726
- console.log(e.code, e.underlyingMessage);
297
+ const error = AuthError.from(e);
298
+ switch (error.code) {
299
+ case "cancelled":
300
+ break;
301
+ case "configuration_error":
302
+ break;
303
+ case "token_error":
304
+ break;
305
+ default:
306
+ break;
727
307
  }
728
308
  }
729
309
  ```
730
310
 
731
- The `AuthUser.underlyingError` field carries a raw warning string when the provider returns one alongside a successful result.
732
-
733
- ### Troubleshooting
734
-
735
- - `configuration_error`: verify client IDs, URL schemes, and redirect URIs are set for the current platform. On Android, check that the `microsoftTenant` is a non-empty valid value.
736
- - `invalid_state` or `invalid_nonce`: ensure the redirect URI in your provider console matches your app config exactly. These indicate a potential CSRF or replay attack — do not retry silently.
737
- - `refresh_failed`: the refresh token is likely expired or revoked. Clear the session and prompt the user to sign in again. On Android and web, structured error details from the provider (e.g. `invalid_grant`) are surfaced via `error.underlyingMessage`.
738
- - `hasPlayServices` is false: prompt the user to install/update Google Play Services or disable One-Tap.
739
- - Apple web login fails: confirm `appleWebClientId` is set and your domain is registered with Apple.
740
- - Microsoft Android redirect hangs: confirm the `msauth://` redirect URI in your Azure app matches your package name and client ID.
741
-
742
- ### Automatic Token Refresh
743
-
744
- The `getAccessToken()` method automatically checks if the current token is expired (or about to expire) and triggers a silent refresh if possible:
745
-
746
- ```ts
747
- const { getAccessToken } = useAuth();
748
-
749
- // This will silently refresh if needed!
750
- const token = await getAccessToken();
751
- ```
752
-
753
- ### Offline Access (Server Auth Code)
754
-
755
- 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:
311
+ Known error codes:
756
312
 
757
313
  ```ts
758
- const { user } = useAuth();
314
+ type AuthErrorCode =
315
+ | "cancelled"
316
+ | "timeout"
317
+ | "popup_blocked"
318
+ | "network_error"
319
+ | "configuration_error"
320
+ | "not_signed_in"
321
+ | "operation_in_progress"
322
+ | "unsupported_provider"
323
+ | "invalid_state"
324
+ | "invalid_nonce"
325
+ | "token_error"
326
+ | "no_id_token"
327
+ | "parse_error"
328
+ | "refresh_failed"
329
+ | "unknown";
330
+ ```
331
+
332
+ `underlyingMessage` keeps the raw native or OAuth message when it differs from the stable code.
759
333
 
760
- if (user?.serverAuthCode) {
761
- // Send this to your backend!
762
- await api.verifyGoogleAccess(user.serverAuthCode);
763
- }
764
- ```
765
-
766
- ### Google One-Tap & Sheet
334
+ ## API Reference
767
335
 
768
- Explicitly enable the modern One-Tap flow on Android or the Sign-In Sheet on iOS:
336
+ ### Exports
769
337
 
770
338
  ```ts
771
- await login("google", {
772
- useOneTap: true, // Android
773
- useSheet: true, // iOS
774
- });
339
+ export * from "react-native-nitro-auth";
775
340
  ```
776
341
 
777
- > [!NOTE]
778
- > One-Tap requires Google Play Services. You can check `hasPlayServices` from `useAuth()` and show a fallback UI if needed.
342
+ Main exports:
779
343
 
780
- ### Android Legacy Google Sign-In (Server Auth Code)
344
+ - `useAuth()`
345
+ - `AuthService`
346
+ - `SocialButton`
347
+ - `AuthError`
348
+ - `isAuthErrorCode()`
349
+ - `toAuthErrorCode()`
350
+ - `AuthProvider`
351
+ - `AuthUser`
352
+ - `AuthTokens`
353
+ - `LoginOptions`
781
354
 
782
- Credential Manager is the recommended default on Android, but it **does not return** `serverAuthCode`.
783
- If your backend requires `serverAuthCode`, opt into the legacy flow:
355
+ ### useAuth()
784
356
 
785
357
  ```ts
786
- await login("google", { useLegacyGoogleSignIn: true });
358
+ type UseAuthReturn = {
359
+ user: AuthUser | undefined;
360
+ scopes: string[];
361
+ loading: boolean;
362
+ error: AuthError | undefined;
363
+ hasPlayServices: boolean;
364
+ login(provider: AuthProvider, options?: LoginOptions): Promise<void>;
365
+ logout(): void;
366
+ requestScopes(scopes: string[]): Promise<void>;
367
+ revokeScopes(scopes: string[]): Promise<void>;
368
+ getAccessToken(): Promise<string | undefined>;
369
+ refreshToken(): Promise<AuthTokens>;
370
+ silentRestore(): Promise<void>;
371
+ };
787
372
  ```
788
373
 
789
- ### Force Account Picker
790
-
791
- 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:
374
+ ### AuthUser
792
375
 
793
376
  ```ts
794
- await login("google", {
795
- scopes: ["https://www.googleapis.com/auth/calendar.readonly"],
796
- forceAccountPicker: true, // Always show account picker
797
- });
377
+ type AuthUser = {
378
+ provider: "google" | "apple" | "microsoft";
379
+ email?: string;
380
+ name?: string;
381
+ photo?: string;
382
+ idToken?: string;
383
+ accessToken?: string;
384
+ refreshToken?: string;
385
+ serverAuthCode?: string;
386
+ scopes?: string[];
387
+ expirationTime?: number;
388
+ underlyingError?: string;
389
+ };
798
390
  ```
799
391
 
800
- This is useful for scenarios where:
392
+ ## Example App
801
393
 
802
- - Users want to connect a different Google account for calendar integration
803
- - You need to ensure the user can select any account they've added to their device
804
- - The cached session is interfering with the expected account selection UX
394
+ The example app is the fastest way to verify setup and read a complete integration.
805
395
 
806
- ## API Reference
807
-
808
- ### Package Exports
809
-
810
- ```ts
811
- import {
812
- AuthService,
813
- AuthError,
814
- isAuthErrorCode,
815
- toAuthErrorCode,
816
- SocialButton,
817
- useAuth,
818
- type UseAuthReturn,
819
- type Auth,
820
- type AuthUser,
821
- type AuthTokens,
822
- type AuthProvider,
823
- type AuthErrorCode,
824
- type LoginOptions,
825
- } from "react-native-nitro-auth";
396
+ ```sh
397
+ cp apps/example/.env.example apps/example/.env.local
398
+ bun install
399
+ bun example:prebuild:clean
400
+ bun example:ios
401
+ bun example:android
826
402
  ```
827
403
 
828
- ### Core Types
829
-
830
- | Type | Definition |
831
- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
832
- | `AuthProvider` | `"google" \| "apple" \| "microsoft"` |
833
- | `AuthErrorCode` | `"cancelled" \| "timeout" \| "popup_blocked" \| "network_error" \| "configuration_error" \| "unsupported_provider" \| "invalid_state" \| "invalid_nonce" \| "token_error" \| "no_id_token" \| "parse_error" \| "refresh_failed" \| "unknown"` |
834
- | `MicrosoftPrompt` | `"login" \| "consent" \| "select_account" \| "none"` |
835
-
836
- ### `AuthUser`
837
-
838
- | Field | Type | Description |
839
- | ----------------- | ----------------------- | -------------------------------------------------------------------------- |
840
- | `provider` | `AuthProvider` | Provider that authenticated the user |
841
- | `email` | `string \| undefined` | User email |
842
- | `name` | `string \| undefined` | Display name |
843
- | `photo` | `string \| undefined` | Profile image URL (Google) |
844
- | `idToken` | `string \| undefined` | OIDC ID token |
845
- | `accessToken` | `string \| undefined` | OAuth access token |
846
- | `refreshToken` | `string \| undefined` | OAuth refresh token |
847
- | `serverAuthCode` | `string \| undefined` | Google server auth code (legacy Android flow + backend exchange scenarios) |
848
- | `scopes` | `string[] \| undefined` | Granted scopes for current session |
849
- | `expirationTime` | `number \| undefined` | Expiration timestamp in milliseconds since epoch |
850
- | `underlyingError` | `string \| undefined` | Raw provider/native error message |
851
-
852
- > [!NOTE]
853
- > On Android Google One-Tap/Credential Manager, `idToken` is typically available immediately, while `accessToken` and `expirationTime` may be `undefined` until refresh/exchange flow provides them.
854
-
855
- ### `AuthTokens`
856
-
857
- | Field | Type | Description |
858
- | ---------------- | --------------------- | ----------------------------- |
859
- | `accessToken` | `string \| undefined` | Refreshed access token |
860
- | `idToken` | `string \| undefined` | Refreshed ID token |
861
- | `refreshToken` | `string \| undefined` | Refresh token (if available) |
862
- | `expirationTime` | `number \| undefined` | Optional expiration timestamp |
863
-
864
- ### `LoginOptions`
865
-
866
- | Option | Type | Platform | Description |
867
- | ----------------------- | ----------------- | --------- | --------------------------------------------------------------------------------- |
868
- | `scopes` | `string[]` | All | Requested scopes (defaults are provider-specific) |
869
- | `loginHint` | `string` | All | Prefills account identifier |
870
- | `useOneTap` | `boolean` | Android | Use Credential Manager/One-Tap flow |
871
- | `useSheet` | `boolean` | iOS | Use native Google Sign-In sheet |
872
- | `forceAccountPicker` | `boolean` | All | Always show account chooser |
873
- | `useLegacyGoogleSignIn` | `boolean` | Android | Use legacy Google Sign-In (required when you need `serverAuthCode`) |
874
- | `tenant` | `string` | Microsoft | Tenant (`common`, `organizations`, `consumers`, tenant id, or full authority URL) |
875
- | `prompt` | `MicrosoftPrompt` | Microsoft | Prompt behavior |
876
-
877
- ### `useAuth()`
878
-
879
- ```ts
880
- declare function useAuth(): UseAuthReturn;
881
- ```
404
+ The demo includes:
882
405
 
883
- | Property | Type | Description |
884
- | ----------------- | ------------------------------------------------------------------- | ---------------------------------------------------- |
885
- | `user` | `AuthUser \| undefined` | Current in-memory user |
886
- | `scopes` | `string[]` | Current granted scopes |
887
- | `loading` | `boolean` | `true` while an auth operation is in-flight |
888
- | `error` | `AuthError \| undefined` | Last operation error (typed, safe to switch on `.code`) |
889
- | `hasPlayServices` | `boolean` | Android Play Services availability |
890
- | `login` | `(provider: AuthProvider, options?: LoginOptions) => Promise<void>` | Starts provider login |
891
- | `logout` | `() => void` | Clears current session |
892
- | `requestScopes` | `(scopes: string[]) => Promise<void>` | Requests additional scopes |
893
- | `revokeScopes` | `(scopes: string[]) => Promise<void>` | Revokes scopes in current session |
894
- | `getAccessToken` | `() => Promise<string \| undefined>` | Returns access token, auto-refreshing when supported |
895
- | `refreshToken` | `() => Promise<AuthTokens>` | Explicit refresh |
896
- | `silentRestore` | `() => Promise<void>` | Restores provider SDK session (if available) |
897
-
898
- ### `AuthService`
899
-
900
- Synchronous state + async operations (useful outside React trees).
901
-
902
- #### Readonly state
903
-
904
- | Property | Type | Description |
905
- | ----------------- | ----------------------- | ---------------------------------- |
906
- | `name` | `string` | Hybrid object name (`"Auth"`) |
907
- | `currentUser` | `AuthUser \| undefined` | Current user snapshot |
908
- | `grantedScopes` | `string[]` | Current scope snapshot |
909
- | `hasPlayServices` | `boolean` | Android Play Services availability |
910
-
911
- #### Methods
912
-
913
- | Method | Signature | Description |
914
- | -------------------- | -------------------------------------------------------- | -------------------------------- |
915
- | `login` | `(provider, options?) => Promise<void>` | Starts login |
916
- | `logout` | `() => void` | Clears session |
917
- | `requestScopes` | `(scopes) => Promise<void>` | Incremental auth |
918
- | `revokeScopes` | `(scopes) => Promise<void>` | Scope revoke |
919
- | `getAccessToken` | `() => Promise<string \| undefined>` | Access token getter with refresh |
920
- | `refreshToken` | `() => Promise<AuthTokens>` | Explicit token refresh |
921
- | `silentRestore` | `() => Promise<void>` | Restore provider SDK session |
922
- | `onAuthStateChanged` | `(callback: (user?: AuthUser) => void) => () => void` | Auth change listener |
923
- | `onTokensRefreshed` | `(callback: (tokens: AuthTokens) => void) => () => void` | Token refresh listener |
924
- | `setLoggingEnabled` | `(enabled: boolean) => void` | Debug logging toggle |
925
- | `dispose` | `() => void` | Disposes hybrid object |
926
- | `equals` | `(other: unknown) => boolean` | Hybrid object identity check |
927
-
928
- ### Storage Contract
929
-
930
- Nitro Auth does not provide persistence APIs. Persist the auth data you need in your app layer (for example with `react-native-nitro-storage`, MMKV, Keychain wrappers, or backend-issued sessions).
931
- There is no public `setStorageAdapter`/`setJSStorageAdapter` API in this package.
932
-
933
- ### `SocialButton`
934
-
935
- | Prop | Type | Default | Description |
936
- | -------------- | ---------------------------------------------- | ----------- | ------------------------------------------- |
937
- | `provider` | `AuthProvider` | required | Provider |
938
- | `variant` | `"primary" \| "outline" \| "white" \| "black"` | `"primary"` | Visual style |
939
- | `borderRadius` | `number` | `8` | Border radius |
940
- | `style` | `ViewStyle` | `undefined` | Container style override |
941
- | `textStyle` | `TextStyle` | `undefined` | Text style override |
942
- | `disabled` | `boolean` | `false` | Disabled state |
943
- | `onPress` | `() => void` | `undefined` | Custom press handler (skips built-in login) |
944
- | `onSuccess` | `(user: AuthUser) => void` | `undefined` | Called after successful default login |
945
- | `onError` | `(error: unknown) => void` | `undefined` | Called when default login fails |
946
-
947
- ### Config Plugin API (`app.json` / `app.config.js`)
948
-
949
- `plugins: [["react-native-nitro-auth", { ios: {...}, android: {...} }]]`
950
-
951
- #### iOS plugin options
952
-
953
- | Option | Type | Description |
954
- | ---------------------- | --------- | -------------------------------------------- |
955
- | `googleClientId` | `string` | Writes `GIDClientID` |
956
- | `googleServerClientId` | `string` | Writes `GIDServerClientID` |
957
- | `googleUrlScheme` | `string` | Adds Google URL scheme |
958
- | `appleSignIn` | `boolean` | Enables Apple Sign-In entitlement |
959
- | `microsoftClientId` | `string` | Writes `MSALClientID` + MSAL redirect scheme |
960
- | `microsoftTenant` | `string` | Writes `MSALTenant` |
961
- | `microsoftB2cDomain` | `string` | Writes `MSALB2cDomain` |
962
-
963
- #### Android plugin options
964
-
965
- | Option | Type | Description |
966
- | -------------------- | -------- | ---------------------------------------------------------------- |
967
- | `googleClientId` | `string` | Writes `nitro_auth_google_client_id` string |
968
- | `microsoftClientId` | `string` | Writes `nitro_auth_microsoft_client_id` + redirect intent filter |
969
- | `microsoftTenant` | `string` | Writes `nitro_auth_microsoft_tenant` |
970
- | `microsoftB2cDomain` | `string` | Writes `nitro_auth_microsoft_b2c_domain` |
971
-
972
- ### Web runtime config (`expo.extra`)
973
-
974
- | Key | Type | Default | Description |
975
- | ----------------------------- | ---------------------------------- | ----------- | --------------------------------------------------------- |
976
- | `googleWebClientId` | `string` | `undefined` | Google web OAuth client id |
977
- | `microsoftClientId` | `string` | `undefined` | Microsoft app client id |
978
- | `microsoftTenant` | `string` | `"common"` | Microsoft tenant/authority |
979
- | `microsoftB2cDomain` | `string` | `undefined` | B2C domain when applicable |
980
- | `appleWebClientId` | `string` | `undefined` | Apple Service ID |
981
- | `nitroAuthWebStorage` | `"session" \| "local" \| "memory"` | `"session"` | Storage for non-sensitive web cache |
982
- | `nitroAuthPersistTokensOnWeb` | `boolean` | `false` | Persist sensitive tokens on web storage instead of memory |
983
-
984
- ### `AuthError`
985
-
986
- All thrown errors from `AuthService` and `useAuth` are `AuthError` instances.
406
+ - Provider cards for Google, Apple, and Microsoft.
407
+ - Token and scope operations.
408
+ - Silent restore and account picker actions.
409
+ - App-owned disk snapshot example with `react-native-nitro-storage`.
410
+ - Runtime smoke tests for the public API.
987
411
 
988
- ```ts
989
- class AuthError extends Error {
990
- readonly code: AuthErrorCode; // Always a valid AuthErrorCode — safe to switch on
991
- readonly underlyingMessage?: string; // Raw native/platform string when it differs from code
992
- static from(e: unknown): AuthError; // Wraps any value; passes AuthError instances through
993
- }
412
+ ## Troubleshooting
994
413
 
995
- function isAuthErrorCode(value: string): value is AuthErrorCode;
996
- function toAuthErrorCode(raw: string): AuthErrorCode; // Returns "unknown" for unrecognized strings
997
- ```
414
+ | Symptom | Check |
415
+ | --- | --- |
416
+ | `configuration_error` on Google | Client ID is missing or wrong for the current platform |
417
+ | Google works in debug but not release | Add release SHA-1/SHA-256 fingerprints to Google Cloud Console |
418
+ | Android `hasPlayServices` is false | Use an emulator image with Google Play Services |
419
+ | Apple email/name missing | Apple only returns these fields on first authorization |
420
+ | Microsoft `invalid_state` | Redirect URI or app resume path is wrong, or an old auth redirect completed late |
421
+ | Microsoft `token_error` | Check tenant, client ID, redirect URI, and requested scopes |
422
+ | Web popup blocked | Call `login()` from a user gesture such as a button press |
423
+ | `operation_in_progress` | A provider flow is already active; wait for it to finish or sign out |
998
424
 
999
- | `code` | Meaning |
1000
- | ---------------------- | ---------------------------------------------- |
1001
- | `cancelled` | User cancelled the login flow |
1002
- | `timeout` | Provider popup did not complete before timeout |
1003
- | `popup_blocked` | Browser blocked popup opening |
1004
- | `network_error` | Network failure |
1005
- | `configuration_error` | Missing/invalid provider configuration |
1006
- | `unsupported_provider` | Provider not supported on this platform |
1007
- | `invalid_state` | PKCE state mismatch (possible CSRF) |
1008
- | `invalid_nonce` | Nonce mismatch in token response |
1009
- | `token_error` | Token exchange failed |
1010
- | `no_id_token` | No `id_token` in token response |
1011
- | `parse_error` | Failed to parse token response |
1012
- | `refresh_failed` | Refresh token flow failed |
1013
- | `unknown` | Unrecognized error |
1014
-
1015
- ## Quality Gates
1016
-
1017
- From monorepo root:
1018
-
1019
- ```bash
1020
- bun run format:check
1021
- bun run lint
1022
- bun run typecheck
1023
- ```
425
+ ## Production Notes
1024
426
 
1025
- Workspace-local commands:
427
+ - Verify ID tokens on your backend. Client-side JWT parsing is for display and expiration hints only.
428
+ - Store refresh tokens only in storage your app explicitly owns and secures.
429
+ - Keep Google debug and release signing fingerprints in sync with your OAuth clients.
430
+ - Add provider-specific redirect URIs for every environment.
431
+ - Run the example app on iOS and Android before shipping provider config changes.
1026
432
 
1027
- ```bash
1028
- # apps/example
1029
- bun run format
1030
- bun run lint
1031
- bun run typecheck
433
+ ## Release Checks
1032
434
 
1033
- # packages/react-native-nitro-auth
1034
- bun run format
1035
- bun run lint
1036
- bun run typecheck
1037
- bun run test
435
+ ```sh
436
+ bun run codegen
437
+ bun run build
438
+ bun run check
439
+ bun run test:cpp
440
+ bun example:prebuild:clean
441
+ bun example:ios
442
+ bun example:android
1038
443
  ```
1039
444
 
1040
- ## Platform Support
1041
-
1042
- | Feature | iOS | Android | Web |
1043
- | -------------------------- | --- | ------- | --- |
1044
- | Google Sign-In | ✅ | ✅ | ✅ |
1045
- | Apple Sign-In | ✅ | ❌ | ✅ |
1046
- | Microsoft Sign-In | ✅ | ✅ | ✅ |
1047
- | Custom OAuth Scopes | ✅ | ✅ | ✅ |
1048
- | Incremental Authorization | ✅ | ✅ | ✅ |
1049
- | Token Refresh | ✅ | ✅ | ✅ |
1050
- | Native Session Persistence | ❌ | ❌ | — |
1051
- | Web Auth Snapshot Cache | — | — | ✅ |
1052
- | App-Owned Persistence | ✅ | ✅ | ✅ |
1053
- | Auto-Refresh | ✅ | ✅ | ✅ |
1054
- | Native C++ Performance | ✅ | ✅ | — |
1055
-
1056
- ## Architecture
1057
-
1058
- `react-native-nitro-auth` is built using [Nitro Modules](https://github.com/mrousavy/nitro). Unlike traditional React Native modules, Nitro uses JSI to provide:
1059
-
1060
- - **Zero-bridge overhead**: Calls are made directly from JS to C++.
1061
- - **Type safety**: TypeScript types are automatically kept in sync with native C++ and Swift/Kotlin code.
1062
- - **Synchronous access**: Properties like `currentUser` are accessible synchronously without async overhead.
1063
-
1064
445
  ## License
1065
446
 
1066
447
  MIT