react-native-nitro-auth 0.5.7 → 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.
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,841 +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
- ```
329
-
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
- ```
349
-
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.
103
+ ### Plugin Options
351
104
 
352
- - `nitroAuthWebStorage`: `"session"` (default), `"local"`, or `"memory"`.
353
- - `nitroAuthPersistTokensOnWeb`: `false` by default (recommended). Set `true` only if you need cross-reload token persistence.
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 |
354
118
 
355
- ## Quick Start
119
+ ## Provider Setup
356
120
 
357
- ### Using the Hook
121
+ ### Google Sign-In
358
122
 
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
- }
123
+ Create OAuth clients in Google Cloud Console:
374
124
 
375
- return (
376
- <View>
377
- {error && <Text style={{ color: "red" }}>{error.message}</Text>}
378
- {!hasPlayServices && <Text>Please install Google Play Services</Text>}
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.
379
128
 
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
- ```
399
-
400
- ### Microsoft Login Options
401
-
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
- ```
129
+ Use the iOS reversed client ID as `GOOGLE_IOS_URL_SCHEME`.
411
130
 
412
- **B2C example:**
413
-
414
- ```tsx
415
- await login("microsoft", {
416
- tenant: "your-tenant.onmicrosoft.com/B2C_1_signin",
417
- scopes: ["openid", "email", "profile", "offline_access"],
418
- });
419
- ```
131
+ ### Apple Sign-In
420
132
 
421
- ## Migration from @react-native-google-signin/google-signin
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.
422
134
 
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.
135
+ Apple Sign-In is supported on iOS and web. It is intentionally reported as `unsupported_provider` on Android.
424
136
 
425
- ### 1) Replace the dependency
137
+ ### Microsoft Entra ID
426
138
 
427
- ```bash
428
- bun remove @react-native-google-signin/google-signin
429
- bun add react-native-nitro-auth react-native-nitro-modules
430
- ```
139
+ Create an app registration in Microsoft Entra ID and add redirect URIs:
431
140
 
432
- ### 2) Move configuration to the Nitro Auth plugin
141
+ - iOS: `msauth.<bundleIdentifier>://auth`
142
+ - Android: `msauth://<androidPackage>/<clientId>`
143
+ - Web: your web origin, for example `https://app.example.com`
433
144
 
434
- Nitro Auth does not use `GoogleSignin.configure(...)`. Instead, set your client IDs via the config plugin (Expo) or native config (bare).
145
+ Use `microsoftTenant` for `common`, `organizations`, `consumers`, a tenant ID, or a B2C policy path. Use `microsoftB2cDomain` for Azure AD B2C.
435
146
 
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
166
 
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
-
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
-
549
- Request new scopes when you need them without logging the user out:
550
234
 
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";
573
-
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
- });
256
+ const calendarScope = "https://www.googleapis.com/auth/calendar.readonly";
589
257
 
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
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.
632
263
 
633
- Nitro Auth is suitable for production use when configured with provider-accurate expectations:
264
+ ## Storage Model
634
265
 
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.
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.
638
267
 
639
- Production checklist:
640
-
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.
648
-
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.
675
-
676
- ```ts
677
- import { AuthError } from "react-native-nitro-auth";
280
+ On web, the default is also memory storage. You can opt into browser storage with:
678
281
 
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
- | `not_signed_in` | The operation requires an authenticated user/session |
705
- | `operation_in_progress`| Another auth flow of the same kind is already running |
706
- | `unsupported_provider` | The provider is not supported on this platform |
707
- | `invalid_state` | PKCE state mismatch — possible CSRF attack |
708
- | `invalid_nonce` | Nonce mismatch in token response — possible replay attack |
709
- | `token_error` | Token exchange or storage failed |
710
- | `no_id_token` | No `id_token` in token response |
711
- | `parse_error` | Failed to parse token response |
712
- | `refresh_failed` | Refresh token flow failed (token may be expired or revoked) |
713
- | `unknown` | An unknown or unmapped error occurred |
714
-
715
- ### Native Error Metadata
716
-
717
- `AuthError` carries the raw provider/native message in `underlyingMessage` when the platform error didn't map to a known code:
289
+ ## Error Contract
718
290
 
719
- ```ts
720
- import { AuthError } from "react-native-nitro-auth";
291
+ All public async APIs throw `AuthError`.
721
292
 
293
+ ```ts
722
294
  try {
723
- await login("google");
295
+ await AuthService.login("microsoft");
724
296
  } catch (e) {
725
- if (e instanceof AuthError) {
726
- // e.code is always a valid AuthErrorCode
727
- // e.underlyingMessage is the original native string (or undefined if it equalled the code)
728
- 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;
729
307
  }
730
308
  }
731
309
  ```
732
310
 
733
- The `AuthUser.underlyingError` field carries a raw warning string when the provider returns one alongside a successful result.
734
-
735
- ### Troubleshooting
736
-
737
- - `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.
738
- - `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.
739
- - `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`.
740
- - `hasPlayServices` is false: prompt the user to install/update Google Play Services or disable One-Tap.
741
- - Apple web login fails: confirm `appleWebClientId` is set and your domain is registered with Apple.
742
- - Microsoft Android redirect hangs: confirm the `msauth://` redirect URI in your Azure app matches your package name and client ID.
743
-
744
- ### Automatic Token Refresh
745
-
746
- The `getAccessToken()` method automatically checks if the current token is expired (or about to expire) and triggers a silent refresh if possible:
747
-
748
- ```ts
749
- const { getAccessToken } = useAuth();
750
-
751
- // This will silently refresh if needed!
752
- const token = await getAccessToken();
753
- ```
754
-
755
- ### Offline Access (Server Auth Code)
756
-
757
- 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:
758
312
 
759
313
  ```ts
760
- 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.
761
333
 
762
- if (user?.serverAuthCode) {
763
- // Send this to your backend!
764
- await api.verifyGoogleAccess(user.serverAuthCode);
765
- }
766
- ```
767
-
768
- ### Google One-Tap & Sheet
334
+ ## API Reference
769
335
 
770
- Explicitly enable the modern One-Tap flow on Android or the Sign-In Sheet on iOS:
336
+ ### Exports
771
337
 
772
338
  ```ts
773
- await login("google", {
774
- useOneTap: true, // Android
775
- useSheet: true, // iOS
776
- });
339
+ export * from "react-native-nitro-auth";
777
340
  ```
778
341
 
779
- > [!NOTE]
780
- > One-Tap requires Google Play Services. You can check `hasPlayServices` from `useAuth()` and show a fallback UI if needed.
342
+ Main exports:
781
343
 
782
- ### 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`
783
354
 
784
- Credential Manager is the recommended default on Android, but it **does not return** `serverAuthCode`.
785
- If your backend requires `serverAuthCode`, opt into the legacy flow:
355
+ ### useAuth()
786
356
 
787
357
  ```ts
788
- 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
+ };
789
372
  ```
790
373
 
791
- ### Force Account Picker
792
-
793
- 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
794
375
 
795
376
  ```ts
796
- await login("google", {
797
- scopes: ["https://www.googleapis.com/auth/calendar.readonly"],
798
- forceAccountPicker: true, // Always show account picker
799
- });
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
+ };
800
390
  ```
801
391
 
802
- This is useful for scenarios where:
803
-
804
- - Users want to connect a different Google account for calendar integration
805
- - You need to ensure the user can select any account they've added to their device
806
- - The cached session is interfering with the expected account selection UX
392
+ ## Example App
807
393
 
808
- On Android, `forceAccountPicker` routes through the legacy Google Sign-In chooser to guarantee the account picker appears. Credential Manager / One-Tap remains the default when the chooser is not forced.
394
+ The example app is the fastest way to verify setup and read a complete integration.
809
395
 
810
- ## API Reference
811
-
812
- ### Package Exports
813
-
814
- ```ts
815
- import {
816
- AuthService,
817
- AuthError,
818
- isAuthErrorCode,
819
- toAuthErrorCode,
820
- SocialButton,
821
- useAuth,
822
- type UseAuthReturn,
823
- type Auth,
824
- type AuthUser,
825
- type AuthTokens,
826
- type AuthProvider,
827
- type AuthErrorCode,
828
- type LoginOptions,
829
- } 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
830
402
  ```
831
403
 
832
- ### Core Types
833
-
834
- | Type | Definition |
835
- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
836
- | `AuthProvider` | `"google" \| "apple" \| "microsoft"` |
837
- | `AuthErrorCode` | `"cancelled" \| "timeout" \| "popup_blocked" \| "network_error" \| "configuration_error" \| "not_signed_in" \| "operation_in_progress" \| "unsupported_provider" \| "invalid_state" \| "invalid_nonce" \| "token_error" \| "no_id_token" \| "parse_error" \| "refresh_failed" \| "unknown"` |
838
- | `MicrosoftPrompt` | `"login" \| "consent" \| "select_account" \| "none"` |
839
-
840
- ### `AuthUser`
841
-
842
- | Field | Type | Description |
843
- | ----------------- | ----------------------- | -------------------------------------------------------------------------- |
844
- | `provider` | `AuthProvider` | Provider that authenticated the user |
845
- | `email` | `string \| undefined` | User email |
846
- | `name` | `string \| undefined` | Display name |
847
- | `photo` | `string \| undefined` | Profile image URL (Google) |
848
- | `idToken` | `string \| undefined` | OIDC ID token |
849
- | `accessToken` | `string \| undefined` | OAuth access token |
850
- | `refreshToken` | `string \| undefined` | OAuth refresh token |
851
- | `serverAuthCode` | `string \| undefined` | Google server auth code (legacy Android flow + backend exchange scenarios) |
852
- | `scopes` | `string[] \| undefined` | Granted scopes for current session |
853
- | `expirationTime` | `number \| undefined` | Expiration timestamp in milliseconds since epoch |
854
- | `underlyingError` | `string \| undefined` | Raw provider/native error message |
855
-
856
- > [!NOTE]
857
- > 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.
858
-
859
- ### `AuthTokens`
860
-
861
- | Field | Type | Description |
862
- | ---------------- | --------------------- | ----------------------------- |
863
- | `accessToken` | `string \| undefined` | Refreshed access token |
864
- | `idToken` | `string \| undefined` | Refreshed ID token |
865
- | `refreshToken` | `string \| undefined` | Refresh token (if available) |
866
- | `expirationTime` | `number \| undefined` | Optional expiration timestamp |
867
-
868
- ### `LoginOptions`
869
-
870
- | Option | Type | Platform | Description |
871
- | ----------------------- | ----------------- | --------- | --------------------------------------------------------------------------------- |
872
- | `scopes` | `string[]` | All | Requested scopes (defaults are provider-specific) |
873
- | `loginHint` | `string` | All | Prefills account identifier |
874
- | `useOneTap` | `boolean` | Android | Use Credential Manager/One-Tap flow |
875
- | `useSheet` | `boolean` | iOS | Use native Google Sign-In sheet |
876
- | `forceAccountPicker` | `boolean` | All | Always show account chooser; Android Google uses the legacy chooser path |
877
- | `useLegacyGoogleSignIn` | `boolean` | Android | Use legacy Google Sign-In (required when you need `serverAuthCode`) |
878
- | `tenant` | `string` | Microsoft | Tenant (`common`, `organizations`, `consumers`, tenant id, or full authority URL) |
879
- | `prompt` | `MicrosoftPrompt` | Microsoft | Prompt behavior |
880
-
881
- ### `useAuth()`
882
-
883
- ```ts
884
- declare function useAuth(): UseAuthReturn;
885
- ```
404
+ The demo includes:
886
405
 
887
- | Property | Type | Description |
888
- | ----------------- | ------------------------------------------------------------------- | ---------------------------------------------------- |
889
- | `user` | `AuthUser \| undefined` | Current in-memory user |
890
- | `scopes` | `string[]` | Current granted scopes |
891
- | `loading` | `boolean` | `true` while an auth operation is in-flight |
892
- | `error` | `AuthError \| undefined` | Last operation error (typed, safe to switch on `.code`) |
893
- | `hasPlayServices` | `boolean` | Android Play Services availability |
894
- | `login` | `(provider: AuthProvider, options?: LoginOptions) => Promise<void>` | Starts provider login |
895
- | `logout` | `() => void` | Clears current session |
896
- | `requestScopes` | `(scopes: string[]) => Promise<void>` | Requests additional scopes |
897
- | `revokeScopes` | `(scopes: string[]) => Promise<void>` | Revokes scopes in current session |
898
- | `getAccessToken` | `() => Promise<string \| undefined>` | Returns access token, auto-refreshing when supported |
899
- | `refreshToken` | `() => Promise<AuthTokens>` | Explicit refresh |
900
- | `silentRestore` | `() => Promise<void>` | Restores provider SDK session (if available) |
901
-
902
- ### `AuthService`
903
-
904
- Synchronous state + async operations (useful outside React trees).
905
-
906
- #### Readonly state
907
-
908
- | Property | Type | Description |
909
- | ----------------- | ----------------------- | ---------------------------------- |
910
- | `name` | `string` | Hybrid object name (`"Auth"`) |
911
- | `currentUser` | `AuthUser \| undefined` | Current user snapshot |
912
- | `grantedScopes` | `string[]` | Current scope snapshot |
913
- | `hasPlayServices` | `boolean` | Android Play Services availability |
914
-
915
- #### Methods
916
-
917
- | Method | Signature | Description |
918
- | -------------------- | -------------------------------------------------------- | -------------------------------- |
919
- | `login` | `(provider, options?) => Promise<void>` | Starts login |
920
- | `logout` | `() => void` | Clears session |
921
- | `requestScopes` | `(scopes) => Promise<void>` | Incremental auth |
922
- | `revokeScopes` | `(scopes) => Promise<void>` | Scope revoke |
923
- | `getAccessToken` | `() => Promise<string \| undefined>` | Access token getter with refresh |
924
- | `refreshToken` | `() => Promise<AuthTokens>` | Explicit token refresh |
925
- | `silentRestore` | `() => Promise<void>` | Restore provider SDK session |
926
- | `onAuthStateChanged` | `(callback: (user?: AuthUser) => void) => () => void` | Auth change listener |
927
- | `onTokensRefreshed` | `(callback: (tokens: AuthTokens) => void) => () => void` | Token refresh listener |
928
- | `setLoggingEnabled` | `(enabled: boolean) => void` | Debug logging toggle |
929
- | `dispose` | `() => void` | Disposes hybrid object |
930
- | `equals` | `(other: unknown) => boolean` | Hybrid object identity check |
931
-
932
- ### Storage Contract
933
-
934
- 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).
935
- There is no public `setStorageAdapter`/`setJSStorageAdapter` API in this package.
936
-
937
- ### `SocialButton`
938
-
939
- | Prop | Type | Default | Description |
940
- | -------------- | ---------------------------------------------- | ----------- | ------------------------------------------- |
941
- | `provider` | `AuthProvider` | required | Provider |
942
- | `variant` | `"primary" \| "outline" \| "white" \| "black"` | `"primary"` | Visual style |
943
- | `borderRadius` | `number` | `8` | Border radius |
944
- | `style` | `ViewStyle` | `undefined` | Container style override |
945
- | `textStyle` | `TextStyle` | `undefined` | Text style override |
946
- | `disabled` | `boolean` | `false` | Disabled state |
947
- | `onPress` | `() => void` | `undefined` | Custom press handler (skips built-in login) |
948
- | `onSuccess` | `(user: AuthUser) => void` | `undefined` | Called after successful default login |
949
- | `onError` | `(error: unknown) => void` | `undefined` | Called when default login fails |
950
-
951
- ### Config Plugin API (`app.json` / `app.config.js`)
952
-
953
- `plugins: [["react-native-nitro-auth", { ios: {...}, android: {...} }]]`
954
-
955
- #### iOS plugin options
956
-
957
- | Option | Type | Description |
958
- | ---------------------- | --------- | -------------------------------------------- |
959
- | `googleClientId` | `string` | Writes `GIDClientID` |
960
- | `googleServerClientId` | `string` | Writes `GIDServerClientID` |
961
- | `googleUrlScheme` | `string` | Adds Google URL scheme |
962
- | `appleSignIn` | `boolean` | Enables Apple Sign-In entitlement |
963
- | `microsoftClientId` | `string` | Writes `MSALClientID` + MSAL redirect scheme |
964
- | `microsoftTenant` | `string` | Writes `MSALTenant` |
965
- | `microsoftB2cDomain` | `string` | Writes `MSALB2cDomain` |
966
-
967
- #### Android plugin options
968
-
969
- | Option | Type | Description |
970
- | -------------------- | -------- | ---------------------------------------------------------------- |
971
- | `googleClientId` | `string` | Writes `nitro_auth_google_client_id` string |
972
- | `microsoftClientId` | `string` | Writes `nitro_auth_microsoft_client_id` + redirect intent filter |
973
- | `microsoftTenant` | `string` | Writes `nitro_auth_microsoft_tenant` |
974
- | `microsoftB2cDomain` | `string` | Writes `nitro_auth_microsoft_b2c_domain` |
975
-
976
- ### Web runtime config (`expo.extra`)
977
-
978
- | Key | Type | Default | Description |
979
- | ----------------------------- | ---------------------------------- | ----------- | --------------------------------------------------------- |
980
- | `googleWebClientId` | `string` | `undefined` | Google web OAuth client id |
981
- | `microsoftClientId` | `string` | `undefined` | Microsoft app client id |
982
- | `microsoftTenant` | `string` | `"common"` | Microsoft tenant/authority |
983
- | `microsoftB2cDomain` | `string` | `undefined` | B2C domain when applicable |
984
- | `appleWebClientId` | `string` | `undefined` | Apple Service ID |
985
- | `nitroAuthWebStorage` | `"session" \| "local" \| "memory"` | `"session"` | Storage for non-sensitive web cache |
986
- | `nitroAuthPersistTokensOnWeb` | `boolean` | `false` | Persist sensitive tokens on web storage instead of memory |
987
-
988
- ### `AuthError`
989
-
990
- 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.
991
411
 
992
- ```ts
993
- class AuthError extends Error {
994
- readonly code: AuthErrorCode; // Always a valid AuthErrorCode — safe to switch on
995
- readonly underlyingMessage?: string; // Raw native/platform string when it differs from code
996
- static from(e: unknown): AuthError; // Wraps any value; passes AuthError instances through
997
- }
412
+ ## Troubleshooting
998
413
 
999
- function isAuthErrorCode(value: string): value is AuthErrorCode;
1000
- function toAuthErrorCode(raw: string): AuthErrorCode; // Returns "unknown" for unrecognized strings
1001
- ```
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 |
1002
424
 
1003
- | `code` | Meaning |
1004
- | ---------------------- | ---------------------------------------------- |
1005
- | `cancelled` | User cancelled the login flow |
1006
- | `timeout` | Provider popup did not complete before timeout |
1007
- | `popup_blocked` | Browser blocked popup opening |
1008
- | `network_error` | Network failure |
1009
- | `configuration_error` | Missing/invalid provider configuration |
1010
- | `not_signed_in` | Operation requires an active authenticated user |
1011
- | `operation_in_progress`| Another auth flow is already running |
1012
- | `unsupported_provider` | Provider not supported on this platform |
1013
- | `invalid_state` | PKCE state mismatch (possible CSRF) |
1014
- | `invalid_nonce` | Nonce mismatch in token response |
1015
- | `token_error` | Token exchange failed |
1016
- | `no_id_token` | No `id_token` in token response |
1017
- | `parse_error` | Failed to parse token response |
1018
- | `refresh_failed` | Refresh token flow failed |
1019
- | `unknown` | Unrecognized error |
1020
-
1021
- ## Quality Gates
1022
-
1023
- From monorepo root:
1024
-
1025
- ```bash
1026
- bun run format:check
1027
- bun run lint
1028
- bun run typecheck
1029
- ```
425
+ ## Production Notes
1030
426
 
1031
- 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.
1032
432
 
1033
- ```bash
1034
- # apps/example
1035
- bun run format
1036
- bun run lint
1037
- bun run typecheck
433
+ ## Release Checks
1038
434
 
1039
- # packages/react-native-nitro-auth
1040
- bun run format
1041
- bun run lint
1042
- bun run typecheck
1043
- 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
1044
443
  ```
1045
444
 
1046
- ## Platform Support
1047
-
1048
- | Feature | iOS | Android | Web |
1049
- | -------------------------- | --- | ------- | --- |
1050
- | Google Sign-In | ✅ | ✅ | ✅ |
1051
- | Apple Sign-In | ✅ | ❌ | ✅ |
1052
- | Microsoft Sign-In | ✅ | ✅ | ✅ |
1053
- | Custom OAuth Scopes | ✅ | ✅ | ✅ |
1054
- | Incremental Authorization | ✅ | ✅ | ✅ |
1055
- | Token Refresh | ✅ | ✅ | ✅ |
1056
- | Native Session Persistence | ❌ | ❌ | — |
1057
- | Web Auth Snapshot Cache | — | — | ✅ |
1058
- | App-Owned Persistence | ✅ | ✅ | ✅ |
1059
- | Auto-Refresh | ✅ | ✅ | ✅ |
1060
- | Native C++ Performance | ✅ | ✅ | — |
1061
-
1062
- ## Architecture
1063
-
1064
- `react-native-nitro-auth` is built using [Nitro Modules](https://github.com/mrousavy/nitro). Unlike traditional React Native modules, Nitro uses JSI to provide:
1065
-
1066
- - **Zero-bridge overhead**: Calls are made directly from JS to C++.
1067
- - **Type safety**: TypeScript types are automatically kept in sync with native C++ and Swift/Kotlin code.
1068
- - **Synchronous access**: Properties like `currentUser` are accessible synchronously without async overhead.
1069
-
1070
445
  ## License
1071
446
 
1072
447
  MIT