react-native-nitro-auth 0.5.7 → 0.5.9

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 (54) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +295 -906
  3. package/android/src/main/java/com/auth/AuthAdapter.kt +22 -6
  4. package/cpp/HybridAuth.cpp +175 -39
  5. package/cpp/HybridAuth.hpp +2 -0
  6. package/ios/AuthAdapter.swift +2 -2
  7. package/lib/commonjs/Auth.web.js +141 -73
  8. package/lib/commonjs/Auth.web.js.map +1 -1
  9. package/lib/commonjs/create-auth-service.js +71 -0
  10. package/lib/commonjs/create-auth-service.js.map +1 -0
  11. package/lib/commonjs/service.js +2 -79
  12. package/lib/commonjs/service.js.map +1 -1
  13. package/lib/commonjs/service.web.js +2 -79
  14. package/lib/commonjs/service.web.js.map +1 -1
  15. package/lib/commonjs/use-auth.js +6 -3
  16. package/lib/commonjs/use-auth.js.map +1 -1
  17. package/lib/commonjs/utils/auth-error.js +8 -1
  18. package/lib/commonjs/utils/auth-error.js.map +1 -1
  19. package/lib/module/Auth.web.js +141 -73
  20. package/lib/module/Auth.web.js.map +1 -1
  21. package/lib/module/create-auth-service.js +67 -0
  22. package/lib/module/create-auth-service.js.map +1 -0
  23. package/lib/module/service.js +2 -79
  24. package/lib/module/service.js.map +1 -1
  25. package/lib/module/service.web.js +2 -79
  26. package/lib/module/service.web.js.map +1 -1
  27. package/lib/module/use-auth.js +6 -3
  28. package/lib/module/use-auth.js.map +1 -1
  29. package/lib/module/utils/auth-error.js +8 -1
  30. package/lib/module/utils/auth-error.js.map +1 -1
  31. package/lib/typescript/commonjs/Auth.web.d.ts +4 -2
  32. package/lib/typescript/commonjs/Auth.web.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/create-auth-service.d.ts +5 -0
  34. package/lib/typescript/commonjs/create-auth-service.d.ts.map +1 -0
  35. package/lib/typescript/commonjs/service.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/service.web.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/use-auth.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/utils/auth-error.d.ts.map +1 -1
  39. package/lib/typescript/module/Auth.web.d.ts +4 -2
  40. package/lib/typescript/module/Auth.web.d.ts.map +1 -1
  41. package/lib/typescript/module/create-auth-service.d.ts +5 -0
  42. package/lib/typescript/module/create-auth-service.d.ts.map +1 -0
  43. package/lib/typescript/module/service.d.ts.map +1 -1
  44. package/lib/typescript/module/service.web.d.ts.map +1 -1
  45. package/lib/typescript/module/use-auth.d.ts.map +1 -1
  46. package/lib/typescript/module/utils/auth-error.d.ts.map +1 -1
  47. package/package.json +12 -9
  48. package/react-native-nitro-auth.podspec +1 -0
  49. package/src/Auth.web.ts +261 -102
  50. package/src/create-auth-service.ts +97 -0
  51. package/src/service.ts +3 -101
  52. package/src/service.web.ts +3 -101
  53. package/src/use-auth.ts +7 -3
  54. package/src/utils/auth-error.ts +10 -1
package/README.md CHANGED
@@ -1,213 +1,71 @@
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.
16
+ - Consistent `AuthError` mapping for async `AuthService` failures on native and web.
12
17
 
13
- Nitro Auth is designed to replace legacy modules like `@react-native-google-signin/google-signin` with a modern, high-performance architecture.
18
+ ## Choose Your Path
14
19
 
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** |
20
+ | Need | Use |
21
+ | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
22
+ | Google, Apple, or Microsoft sign-in in an Expo or React Native app | `react-native-nitro-auth` |
23
+ | Generic OAuth or OIDC provider not covered by this package | `expo-auth-session` or `react-native-app-auth` |
24
+ | Firebase user management, password auth, MFA, and hosted auth platform | `@react-native-firebase/auth`, Auth0, Authgear, or your IDaaS SDK |
25
+ | Server-side session validation | Your backend; client JWT decode is display-only |
22
26
 
23
- ## Features
27
+ ## Install
24
28
 
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
29
+ ```sh
69
30
  bun add react-native-nitro-auth react-native-nitro-modules
70
31
  ```
71
32
 
72
- ### Requirements
73
-
74
- | Dependency | Version |
75
- | ---------------------------- | ------------ |
76
- | `react-native` | `>= 0.75.0` |
77
- | `react-native-nitro-modules` | `>= 0.35.0` |
78
- | `react` | `*` |
33
+ For Expo projects, prebuild after adding the config plugin:
79
34
 
80
- For Expo projects, rebuild native code after installation:
81
-
82
- ```bash
83
- bunx expo prebuild
35
+ ```sh
36
+ bunx expo prebuild --clean
84
37
  ```
85
38
 
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
- ```
39
+ For bare React Native projects, install pods after installing the package:
132
40
 
133
- Wait for the app to install and open.
134
-
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
- }
41
+ ```sh
42
+ cd ios && pod install
180
43
  ```
181
44
 
182
- **Using environment variables (recommended):**
183
-
184
- Create a `.env.local` file:
185
-
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
45
+ ## Requirements
191
46
 
192
- # Web Client ID (used for Android OAuth flow)
193
- GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com
194
-
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
- ```
47
+ | Runtime | Requirement |
48
+ | --------------------- | ---------------------------------------- |
49
+ | React Native | `>=0.75` |
50
+ | Nitro Modules | `>=0.35` |
51
+ | iOS | 15.1+ recommended |
52
+ | Android | min SDK 24+ recommended |
53
+ | Expo example baseline | Expo SDK 55, React Native 0.83, React 19 |
203
54
 
204
- Then reference them in `app.config.js`:
55
+ ## Expo Setup
205
56
 
206
- ```javascript
207
- import "dotenv/config";
57
+ Add the plugin to `app.json` or `app.config.js`.
208
58
 
59
+ ```js
209
60
  export default {
210
61
  expo: {
62
+ scheme: "myapp",
63
+ ios: {
64
+ bundleIdentifier: "com.company.myapp",
65
+ },
66
+ android: {
67
+ package: "com.company.myapp",
68
+ },
211
69
  plugins: [
212
70
  [
213
71
  "react-native-nitro-auth",
@@ -232,841 +90,372 @@ export default {
232
90
  ],
233
91
  extra: {
234
92
  googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID,
93
+ appleWebClientId: process.env.APPLE_WEB_CLIENT_ID,
235
94
  microsoftClientId: process.env.MICROSOFT_CLIENT_ID,
236
95
  microsoftTenant: process.env.MICROSOFT_TENANT,
237
96
  microsoftB2cDomain: process.env.MICROSOFT_B2C_DOMAIN,
238
- appleWebClientId: process.env.APPLE_WEB_CLIENT_ID,
97
+ nitroAuthWebStorage: "memory",
98
+ nitroAuthPersistTokensOnWeb: false,
239
99
  },
240
100
  },
241
101
  };
242
102
  ```
243
103
 
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.
351
-
352
- - `nitroAuthWebStorage`: `"session"` (default), `"local"`, or `"memory"`.
353
- - `nitroAuthPersistTokensOnWeb`: `false` by default (recommended). Set `true` only if you need cross-reload token persistence.
104
+ ### Plugin Options
354
105
 
355
- ## Quick Start
356
-
357
- ### Using the Hook
358
-
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
- }
106
+ | Option | Platform | Purpose |
107
+ | ---------------------------- | -------- | ---------------------------------------------------------------------- |
108
+ | `ios.googleClientId` | iOS | Google iOS OAuth client ID |
109
+ | `ios.googleServerClientId` | iOS | Google web/server client ID for server auth code flows |
110
+ | `ios.googleUrlScheme` | iOS | Reversed iOS client ID URL scheme |
111
+ | `ios.appleSignIn` | iOS | Adds Apple Sign-In entitlement when `true` |
112
+ | `ios.microsoftClientId` | iOS | Microsoft app/client ID |
113
+ | `ios.microsoftTenant` | iOS | Microsoft tenant, `common`, `organizations`, `consumers`, or tenant ID |
114
+ | `ios.microsoftB2cDomain` | iOS | Azure AD B2C domain |
115
+ | `android.googleClientId` | Android | Google web OAuth client ID |
116
+ | `android.microsoftClientId` | Android | Microsoft app/client ID |
117
+ | `android.microsoftTenant` | Android | Microsoft tenant |
118
+ | `android.microsoftB2cDomain` | Android | Azure AD B2C domain |
374
119
 
375
- return (
376
- <View>
377
- {error && <Text style={{ color: "red" }}>{error.message}</Text>}
378
- {!hasPlayServices && <Text>Please install Google Play Services</Text>}
120
+ ## Provider Setup
379
121
 
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
- ```
122
+ ### Google Sign-In
399
123
 
400
- ### Microsoft Login Options
124
+ Create OAuth clients in Google Cloud Console:
401
125
 
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
- ```
126
+ - iOS client ID for your bundle identifier.
127
+ - Web client ID for Android, web, and server auth code flows.
128
+ - Android SHA-1/SHA-256 entries for local debug and release signing.
411
129
 
412
- **B2C example:**
130
+ Use the iOS reversed client ID as `GOOGLE_IOS_URL_SCHEME`.
413
131
 
414
- ```tsx
415
- await login("microsoft", {
416
- tenant: "your-tenant.onmicrosoft.com/B2C_1_signin",
417
- scopes: ["openid", "email", "profile", "offline_access"],
418
- });
419
- ```
132
+ ### Apple Sign-In
420
133
 
421
- ## Migration from @react-native-google-signin/google-signin
134
+ 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
135
 
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.
136
+ Apple Sign-In is supported on iOS and web. It is intentionally reported as `unsupported_provider` on Android.
424
137
 
425
- ### 1) Replace the dependency
138
+ ### Microsoft Entra ID
426
139
 
427
- ```bash
428
- bun remove @react-native-google-signin/google-signin
429
- bun add react-native-nitro-auth react-native-nitro-modules
430
- ```
140
+ Create an app registration in Microsoft Entra ID and add redirect URIs:
431
141
 
432
- ### 2) Move configuration to the Nitro Auth plugin
142
+ - iOS: `msauth.<bundleIdentifier>://auth`
143
+ - Android: `msauth://<androidPackage>/<clientId>`
144
+ - Web: your web origin, for example `https://app.example.com`
433
145
 
434
- Nitro Auth does not use `GoogleSignin.configure(...)`. Instead, set your client IDs via the config plugin (Expo) or native config (bare).
146
+ Use `microsoftTenant` for `common`, `organizations`, `consumers`, a tenant ID, or a B2C policy path. Use `microsoftB2cDomain` for Azure AD B2C.
435
147
 
436
- **Expo** (recommended):
148
+ ## Quick Start
437
149
 
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"
150
+ ```tsx
151
+ import { Button, Text, View } from "react-native";
152
+ import { AuthError, useAuth } from "react-native-nitro-auth";
153
+
154
+ export function SignInScreen() {
155
+ const { user, loading, login, logout, getAccessToken } = useAuth();
156
+
157
+ async function signInWithGoogle() {
158
+ try {
159
+ await login("google", {
160
+ scopes: ["email", "profile"],
161
+ });
162
+ } catch (e) {
163
+ const error = AuthError.from(e);
164
+ console.warn(error.code, error.underlyingMessage);
457
165
  }
458
166
  }
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
-
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
167
 
486
- // After
487
- import { useAuth } from "react-native-nitro-auth";
488
-
489
- const { login, getAccessToken } = useAuth();
168
+ async function readToken() {
169
+ const token = await getAccessToken();
170
+ console.log(token);
171
+ }
490
172
 
491
- await login("google");
492
- const accessToken = await getAccessToken();
173
+ return (
174
+ <View>
175
+ <Text>{user?.email ?? "Signed out"}</Text>
176
+ <Button
177
+ title={loading ? "Signing in..." : "Sign in with Google"}
178
+ onPress={signInWithGoogle}
179
+ />
180
+ <Button title="Get access token" onPress={readToken} />
181
+ <Button title="Sign out" onPress={logout} />
182
+ </View>
183
+ );
184
+ }
493
185
  ```
494
186
 
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.
187
+ ## SocialButton
508
188
 
509
189
  ```tsx
510
- useEffect(() => {
511
- AuthService.silentRestore();
512
- }, []);
190
+ import { SocialButton } from "react-native-nitro-auth";
191
+
192
+ export function AuthButtons() {
193
+ return (
194
+ <>
195
+ <SocialButton provider="google" />
196
+ <SocialButton provider="apple" variant="black" />
197
+ <SocialButton provider="microsoft" variant="outline" />
198
+ </>
199
+ );
200
+ }
513
201
  ```
514
202
 
515
- ### Global Auth State Listener
203
+ ## AuthService
516
204
 
517
- Subscribe to authentication changes outside of React components:
205
+ Use `AuthService` when you need auth outside React components.
518
206
 
519
207
  ```ts
520
208
  import { AuthService } from "react-native-nitro-auth";
521
209
 
210
+ await AuthService.silentRestore();
211
+
522
212
  const unsubscribe = AuthService.onAuthStateChanged((user) => {
523
- if (user) {
524
- console.log("Logged in:", user.email);
525
- } else {
526
- console.log("Logged out");
527
- }
213
+ console.log(user?.email);
214
+ });
215
+
216
+ const tokensUnsubscribe = AuthService.onTokensRefreshed((tokens) => {
217
+ console.log(tokens.expirationTime);
528
218
  });
529
219
 
530
- // Later...
531
220
  unsubscribe();
221
+ tokensUnsubscribe();
532
222
  ```
533
223
 
534
- ### Global Token Refresh Listener
535
-
536
- Be notified whenever tokens are refreshed automatically (or manually):
224
+ ## Login Options
537
225
 
538
226
  ```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
227
+ await login("google", {
228
+ scopes: ["email", "profile"],
229
+ loginHint: "user@example.com",
230
+ useOneTap: true,
231
+ useSheet: true,
232
+ forceAccountPicker: true,
233
+ useLegacyGoogleSignIn: true,
544
234
  });
545
- ```
546
235
 
547
- ### Incremental Authorization
548
-
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
- };
236
+ await login("microsoft", {
237
+ scopes: ["openid", "profile", "email", "offline_access", "User.Read"],
238
+ tenant: "organizations",
239
+ prompt: "select_account",
240
+ });
562
241
  ```
563
242
 
564
- ### App-Owned Persistence
565
-
566
- Nitro Auth is intentionally stateless in-process. Persist only what your app needs.
243
+ | Option | Applies to | Notes |
244
+ | ----------------------- | ----------------- | ---------------------------------------------------- |
245
+ | `scopes` | Google, Microsoft | Requested OAuth scopes |
246
+ | `loginHint` | Google, Microsoft | Prefills account selection when supported |
247
+ | `useOneTap` | Android Google | Enables Credential Manager auto-select |
248
+ | `useSheet` | iOS Google | Uses native sign-in sheet behavior |
249
+ | `forceAccountPicker` | Google | Forces account picker |
250
+ | `useLegacyGoogleSignIn` | Android Google | Uses legacy Google Sign-In path for server auth code |
251
+ | `tenant` | Microsoft | Overrides configured tenant |
252
+ | `prompt` | Microsoft | `login`, `consent`, `select_account`, or `none` |
567
253
 
568
- #### Using react-native-nitro-storage (Recommended)
254
+ ## Incremental Scopes
569
255
 
570
256
  ```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
- });
257
+ const calendarScope = "https://www.googleapis.com/auth/calendar.readonly";
589
258
 
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
- }
259
+ await requestScopes([calendarScope]);
260
+ await revokeScopes([calendarScope]);
629
261
  ```
630
262
 
631
- ### Production Readiness
263
+ 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
264
 
633
- Nitro Auth is suitable for production use when configured with provider-accurate expectations:
265
+ ## Storage Model
634
266
 
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.
267
+ 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
268
 
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:
269
+ For app-managed persistence, store only the minimum state your product needs:
652
270
 
653
271
  ```ts
654
272
  import { AuthService } from "react-native-nitro-auth";
655
273
 
656
- AuthService.setLoggingEnabled(true);
274
+ const snapshot = {
275
+ user: AuthService.currentUser,
276
+ scopes: AuthService.grantedScopes,
277
+ updatedAt: Date.now(),
278
+ };
657
279
  ```
658
280
 
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;
281
+ On web, the default is also memory storage. You can opt into browser storage with:
667
282
 
668
- // Async access ensures fresh tokens (will refresh if expired)
669
- const freshToken = await AuthService.getAccessToken();
283
+ ```js
284
+ extra: {
285
+ nitroAuthWebStorage: "session", // "session", "local", or "memory"
286
+ nitroAuthPersistTokensOnWeb: true,
287
+ }
670
288
  ```
671
289
 
672
- ### Standardized Error Codes
290
+ ## Error Contract
673
291
 
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.
292
+ All public async APIs throw `AuthError`, including provider errors surfaced by native and web `AuthService` implementations.
675
293
 
676
294
  ```ts
677
- import { AuthError } from "react-native-nitro-auth";
678
-
679
295
  try {
680
- await login("google");
296
+ await AuthService.login("microsoft");
681
297
  } 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
- }
298
+ const error = AuthError.from(e);
299
+ switch (error.code) {
300
+ case "cancelled":
301
+ break;
302
+ case "configuration_error":
303
+ break;
304
+ case "token_error":
305
+ break;
306
+ default:
307
+ break;
693
308
  }
694
309
  }
695
310
  ```
696
311
 
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:
312
+ Known error codes:
718
313
 
719
314
  ```ts
720
- import { AuthError } from "react-native-nitro-auth";
721
-
722
- try {
723
- await login("google");
724
- } 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);
729
- }
730
- }
731
- ```
732
-
733
- The `AuthUser.underlyingError` field carries a raw warning string when the provider returns one alongside a successful result.
734
-
735
- ### Troubleshooting
315
+ type AuthErrorCode =
316
+ | "cancelled"
317
+ | "timeout"
318
+ | "popup_blocked"
319
+ | "network_error"
320
+ | "configuration_error"
321
+ | "not_signed_in"
322
+ | "operation_in_progress"
323
+ | "unsupported_provider"
324
+ | "invalid_state"
325
+ | "invalid_nonce"
326
+ | "token_error"
327
+ | "no_id_token"
328
+ | "parse_error"
329
+ | "refresh_failed"
330
+ | "unknown";
331
+ ```
332
+
333
+ `underlyingMessage` keeps the raw native or OAuth message when it differs from the stable code.
736
334
 
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
335
+ ## API Reference
745
336
 
746
- The `getAccessToken()` method automatically checks if the current token is expired (or about to expire) and triggers a silent refresh if possible:
337
+ ### Exports
747
338
 
748
339
  ```ts
749
- const { getAccessToken } = useAuth();
750
-
751
- // This will silently refresh if needed!
752
- const token = await getAccessToken();
340
+ export * from "react-native-nitro-auth";
753
341
  ```
754
342
 
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:
758
-
759
- ```ts
760
- const { user } = useAuth();
761
-
762
- if (user?.serverAuthCode) {
763
- // Send this to your backend!
764
- await api.verifyGoogleAccess(user.serverAuthCode);
765
- }
766
- ```
343
+ Main exports:
767
344
 
768
- ### Google One-Tap & Sheet
345
+ - `useAuth()`
346
+ - `AuthService`
347
+ - `SocialButton`
348
+ - `AuthError`
349
+ - `isAuthErrorCode()`
350
+ - `toAuthErrorCode()`
351
+ - `AuthProvider`
352
+ - `AuthUser`
353
+ - `AuthTokens`
354
+ - `LoginOptions`
769
355
 
770
- Explicitly enable the modern One-Tap flow on Android or the Sign-In Sheet on iOS:
356
+ ### useAuth()
771
357
 
772
358
  ```ts
773
- await login("google", {
774
- useOneTap: true, // Android
775
- useSheet: true, // iOS
776
- });
359
+ type UseAuthReturn = {
360
+ user: AuthUser | undefined;
361
+ scopes: string[];
362
+ loading: boolean;
363
+ error: AuthError | undefined;
364
+ hasPlayServices: boolean;
365
+ login(provider: AuthProvider, options?: LoginOptions): Promise<void>;
366
+ logout(): void;
367
+ requestScopes(scopes: string[]): Promise<void>;
368
+ revokeScopes(scopes: string[]): Promise<void>;
369
+ getAccessToken(): Promise<string | undefined>;
370
+ refreshToken(): Promise<AuthTokens>;
371
+ silentRestore(): Promise<void>;
372
+ };
777
373
  ```
778
374
 
779
- > [!NOTE]
780
- > One-Tap requires Google Play Services. You can check `hasPlayServices` from `useAuth()` and show a fallback UI if needed.
781
-
782
- ### Android Legacy Google Sign-In (Server Auth Code)
783
-
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:
375
+ ### AuthUser
786
376
 
787
377
  ```ts
788
- await login("google", { useLegacyGoogleSignIn: true });
378
+ type AuthUser = {
379
+ provider: "google" | "apple" | "microsoft";
380
+ email?: string;
381
+ name?: string;
382
+ photo?: string;
383
+ idToken?: string;
384
+ accessToken?: string;
385
+ refreshToken?: string;
386
+ serverAuthCode?: string;
387
+ scopes?: string[];
388
+ expirationTime?: number;
389
+ underlyingError?: string;
390
+ };
789
391
  ```
790
392
 
791
- ### Force Account Picker
393
+ ## Example App
792
394
 
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:
395
+ The example app is the fastest way to verify setup and read a complete integration.
794
396
 
795
- ```ts
796
- await login("google", {
797
- scopes: ["https://www.googleapis.com/auth/calendar.readonly"],
798
- forceAccountPicker: true, // Always show account picker
799
- });
397
+ ```sh
398
+ cp apps/example/.env.example apps/example/.env.local
399
+ bun install
400
+ bun example:prebuild:clean
401
+ bun example:ios
402
+ bun example:android
800
403
  ```
801
404
 
802
- This is useful for scenarios where:
405
+ The demo includes:
803
406
 
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
407
+ - Provider cards for Google, Apple, and Microsoft.
408
+ - Token and scope operations.
409
+ - Silent restore and account picker actions.
410
+ - App-owned disk snapshot example with `react-native-nitro-storage`.
411
+ - Runtime smoke tests for the public API.
807
412
 
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.
413
+ ## Troubleshooting
809
414
 
810
- ## API Reference
415
+ | Symptom | Check |
416
+ | ------------------------------------- | -------------------------------------------------------------------------------- |
417
+ | `configuration_error` on Google | Client ID is missing or wrong for the current platform |
418
+ | Google works in debug but not release | Add release SHA-1/SHA-256 fingerprints to Google Cloud Console |
419
+ | Android `hasPlayServices` is false | Use an emulator image with Google Play Services |
420
+ | Apple email/name missing | Apple only returns these fields on first authorization |
421
+ | Microsoft `invalid_state` | Redirect URI or app resume path is wrong, or an old auth redirect completed late |
422
+ | Microsoft `token_error` | Check tenant, client ID, redirect URI, and requested scopes |
423
+ | Web popup blocked | Call `login()` from a user gesture such as a button press |
424
+ | `operation_in_progress` | A provider flow is already active; wait for it to finish or sign out |
811
425
 
812
- ### Package Exports
426
+ ## Production Notes
813
427
 
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";
830
- ```
428
+ - Verify ID tokens on your backend. Client-side JWT parsing is for display and expiration hints only.
429
+ - Store refresh tokens only in storage your app explicitly owns and secures.
430
+ - Keep Google debug and release signing fingerprints in sync with your OAuth clients.
431
+ - Add provider-specific redirect URIs for every environment.
432
+ - Run the example app on iOS and Android before shipping provider config changes.
831
433
 
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()`
434
+ ## Release Checks
882
435
 
883
- ```ts
884
- declare function useAuth(): UseAuthReturn;
436
+ ```sh
437
+ bun run publish-package:dry-run
885
438
  ```
886
439
 
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.
440
+ The publish script runs frozen install, core-version verification, codegen, build, lint, typecheck, Jest, JS coverage, C++ tests, C++ coverage, Expo Doctor, package docs sync, pack dry run, and `bun publish --dry-run --ignore-scripts`.
991
441
 
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
- }
998
-
999
- function isAuthErrorCode(value: string): value is AuthErrorCode;
1000
- function toAuthErrorCode(raw: string): AuthErrorCode; // Returns "unknown" for unrecognized strings
1001
- ```
442
+ For faster local iteration before the full release dry run:
1002
443
 
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
444
+ ```sh
445
+ bun run check
446
+ bun run test:cpp
447
+ bun run --cwd packages/react-native-nitro-auth test:coverage -- --runInBand
448
+ bun run --cwd packages/react-native-nitro-auth test:cpp:coverage
1029
449
  ```
1030
450
 
1031
- Workspace-local commands:
1032
-
1033
- ```bash
1034
- # apps/example
1035
- bun run format
1036
- bun run lint
1037
- bun run typecheck
451
+ Before shipping provider or native config changes, also verify the example app:
1038
452
 
1039
- # packages/react-native-nitro-auth
1040
- bun run format
1041
- bun run lint
1042
- bun run typecheck
1043
- bun run test
453
+ ```sh
454
+ bun run example:prebuild
455
+ bun run example:android
456
+ bun run example:ios
1044
457
  ```
1045
458
 
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
459
  ## License
1071
460
 
1072
461
  MIT