react-native-oauth-essentials 1.0.12 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/OauthEssentials.podspec +17 -2
  2. package/README.md +385 -361
  3. package/android/build.gradle +15 -10
  4. package/android/src/main/java/com/oauthessentials/CredentialError.kt +4 -1
  5. package/android/src/main/java/com/oauthessentials/CredentialFactory.kt +24 -2
  6. package/android/src/main/java/com/oauthessentials/CustomTabsActivity.kt +14 -1
  7. package/android/src/main/java/com/oauthessentials/OauthEssentialsModule.kt +445 -187
  8. package/app.plugin.js +1 -0
  9. package/ios/AppleIdRetriever.swift +25 -26
  10. package/ios/CredentialError.swift +3 -0
  11. package/ios/CredentialFactory.swift +38 -20
  12. package/ios/CredentialsType.swift +1 -0
  13. package/ios/GoogleIdRetriever.swift +132 -56
  14. package/ios/HybridRetriever.swift +12 -18
  15. package/ios/OauthEssentials.h +0 -3
  16. package/ios/OauthEssentials.mm +45 -2
  17. package/ios/OauthEssentialsModule.swift +266 -57
  18. package/ios/PasskeyRetriever.swift +31 -49
  19. package/ios/PasswordRetriever.swift +10 -30
  20. package/ios/RCTPromiseSettler.swift +125 -0
  21. package/jest/index.js +149 -0
  22. package/lib/module/NativeOauthEssentials.js +10 -1
  23. package/lib/module/NativeOauthEssentials.js.map +1 -1
  24. package/lib/module/errors.js +38 -0
  25. package/lib/module/errors.js.map +1 -0
  26. package/lib/module/index.js +432 -58
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/module/oauth.js +97 -0
  29. package/lib/module/oauth.js.map +1 -0
  30. package/lib/module/platform.js +108 -0
  31. package/lib/module/platform.js.map +1 -0
  32. package/lib/module/types.js +46 -1
  33. package/lib/module/types.js.map +1 -1
  34. package/lib/typescript/src/NativeOauthEssentials.d.ts +70 -16
  35. package/lib/typescript/src/NativeOauthEssentials.d.ts.map +1 -1
  36. package/lib/typescript/src/errors.d.ts +18 -0
  37. package/lib/typescript/src/errors.d.ts.map +1 -0
  38. package/lib/typescript/src/index.d.ts +235 -40
  39. package/lib/typescript/src/index.d.ts.map +1 -1
  40. package/lib/typescript/src/oauth.d.ts +32 -0
  41. package/lib/typescript/src/oauth.d.ts.map +1 -0
  42. package/lib/typescript/src/platform.d.ts +39 -0
  43. package/lib/typescript/src/platform.d.ts.map +1 -0
  44. package/lib/typescript/src/types.d.ts +190 -2
  45. package/lib/typescript/src/types.d.ts.map +1 -1
  46. package/package.json +9 -5
  47. package/plugin/withOauthEssentials.js +291 -0
  48. package/src/NativeOauthEssentials.ts +91 -17
  49. package/src/errors.ts +49 -0
  50. package/src/index.tsx +587 -80
  51. package/src/oauth.ts +140 -0
  52. package/src/platform.ts +121 -0
  53. package/src/types.ts +200 -1
package/README.md CHANGED
@@ -1,181 +1,148 @@
1
- # About
1
+ <div align="center">
2
2
 
3
- **React Native OAuth Essentials** is an essential OAuth solution for React Native apps. It provides **up-to-date Google
4
- Sign-In, Apple Sign-In, and Password Sign-In**, delivering a **native sign-in experience** with a simple, promise-based
5
- API. It's a must-have for every React Native app.
3
+ # 🔐 React Native OAuth Essentials
6
4
 
7
- ## Installation
5
+ **Every sign-in your app needs, in one library.**<br/>
6
+ Google · Apple · Saved passwords · Passkeys · Any OAuth 2.0 / OpenID Connect provider
8
7
 
9
- Install the library via npm:
8
+ [![npm version](https://img.shields.io/npm/v/react-native-oauth-essentials?style=flat-square&color=cb3837&logo=npm)](https://www.npmjs.com/package/react-native-oauth-essentials)
9
+ [![npm downloads](https://img.shields.io/npm/dm/react-native-oauth-essentials?style=flat-square&color=blue)](https://www.npmjs.com/package/react-native-oauth-essentials)
10
+ [![license](https://img.shields.io/npm/l/react-native-oauth-essentials?style=flat-square&color=green)](./LICENSE)
11
+ ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20Android-lightgrey?style=flat-square)
12
+ ![expo](https://img.shields.io/badge/Expo-config%20plugin-000020?style=flat-square&logo=expo)
13
+ ![new architecture](https://img.shields.io/badge/New%20Architecture-ready-61dafb?style=flat-square&logo=react)
10
14
 
11
- ```bash
12
- npm install react-native-oauth-essentials
13
- ```
15
+ [📖 Docs](https://lucaswitch.github.io/react-native-oauth-essentials/docs/intro) ·
16
+ [⚙️ Installation](https://lucaswitch.github.io/react-native-oauth-essentials/docs/installation) ·
17
+ [🤖 AI setup prompt](https://lucaswitch.github.io/react-native-oauth-essentials/docs/ai-setup) ·
18
+ [📜 Changelog](./CHANGELOG.md)
14
19
 
15
- ```bash
16
- yarn add react-native-oauth-essentials
17
- ```
20
+ </div>
18
21
 
19
- ## How to use it
22
+ ---
20
23
 
21
- Please jump to our docs website
24
+ ## ✨ Why this library?
22
25
 
23
- [Docs](https://lucaswitch.github.io/react-native-oauth-essentials/docs/intro)
24
- [Installation](https://lucaswitch.github.io/react-native-oauth-essentials/docs/installation)
26
+ Most React Native apps end up stitching together several sign-in libraries, each with its own philosophy and API. This one
27
+ gives you **a single, promise-based API** with a **truly native experience** on both platforms:
25
28
 
26
- ### Platform Support Details
29
+ - 🟢 **Google Sign-In**: GoogleSignIn SDK on iOS, Credential Manager on Android.
30
+ - 🍎 **Sign in with Apple**: native on iOS (required by the App Store when you offer Google), web flow on Android.
31
+ - 🔑 **Saved passwords & passkeys**: the system pickers, including iOS AutoFill.
32
+ - 🌐 **Any OAuth 2.0 / OpenID Connect provider**: GitHub, Microsoft, Auth0, Keycloak… with PKCE.
33
+ - 🧩 **Expo config plugin** and a **Jest mock** included.
34
+ - 🧾 **Fully typed**, with typed errors you can narrow with `isCredentialError`.
27
35
 
28
- This library provides native OAuth implementations with platform-specific behaviors and requirements:
36
+ > [!NOTE]
37
+ > Requires the **New Architecture**. Not supported in Expo Go: use a [development build](#-expo).
29
38
 
30
- #### Google Sign-In
39
+ ## 📦 Installation
31
40
 
32
- **iOS:**
33
- - **Minimum:** iOS 13+
34
- - Uses native iOS Google Sign-In SDK
35
- - Presents native iOS authentication UI
36
- - No Google Play Services dependency
37
- - Requires Google OAuth 2.0 Web Client ID from Google Cloud Console
38
- - Supports One Tap sign-in experience
39
- - Available: `GOOGLE_ID_SUPPORTED` constant
41
+ ```bash
42
+ npm install react-native-oauth-essentials
43
+ # or
44
+ yarn add react-native-oauth-essentials
45
+ ```
40
46
 
41
- **Android:**
42
- - **Minimum:** Android 8.0+ (API 26+)
43
- - Uses Credential Manager API with Google ID provider
44
- - Requires Google Play Services to be installed and up-to-date
45
- - Check `GOOGLE_PLAY_SERVICES_SUPPORTED` before using
46
- - Supports additional options: `authorizedAccounts` and `autoSelectEnabled`
47
- - Presents native Android credential picker UI
48
- - Requires Google OAuth 2.0 Web Client ID from Google Cloud Console
49
- - Available: `GOOGLE_ID_SUPPORTED` constant
47
+ Then install the iOS pods and rebuild the app (reloading Metro is not enough, this library has native code):
50
48
 
51
- **Common Features:**
52
- - Returns standardized `GoogleIdCredentialResult` with user profile data
53
- - Includes ID token for backend verification
54
- - User can cancel, returning `CancelledCredentialResult`
49
+ ```bash
50
+ cd ios && pod install
51
+ ```
55
52
 
56
- ---
53
+ The iOS side uses GoogleSignIn 10 (iOS 16+). Requires React Native 0.77+ with the New Architecture.
57
54
 
58
- #### Apple Sign-In
59
-
60
- **iOS:**
61
- - **Minimum:** iOS 13+
62
- - Uses native `AuthenticationServices` framework
63
- - Presents native Apple Sign-In UI sheet
64
- - Full integration with iOS Keychain
65
- - Returns `AppleIdCredentialResult` with:
66
- - ID token and authorization code
67
- - User identifier (stable across app reinstalls)
68
- - Email and full name (only on first sign-in)
69
- - Real user indicator (`likelyReal`)
70
- - Caches email and fullName for subsequent sign-ins
71
- - No additional configuration needed
72
- - Available: `APPLE_ID_SUPPORTED` constant
73
-
74
- **Android:**
75
- - **Minimum:** Android 8.0+ (API 26+)
76
- - Uses browser-based OAuth flow via Chrome Custom Tabs (no native Apple Sign-In on Android)
77
- - Opens device's default browser with your provided `androidWebUrl`
78
- - Returns `WebAppleIdCredentialResult` with redirect URL components
79
- - **Requires backend implementation** to handle OAuth flow
80
- - You must implement Apple Sign-In REST API on your server
81
- - See [Apple Sign-In REST API Documentation](https://developer.apple.com/documentation/signinwithapplerestapi)
82
- - Available: `APPLE_ID_SUPPORTED` constant
83
-
84
- **Implementation Note:** iOS provides a complete native experience, while Android uses Chrome Custom Tabs to open your OAuth endpoint in the browser, then captures the redirect URL.
55
+ Using Expo? Jump to [Expo](#-expo). Using a coding agent? Paste the
56
+ [setup prompt for AI agents](https://lucaswitch.github.io/react-native-oauth-essentials/docs/ai-setup) and let it configure everything.
85
57
 
86
- ---
58
+ ## 🚀 Quick start
87
59
 
88
- #### Password Sign-In
89
-
90
- **iOS:**
91
- - **Minimum:** iOS 12+
92
- - Uses iOS Keychain Services
93
- - Integrates with iCloud Keychain for sync across devices
94
- - Presents native password autofill UI
95
- - Supports AutoFill in WebViews
96
- - Available: `PASSWORD_SUPPORTED` constant
97
-
98
- **Android:**
99
- - **Minimum:** Android 7.0+ (API 24+)
100
- - Uses Credential Manager API
101
- - Integrates with Google Smart Lock for Passwords
102
- - Can sync credentials across devices via Google account
103
- - Presents native credential picker UI
104
- - Supports autofill in WebViews and native forms
105
- - Available: `PASSWORD_SUPPORTED` constant
106
-
107
- **Common Features:**
108
- - `passwordSignIn()` - Saves credentials securely
109
- - `getPassword()` - Retrieves stored credentials
110
- - Returns `PasswordCredentialResult` with username and password
111
- - Can be used alongside biometric authentication
112
- - Returns `false` if no credentials stored
60
+ Sign in with Google in a few lines:
113
61
 
114
- ---
62
+ ```tsx
63
+ import { Button } from 'react-native';
64
+ import {
65
+ googleSignIn,
66
+ CredentialsType,
67
+ isCredentialError,
68
+ CredentialError,
69
+ } from 'react-native-oauth-essentials';
115
70
 
116
- #### Hybrid Sign-In
71
+ export function GoogleButton() {
72
+ const onPress = async () => {
73
+ try {
74
+ const result = await googleSignIn();
75
+
76
+ if (result.type === CredentialsType.CANCELLED) return; // the user closed the UI
77
+
78
+ if (result.type === CredentialsType.GOOGLE_ID) {
79
+ // Send the idToken to YOUR backend and verify it there.
80
+ await api.signInWithGoogle(result.data.idToken);
81
+ }
82
+ } catch (e) {
83
+ if (isCredentialError(e, CredentialError.CONFIGURATION_ERROR)) console.error(e.message);
84
+ else throw e;
85
+ }
86
+ };
117
87
 
118
- **Purpose:** Attempts all available sign-in methods simultaneously to find existing credentials.
88
+ return <Button title="Continue with Google" onPress={onPress} />;
89
+ }
90
+ ```
119
91
 
120
- **iOS:**
121
- - Checks for: Apple ID, Google ID, and saved passwords
122
- - Presents unified credential picker if multiple options available
123
- - Returns first available credential
124
- - Ideal for app launch to restore previous session
125
- - Available: `HYBRID_SUPPORTED` constant
92
+ That's the whole pattern: **call a function, check `result.type`, send the token to your backend.** Every other method
93
+ works the same way.
126
94
 
127
- **Android:**
128
- - Checks for: Google ID and saved passwords (Apple requires web flow)
129
- - Uses Credential Manager to aggregate all options
130
- - Presents native Android credential picker
131
- - Returns first selected credential
132
- - Available: `HYBRID_SUPPORTED` constant
95
+ ### Three things to know
133
96
 
134
- **Use Case:** Perfect for "Continue with..." screens or automatic sign-in on app start.
97
+ 1. **Cancelling is not an error.** If the user dismisses the UI, the promise **resolves** with
98
+ `{ type: CredentialsType.CANCELLED, data: null }`.
99
+ 2. **Real errors reject** with an `error.code` from `CredentialError`. Narrow them with `isCredentialError`.
100
+ 3. **Setup mistakes tell you what to fix.** A missing client id or URL scheme rejects with `CONFIGURATION_ERROR` and a
101
+ message that has the exact value to add.
135
102
 
136
- **Returns:**
137
- - `GoogleIdCredentialResult`
138
- - `AppleIdCredentialResult` (iOS only)
139
- - `PasswordCredentialResult`
140
- - `CancelledCredentialResult`
103
+ ## 🧭 What's supported
141
104
 
142
- ---
105
+ | Feature | iOS | Android |
106
+ |---|---|---|
107
+ | Google | GoogleSignIn SDK | Credential Manager (bottom sheet / "Sign in with Google" button flow) + Authorization API |
108
+ | Apple | Native `AuthenticationServices` | Your web flow in a Custom Tab (needs a backend) |
109
+ | Saved passwords | `ASAuthorizationPasswordProvider` | Credential Manager |
110
+ | Passkeys | iOS 16+, including AutoFill | Android 9+ |
111
+ | Hybrid (one picker at app start) | Apple ID + passwords | Google + passwords |
112
+ | Any OAuth 2.0 / OpenID Connect provider | `ASWebAuthenticationSession` + PKCE | Custom Tab + PKCE |
143
113
 
144
- #### Passkeys
145
-
146
- **iOS:**
147
- - Uses `AuthenticationServices` framework (iOS 16+)
148
- - Supports WebAuthn/FIDO2 standard
149
- - Syncs via iCloud Keychain
150
- - Presents native passkey creation/selection UI
151
- - Available: `PASSKEYS_SUPPORTED` constant (iOS 16+)
152
-
153
- **Android:**
154
- - Uses Credential Manager API with passkey support (Android 9+ / API 28+)
155
- - Supports WebAuthn/FIDO2 standard
156
- - Syncs via Google Password Manager
157
- - Additional options: `preferImmediatelyAvailableCredentials`, `isConditional`
158
- - Available: `PASSKEYS_SUPPORTED` constant
159
-
160
- **Common Features:**
161
- - Standards-based implementation (WebAuthn)
162
- - Requires `requestJson` string with `PublicKeyCredentialCreationOptions`
163
- - Returns `PassKeyCredentialResult` with platform public key credential
164
- - More secure than passwords (phishing-resistant)
165
- - Biometric authentication integration
114
+ Android features based on Credential Manager need Google Play Services. Exact per-platform behavior of every function:
115
+ [Methods reference](https://lucaswitch.github.io/react-native-oauth-essentials/docs/methods).
166
116
 
167
- ---
117
+ ## 📚 API reference
168
118
 
169
- ### Import
119
+ <details>
120
+ <summary><b>Import everything</b></summary>
170
121
 
171
122
  ```typescript
172
123
  import {
124
+ // Google
173
125
  googleSignIn,
126
+ googleSilentSignIn,
127
+ signOut,
128
+ // Apple
174
129
  appleSignIn,
175
- passwordSignIn,
130
+ getAppleIdCredentialState,
131
+ // Passwords
176
132
  getPassword,
133
+ passwordSignIn,
134
+ // Hybrid
177
135
  hybridSignIn,
136
+ // Passkeys
178
137
  createPassKey,
138
+ getPassKey,
139
+ cancelPassKeyAutoFill,
140
+ // Generic OAuth / OpenID Connect
141
+ oauthSignIn,
142
+ // Events
143
+ addCredentialSuccessListener,
144
+ addCredentialFailureListener,
145
+ addAppleIdCredentialRevokedListener,
179
146
  // Constants
180
147
  GOOGLE_PLAY_SERVICES_SUPPORTED,
181
148
  PASSWORD_SUPPORTED,
@@ -183,356 +150,405 @@ import {
183
150
  APPLE_ID_SUPPORTED,
184
151
  HYBRID_SUPPORTED,
185
152
  PASSKEYS_SUPPORTED,
186
- // Types
187
- type GoogleSignInOptions,
188
- type PasskeyOptions,
189
- type GoogleIdCredentialResult,
190
- type AppleIdCredentialResult,
191
- type WebAppleIdCredentialResult,
192
- type PasswordCredentialResult,
193
- type PassKeyCredentialResult,
194
- type CancelledCredentialResult,
153
+ OAUTH_REDIRECT_URI,
154
+ // Errors
155
+ isCredentialError,
156
+ // Enums
195
157
  CredentialsType,
196
158
  CredentialError,
159
+ AppleIdCredentialState,
197
160
  } from 'react-native-oauth-essentials';
198
161
  ```
199
162
 
200
- ### Constants
163
+ </details>
201
164
 
202
- Check platform support before using OAuth methods:
165
+ ### Conventions
203
166
 
204
- | Constant | Type | Description |
205
- |----------|------|-------------|
206
- | `GOOGLE_PLAY_SERVICES_SUPPORTED` | `boolean` | Google Play Services are available. Always `false` on iOS. |
207
- | `PASSWORD_SUPPORTED` | `boolean` | Password credential manager is supported on this device. |
208
- | `GOOGLE_ID_SUPPORTED` | `boolean` | Google Sign-In is supported. **Use this to check if you can use `googleSignIn()`**. |
209
- | `APPLE_ID_SUPPORTED` | `boolean` | Apple Sign-In is supported on this device. |
210
- | `HYBRID_SUPPORTED` | `boolean` | Hybrid sign-in is supported (multiple methods at once). |
211
- | `PASSKEYS_SUPPORTED` | `boolean` | Passkeys are supported on this device. |
167
+ - Dismissing the UI **resolves** `{ type: CredentialsType.CANCELLED, data: null }` instead of rejecting.
168
+ - Errors reject with `error.code` from `CredentialError` (every rejection has a `code`). Narrow them with
169
+ `isCredentialError`.
170
+ - iOS rejects a second call of the same flow while the first is pending with `SIMULTANEOUS_CALL_ERROR`.
171
+ - In development, options that have no effect on the current platform log a `console.warn` once
172
+ (`[react-native-oauth-essentials] googleSignIn: "hint" is ignored on Android.`).
212
173
 
213
- **Example:**
174
+ <details>
175
+ <summary><b>Constants</b> (feature detection)</summary>
214
176
 
215
- ```typescript
216
- if (GOOGLE_ID_SUPPORTED) {
217
- await googleSignIn('YOUR_WEB_CLIENT_ID');
218
- }
219
- ```
177
+ | Constant | iOS | Android |
178
+ |---|---|---|
179
+ | `GOOGLE_PLAY_SERVICES_SUPPORTED` | `false` | Play Services installed and up to date |
180
+ | `GOOGLE_ID_SUPPORTED` | `true` | Same as `GOOGLE_PLAY_SERVICES_SUPPORTED` |
181
+ | `PASSWORD_SUPPORTED` | `true` | Same as `GOOGLE_PLAY_SERVICES_SUPPORTED` |
182
+ | `APPLE_ID_SUPPORTED` | `true` | `true` (web flow) |
183
+ | `HYBRID_SUPPORTED` | `true` | Same as `GOOGLE_PLAY_SERVICES_SUPPORTED` |
184
+ | `PASSKEYS_SUPPORTED` | iOS 16+ | Android 9+ |
185
+ | `OAUTH_REDIRECT_URI` | `<bundle id, lowercased>://oauth/callback` | `<applicationId>://oauth/callback` |
220
186
 
221
- ### Methods
187
+ </details>
222
188
 
223
- #### `googleSignIn(webClientId, options?)`
189
+ ### Google
224
190
 
225
- Performs Google Sign-In with native UI.
191
+ #### `googleSignIn(clientId?, options?)`
226
192
 
227
- **Parameters:**
228
- - `webClientId` (string): Your Google OAuth 2.0 web client ID
229
- - `options` (GoogleSignInOptions, optional): Sign-in configuration
193
+ Shows the Google sign in UI. Also `googleSignIn()` / `googleSignIn(options)`: the client id is optional and, when it is
194
+ omitted, read from the app config:
230
195
 
231
- **Returns:** `Promise<GoogleIdCredentialResult | CancelledCredentialResult>`
232
-
233
- **Platform:** Android, iOS (check `GOOGLE_ID_SUPPORTED`)
196
+ - iOS: `GIDClientID` in Info.plist, then `CLIENT_ID` of the Firebase `GoogleService-Info.plist`. The `serverClientId`
197
+ (idToken audience and `serverAuthCode`) falls back to `webClientId`, then `GIDServerClientID`. iOS also needs the
198
+ reversed iOS client id as a URL scheme in `Info.plist` (`1234-abcd.apps.googleusercontent.com` becomes
199
+ `com.googleusercontent.apps.1234-abcd`): without it the call rejects with `CONFIGURATION_ERROR`, whose message has the
200
+ exact scheme to add.
201
+ - Android: the web client id from the `com.oauthessentials.GOOGLE_WEB_CLIENT_ID` `<meta-data>` of AndroidManifest.xml,
202
+ then the Firebase `default_web_client_id` string (from `google-services.json`). The Android client id is never passed.
203
+ - None found: rejects with `CONFIGURATION_ERROR`. The [Expo config plugin](#-expo) writes all of it.
234
204
 
235
- **Example:**
205
+ `clientId` can also be passed as `{ iosClientId?, webClientId? }` (all from the same Google Cloud project; a passed id
206
+ wins over the config), or as a string used as is (the iOS client id on iOS, the web client id on Android).
236
207
 
237
208
  ```typescript
238
- const result = await googleSignIn('YOUR_WEB_CLIENT_ID', {
239
- authorizedAccounts: true, // Android only
240
- autoSelectEnabled: false, // Android only
209
+ const result = await googleSignIn({ // or googleSignIn({ iosClientId, webClientId }, { ... })
210
+ mode: 'button', // Android: full "Sign in with Google" flow (use it for button taps)
211
+ nonce, // embedded in the idToken
212
+ scopes: ['https://www.googleapis.com/auth/calendar.readonly'],
241
213
  });
242
214
 
243
215
  if (result.type === CredentialsType.GOOGLE_ID) {
244
- console.log('User ID:', result.data.id);
245
- console.log('ID Token:', result.data.idToken);
246
- console.log('Name:', result.data.displayName);
216
+ await api.signInWithGoogle(result.data.idToken);
247
217
  }
248
218
  ```
249
219
 
250
- ---
220
+ **Returns:** `Promise<GoogleIdCredentialResult | CancelledCredentialResult>`
251
221
 
252
- #### `appleSignIn(androidWebUrl?)`
222
+ #### `googleSilentSignIn(clientId?, options?)`
253
223
 
254
- Initiates Apple Sign-In process.
224
+ Restores a previous Google sign in, without UI on iOS (and on Android when a single account signed in before). Rejects
225
+ with `NO_SIGN_IN_AVAILABLE` when there is nothing to restore.
255
226
 
256
- **Parameters:**
257
- - `androidWebUrl` (string, optional): Web URL for Android OAuth flow. **Required on Android.**
227
+ ```typescript
228
+ try {
229
+ const result = await googleSilentSignIn();
230
+ } catch (e) {
231
+ if (isCredentialError(e, CredentialError.NO_SIGN_IN_AVAILABLE)) showSignInScreen();
232
+ }
233
+ ```
258
234
 
259
- **Returns:** `Promise<AppleIdCredentialResult | WebAppleIdCredentialResult>`
235
+ #### `signOut()`
260
236
 
261
- **Platform:**
262
- - iOS: Native Apple Sign-In UI
263
- - Android: Opens browser with your web URL for OAuth flow
237
+ Clears the Google session on iOS and the Credential Manager state on Android (no auto select until the user picks an
238
+ account again). **Returns:** `Promise<true>`
264
239
 
265
- **Platform:** Check `APPLE_ID_SUPPORTED`
240
+ ### Apple
266
241
 
267
- **Example:**
242
+ #### `appleSignIn(androidWebUrl?, options?)`
243
+
244
+ - iOS: native sheet. `options`: `{ nonce?, state? }`. Apple only sends `email`/`fullName` on the first authorization;
245
+ the library caches them in memory, so store them on your backend.
246
+ - Android: opens `androidWebUrl` in a Custom Tab. Your backend runs the Apple web flow and redirects to
247
+ `<applicationId>://oauth/callback`. `options` are ignored.
268
248
 
269
249
  ```typescript
270
- const result = await appleSignIn('https://yourserver.com/apple-signin');
250
+ const result = await appleSignIn('https://api.example.com/auth/apple', { nonce: sha256(rawNonce) });
271
251
 
272
252
  if (result.type === CredentialsType.APPLE_ID) {
273
- // iOS result
274
- console.log('User:', result.data.user);
275
- console.log('ID Token:', result.data.idToken);
276
- console.log('Email:', result.data.email);
253
+ await api.signInWithApple(result.data.idToken, result.data.fullName); // iOS
277
254
  } else if (result.type === CredentialsType.WEB_APPLE_ID) {
278
- // Android result
279
- console.log('Redirect URL:', result.data.url);
255
+ await api.finishAppleWebFlow(result.data.url); // Android
280
256
  }
281
257
  ```
282
258
 
283
- ---
259
+ **Returns:** `Promise<AppleIdCredentialResult | WebAppleIdCredentialResult | CancelledCredentialResult>`
284
260
 
285
- #### `passwordSignIn(username, password)`
261
+ #### `getAppleIdCredentialState(userId)` (iOS)
286
262
 
287
- Saves username/password credentials to the platform's credential manager.
263
+ Checks without UI whether the Apple ID `user` is still signed in to the app. Call it on app start.
288
264
 
289
- **Parameters:**
290
- - `username` (string): Username to save
291
- - `password` (string): Password to save
265
+ ```typescript
266
+ const state = await getAppleIdCredentialState(storedAppleUserId);
267
+ if (state !== AppleIdCredentialState.AUTHORIZED) signOutLocally(); // REVOKED, NOT_FOUND, TRANSFERRED
268
+ ```
269
+
270
+ Rejects with `NOT_SUPPORTED_ERROR` on Android.
292
271
 
293
- **Returns:** `Promise<boolean>`
272
+ ### Passwords
294
273
 
295
- **Platform:** Android, iOS (check `PASSWORD_SUPPORTED`)
274
+ #### `getPassword()`
296
275
 
297
- **Example:**
276
+ Shows the passwords saved for the app. **Returns:** `Promise<PasswordCredentialResult | CancelledCredentialResult>`.
277
+ When there is no password or the user dismisses it, both platforms resolve the `CANCELLED` result. Other Android errors
278
+ reject with `INVALID_RESULT_ERROR`.
298
279
 
299
280
  ```typescript
300
- const saved = await passwordSignIn('user@example.com', 'password123');
301
- console.log('Credentials saved:', saved);
281
+ const result = await getPassword();
282
+ if (result.type === CredentialsType.PASSWORD) {
283
+ await api.signIn(result.data.username, result.data.password);
284
+ }
302
285
  ```
303
286
 
304
- ---
287
+ > **Migrating to 1.1:** `getPassword()` no longer resolves `false` on Android. Replace `if (!result)` /
288
+ > `result === false` checks with `result.type === CredentialsType.CANCELLED`.
305
289
 
306
- #### `getPassword()`
290
+ #### `passwordSignIn(username, password)`
307
291
 
308
- Retrieves stored password credentials.
292
+ Saves a password in the password manager (it does not authenticate). Android shows the save prompt and resolves `true`
293
+ when saved. iOS resolves `false` without doing anything: use `textContentType` on your inputs instead.
309
294
 
310
- **Returns:** `Promise<false | PasswordCredentialResult>`
295
+ ### Hybrid
311
296
 
312
- **Platform:** Android, iOS (check `PASSWORD_SUPPORTED`)
297
+ #### `hybridSignIn(clientId?, options?)`
313
298
 
314
- **Example:**
299
+ One picker with the credentials the user already has, meant for app start. iOS offers Apple ID + saved passwords (the
300
+ Google client id is ignored); Android offers Google + saved passwords (uses `webClientId`, or the one in the app config).
315
301
 
316
302
  ```typescript
317
- const result = await getPassword();
318
-
319
- if (result && result.type === CredentialsType.PASSWORD) {
320
- console.log('Username:', result.data.username);
321
- console.log('Password:', result.data.password);
322
- } else {
323
- console.log('No stored password found');
324
- }
303
+ const result = await hybridSignIn({ authorizedAccounts: true });
304
+ if (result.type === CredentialsType.CANCELLED) showSignInScreen();
325
305
  ```
326
306
 
327
- ---
307
+ **Returns:** `Promise<AppleIdCredentialResult | GoogleIdCredentialResult | PasswordCredentialResult | CancelledCredentialResult>`
328
308
 
329
- #### `hybridSignIn(googleClientId, options?)`
309
+ ### Passkeys
330
310
 
331
- Attempts all available sign-in methods at once. Useful on app start to check for existing credentials.
311
+ Your server generates the WebAuthn options and verifies the responses. Needs `webcredentials:` Associated Domains on iOS
312
+ (Expo plugin `webCredentialsDomains`) and `assetlinks.json` with the `get_login_creds` relation on Android.
332
313
 
333
- **Parameters:**
334
- - `googleClientId` (string): Your Google OAuth 2.0 web client ID
335
- - `options` (GoogleSignInOptions, optional): Google sign-in configuration
314
+ #### `createPassKey({ requestJson })`
336
315
 
337
- **Returns:** `Promise<AppleIdCredentialResult | GoogleIdCredentialResult | PasswordCredentialResult | CancelledCredentialResult>`
316
+ Registers a passkey from a `PublicKeyCredentialCreationOptions` JSON string. `result.data` is the registration response
317
+ JSON to send to the server.
338
318
 
339
- **Platform:** Android, iOS (check `HYBRID_SUPPORTED`)
319
+ #### `getPassKey({ requestJson, mediation? })`
340
320
 
341
- **Example:**
321
+ Signs in with a passkey from a `PublicKeyCredentialRequestOptions` JSON string. On iOS, `mediation: 'autofill'` shows the
322
+ passkeys in the keyboard of a `textContentType="username"` input instead of a sheet.
342
323
 
343
324
  ```typescript
344
- const result = await hybridSignIn('YOUR_WEB_CLIENT_ID');
345
-
346
- switch (result.type) {
347
- case CredentialsType.GOOGLE_ID:
348
- console.log('Google user:', result.data.displayName);
349
- break;
350
- case CredentialsType.APPLE_ID:
351
- console.log('Apple user:', result.data.user);
352
- break;
353
- case CredentialsType.PASSWORD:
354
- console.log('Username:', result.data.username);
355
- break;
356
- case CredentialsType.CANCELLED:
357
- console.log('User cancelled');
358
- break;
359
- }
325
+ const result = await getPassKey({ requestJson: JSON.stringify(await api.passkeyOptions()) });
326
+ if (result.type === CredentialsType.PASSKEY) await api.verifyPasskey(result.data);
360
327
  ```
361
328
 
362
- ---
363
-
364
- #### `createPassKey(options)`
365
-
366
- Creates a new passkey credential.
329
+ #### `cancelPassKeyAutoFill()`
367
330
 
368
- **Parameters:**
369
- - `options` (PasskeyOptions): Passkey creation configuration
331
+ Cancels a pending `getPassKey({ mediation: 'autofill' })` (call it when the screen unmounts). Resolves `true` when there
332
+ was one to cancel.
370
333
 
371
- **Returns:** `Promise<PassKeyCredentialResult>`
334
+ ### Generic OAuth / OpenID Connect
372
335
 
373
- **Platform:** Check `PASSKEYS_SUPPORTED`
336
+ #### `oauthSignIn(options)`
374
337
 
375
- **Example:**
338
+ Authorization code flow with PKCE for any provider (GitHub, Microsoft, Auth0, Keycloak…). Register `OAUTH_REDIRECT_URI`
339
+ (or your own `redirectUri`) in the provider.
376
340
 
377
341
  ```typescript
378
- const result = await createPassKey({
379
- requestJson: JSON.stringify({ /* PublicKeyCredentialCreationOptions */ }),
380
- preferImmediatelyAvailableCredentials: true, // Android only
381
- isConditional: false, // Android only
342
+ const result = await oauthSignIn({
343
+ authorizationEndpoint: 'https://github.com/login/oauth/authorize',
344
+ clientId: GITHUB_CLIENT_ID,
345
+ scopes: ['read:user', 'user:email'],
346
+ // tokenEndpoint: exchanges the code on the device (public clients only, never with a client secret)
382
347
  });
383
348
 
384
- if (result.type === CredentialsType.PASSKEY) {
385
- console.log('Passkey created:', result.data);
349
+ if (result.type === CredentialsType.OAUTH) {
350
+ await api.exchangeCode(result.data.code, result.data.codeVerifier, result.data.redirectUri);
386
351
  }
387
352
  ```
388
353
 
389
- ---
354
+ **Returns:** `Promise<OAuthCredentialResult | CancelledCredentialResult>`
355
+
356
+ ### Events
357
+
358
+ ```typescript
359
+ const success = addCredentialSuccessListener(({ type }) => analytics.track('sign_in', { type }));
360
+ const failure = addCredentialFailureListener(({ code }) => analytics.track('sign_in_error', { code }));
361
+ const revoked = addAppleIdCredentialRevokedListener(() => signOutLocally()); // iOS
390
362
 
391
- ### Type Definitions
363
+ success.remove();
364
+ ```
365
+
366
+ Success and failure fire for `googleSignIn`, `googleSilentSignIn`, `getPassword`, `appleSignIn`, `hybridSignIn`,
367
+ `createPassKey` and `getPassKey`.
392
368
 
393
- #### `GoogleSignInOptions`
369
+ <details>
370
+ <summary><b>Options</b> (TypeScript types)</summary>
394
371
 
395
372
  ```typescript
373
+ type GoogleClientId = string | { iosClientId?: string; webClientId?: string };
374
+
396
375
  type GoogleSignInOptions = {
397
- authorizedAccounts?: boolean; // Android only: Filter to authorized accounts
398
- autoSelectEnabled?: boolean; // Android only: Enable auto-selection
376
+ mode?: 'bottomSheet' | 'button'; // Android
377
+ authorizedAccounts?: boolean; // Android: only accounts that signed in before
378
+ autoSelectEnabled?: boolean; // Android
379
+ nonce?: string;
380
+ hint?: string; // iOS: email to preselect
381
+ hostedDomain?: string; // Google Workspace domain
382
+ scopes?: string[];
383
+ offlineAccess?: boolean; // Android: returns serverAuthCode
384
+ serverClientId?: string; // iOS: idToken audience + serverAuthCode (default webClientId). Android: serverAuthCode client id
399
385
  };
400
- ```
401
386
 
402
- #### `PasskeyOptions`
387
+ type AppleSignInOptions = { nonce?: string; state?: string }; // iOS
403
388
 
404
- ```typescript
405
389
  type PasskeyOptions = {
406
- requestJson: string; // JSON string of PublicKeyCredentialCreationOptions
407
- preferImmediatelyAvailableCredentials?: boolean; // Android only
408
- isConditional?: boolean; // Android only
390
+ requestJson: string;
391
+ preferImmediatelyAvailableCredentials?: boolean; // Android
392
+ mediation?: 'modal' | 'autofill'; // iOS 16+
409
393
  };
410
- ```
411
394
 
412
- ---
395
+ type OAuthSignInOptions = {
396
+ authorizationEndpoint: string;
397
+ clientId: string;
398
+ redirectUri?: string; // default OAUTH_REDIRECT_URI
399
+ scopes?: string[];
400
+ additionalParameters?: Record<string, string>;
401
+ tokenEndpoint?: string;
402
+ usePKCE?: boolean; // default true
403
+ prefersEphemeralSession?: boolean; // iOS
404
+ };
405
+ ```
413
406
 
414
- ### Result Types
407
+ </details>
415
408
 
416
- #### `GoogleIdCredentialResult`
409
+ <details>
410
+ <summary><b>Results</b> (TypeScript types)</summary>
417
411
 
418
412
  ```typescript
419
413
  type GoogleIdCredentialResult = {
420
414
  type: CredentialsType.GOOGLE_ID;
421
415
  data: {
422
- id: string; // User's Google ID
423
- idToken: string; // JWT ID token
424
- displayName: string; // Full name
425
- givenName: string; // First name
426
- familyName: string; // Last name
427
- profilePictureUri?: string | null; // Profile picture URL
428
- phoneNumber?: string | null; // Phone number (if available)
416
+ id: string; // iOS: Google user id (sub). Android: email
417
+ idToken: string;
418
+ displayName: string;
419
+ givenName: string;
420
+ familyName: string;
421
+ email?: string;
422
+ profilePictureUri?: string | null;
423
+ phoneNumber?: string | null; // Android
424
+ accessToken?: string | null;
425
+ serverAuthCode?: string | null;
426
+ grantedScopes?: string[] | null;
429
427
  };
430
428
  };
431
- ```
432
429
 
433
- #### `AppleIdCredentialResult`
434
-
435
- ```typescript
436
430
  type AppleIdCredentialResult = {
437
431
  type: CredentialsType.APPLE_ID;
438
432
  data: {
439
- idToken: string; // JWT ID token
440
- authorizationCode: string; // Authorization code
441
- user: string; // Unique user identifier
442
- email: string; // User's email
443
- fullName: string; // Full name
444
- likelyReal: boolean; // Indicates if user is likely real
433
+ idToken: string;
434
+ authorizationCode: string;
435
+ user: string; // stable Apple user id
436
+ email: string;
437
+ fullName: string;
438
+ likelyReal: boolean;
439
+ state?: string | null;
445
440
  };
446
441
  };
447
- ```
448
-
449
- #### `WebAppleIdCredentialResult`
450
442
 
451
- Android-specific result when using web-based Apple Sign-In:
452
-
453
- ```typescript
454
443
  type WebAppleIdCredentialResult = {
455
- type: CredentialsType.WEB_APPLE_ID;
456
- data: {
457
- url: string; // Full redirect URL
458
- scheme: string | null; // URL scheme
459
- host: string | null; // URL host
460
- path: string | null; // URL path
461
- query: string | null; // URL query parameters
462
- };
444
+ type: CredentialsType.WEB_APPLE_ID; // Android
445
+ data: { url: string; scheme: string | null; host: string | null; path: string | null; query: string | null };
463
446
  };
464
- ```
465
-
466
- #### `PasswordCredentialResult`
467
447
 
468
- ```typescript
469
448
  type PasswordCredentialResult = {
470
449
  type: CredentialsType.PASSWORD;
471
- data: {
472
- username: string; // Stored username
473
- password: string; // Stored password
474
- };
450
+ data: { username: string; password: string };
475
451
  };
476
- ```
477
452
 
478
- #### `PassKeyCredentialResult`
479
-
480
- ```typescript
481
453
  type PassKeyCredentialResult = {
482
454
  type: CredentialsType.PASSKEY;
483
- data: string; // JSON string of platform public key credential
455
+ data: string; // WebAuthn response JSON
484
456
  };
485
- ```
486
457
 
487
- #### `CancelledCredentialResult`
488
-
489
- ```typescript
490
- type CancelledCredentialResult = {
491
- type: CredentialsType.CANCELLED;
492
- data: null; // User cancelled the operation
458
+ type OAuthCredentialResult = {
459
+ type: CredentialsType.OAUTH;
460
+ data: {
461
+ code: string;
462
+ state: string;
463
+ redirectUri: string;
464
+ codeVerifier: string | null;
465
+ params: Record<string, string>; // all redirect parameters
466
+ tokens: Record<string, unknown> | null; // token endpoint response
467
+ };
493
468
  };
469
+
470
+ type CancelledCredentialResult = { type: CredentialsType.CANCELLED; data: null };
494
471
  ```
495
472
 
496
- ---
473
+ </details>
497
474
 
498
- ### Credential Types Enum
475
+ ### Error codes
499
476
 
500
- ```typescript
501
- enum CredentialsType {
502
- GOOGLE_ID = 'GOOGLE_ID', // Google Sign-In credential
503
- PASSWORD = 'PASSWORD', // Username/password credential
504
- APPLE_ID = 'APPLE_ID', // iOS native Apple Sign-In credential
505
- WEB_APPLE_ID = 'WEB_APPLE_ID', // Android web-based Apple Sign-In credential
506
- CANCELLED = 'CANCELLED', // User cancelled the sign-in flow
507
- PASSKEY = 'PASSKEY', // Passkey credential
508
- }
509
- ```
510
-
511
- ---
477
+ | `CredentialError` | When |
478
+ |---|---|
479
+ | `CONFIGURATION_ERROR` | Setup bug: no Google client id (passed or in the app config), iOS URL scheme missing from Info.plist (`message` has the exact scheme), invalid `redirectUri` |
480
+ | `NOT_SUPPORTED_ERROR` | No Play Services (Android), passkeys on old OS, `getAppleIdCredentialState` on Android |
481
+ | `NO_ACTIVITY_ERROR` | No activity / view controller to present the UI |
482
+ | `NO_SIGN_IN_AVAILABLE` | Nothing to return: no previous sign in, account or passkey |
483
+ | `BROWSER_UNAVAILABLE` | Browser session could not open (iOS: app not active, Guided Access; Android: no browser) |
484
+ | `SIMULTANEOUS_CALL_ERROR` | iOS: the same flow is still pending |
485
+ | `NETWORK_ERROR` | `oauthSignIn`: the token request failed |
486
+ | `INVALID_RESULT_ERROR` | The provider failed or returned an unexpected result (`message` has the details) |
487
+ | `NO_PLAY_SERVICES_ERROR` | Deprecated, never thrown |
512
488
 
513
- ### Error Codes
489
+ `isCredentialError(error, code?)` narrows an unknown error to `OauthEssentialsError` (`Error & { code }`):
514
490
 
515
491
  ```typescript
516
- enum CredentialError {
517
- NO_PLAY_SERVICES_ERROR = 'NO_PLAY_SERVICES_ERROR', // Google Play Services unavailable
518
- NO_ACTIVITY_ERROR = 'NO_ACTIVITY_ERROR', // No Android activity available
519
- INVALID_RESULT_ERROR = 'INVALID_RESULT_ERROR', // Invalid result from native module
492
+ try {
493
+ await googleSignIn();
494
+ } catch (e) {
495
+ if (isCredentialError(e, CredentialError.CONFIGURATION_ERROR)) console.error(e.message);
496
+ else if (isCredentialError(e, CredentialError.BROWSER_UNAVAILABLE)) showRetryMessage();
497
+ else if (isCredentialError(e)) report(e.code);
498
+ else throw e;
520
499
  }
521
500
  ```
522
501
 
523
- **Error Handling:**
524
-
525
- ```typescript
526
- try {
527
- const result = await googleSignIn('YOUR_WEB_CLIENT_ID');
528
- } catch (error) {
529
- if (error.code === CredentialError.NO_PLAY_SERVICES_ERROR) {
530
- console.error('Google Play Services not available');
502
+ ## 📱 Expo
503
+
504
+ Not supported in Expo Go (it only contains the Expo SDK native code; importing the library there throws an error saying
505
+ so): use a development build (`npx expo run:ios` / `npx expo run:android` or `eas build --profile development`). The
506
+ config plugin writes the native config:
507
+
508
+ ```json
509
+ {
510
+ "expo": {
511
+ "plugins": [
512
+ ["react-native-oauth-essentials", {
513
+ "google": { "iosClientId": "1234-abcd.apps.googleusercontent.com", "webClientId": "5678-efgh.apps.googleusercontent.com" },
514
+ "appleSignIn": true,
515
+ "webCredentialsDomains": ["example.com"]
516
+ }]
517
+ ]
531
518
  }
532
519
  }
533
520
  ```
534
521
 
535
- ## Short history
522
+ - `google`: `{ iosClientId?, webClientId? }` or `true`. Missing ids are read from the Firebase files set in
523
+ `ios.googleServicesFile` / `android.googleServicesFile`. iOS: the reversed iOS client id URL scheme and `GIDClientID` /
524
+ `GIDServerClientID` in Info.plist. Android: the `com.oauthessentials.GOOGLE_WEB_CLIENT_ID` meta-data. Then call
525
+ `googleSignIn()` without client ids.
526
+ - `appleSignIn`: adds the Sign in with Apple entitlement.
527
+ - `webCredentialsDomains`: adds `webcredentials:` associated domains (passkeys and saved passwords).
528
+
529
+ If the native module is missing outside Expo Go, rebuild the app after installing (`pod install` + native build;
530
+ reloading Metro is not enough) with the New Architecture enabled.
531
+
532
+ [Expo docs](https://lucaswitch.github.io/react-native-oauth-essentials/docs/expo)
533
+
534
+ ## 🧪 Testing (Jest)
535
+
536
+ Importing the library in Jest fails with a `native module "OauthEssentials" was not found` error unless you use the
537
+ bundled mock:
538
+
539
+ ```js
540
+ // jest.setup.js (referenced by "setupFiles" in the Jest config)
541
+ jest.mock('react-native-oauth-essentials', () => require('react-native-oauth-essentials/jest'));
542
+ ```
543
+
544
+ Every method is a `jest.fn()` resolving a plausible result; override it per test with `mockResolvedValueOnce` /
545
+ `mockRejectedValueOnce`. [Testing docs](https://lucaswitch.github.io/react-native-oauth-essentials/docs/testing)
546
+
547
+ ## 🤝 Contributing
548
+
549
+ Contributions are welcome! Read the [contributing guide](./CONTRIBUTING.md) and the [code of conduct](./CODE_OF_CONDUCT.md).
550
+
551
+ ## 💬 Short history
536
552
 
537
553
  I’ve always felt that React Native lacked a single library that handles OAuth end-to-end. Most React Native apps end up
538
554
  downloading multiple libraries just to implement OAuth features. Each library feels different—they follow different
@@ -546,6 +562,14 @@ I was frustrated going through this process myself, and seeing how much time Rea
546
562
  deliver a “good enough” sign-in experience. So I thought—why not create one library that actually behaves and doesn’t
547
563
  make me want to throw my laptop out the window?
548
564
 
549
- ## Philosophy
565
+ ## 💚 Philosophy
550
566
 
551
567
  Free forever, up-to-date features, and focused only on the essentials your mobile app really needs.
568
+
569
+ ---
570
+
571
+ <div align="center">
572
+
573
+ MIT © [Lucas Alves](https://github.com/lucaswitch) · If this saved you time, a ⭐ on [GitHub](https://github.com/lucaswitch/react-native-oauth-essentials) helps a lot!
574
+
575
+ </div>