react-native-oauth-essentials 1.0.12 → 1.1.1

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