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.
- package/OauthEssentials.podspec +17 -2
- package/README.md +385 -361
- package/android/build.gradle +15 -10
- package/android/src/main/java/com/oauthessentials/CredentialError.kt +4 -1
- package/android/src/main/java/com/oauthessentials/CredentialFactory.kt +24 -2
- package/android/src/main/java/com/oauthessentials/CustomTabsActivity.kt +14 -1
- package/android/src/main/java/com/oauthessentials/OauthEssentialsModule.kt +445 -187
- package/app.plugin.js +1 -0
- package/ios/AppleIdRetriever.swift +25 -26
- package/ios/CredentialError.swift +3 -0
- package/ios/CredentialFactory.swift +38 -20
- package/ios/CredentialsType.swift +1 -0
- package/ios/GoogleIdRetriever.swift +132 -56
- package/ios/HybridRetriever.swift +12 -18
- package/ios/OauthEssentials.h +0 -3
- package/ios/OauthEssentials.mm +45 -2
- package/ios/OauthEssentialsModule.swift +266 -57
- package/ios/PasskeyRetriever.swift +31 -49
- package/ios/PasswordRetriever.swift +10 -30
- package/ios/RCTPromiseSettler.swift +125 -0
- package/jest/index.js +149 -0
- package/lib/module/NativeOauthEssentials.js +10 -1
- package/lib/module/NativeOauthEssentials.js.map +1 -1
- package/lib/module/errors.js +38 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +432 -58
- package/lib/module/index.js.map +1 -1
- package/lib/module/oauth.js +97 -0
- package/lib/module/oauth.js.map +1 -0
- package/lib/module/platform.js +108 -0
- package/lib/module/platform.js.map +1 -0
- package/lib/module/types.js +46 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/NativeOauthEssentials.d.ts +70 -16
- package/lib/typescript/src/NativeOauthEssentials.d.ts.map +1 -1
- package/lib/typescript/src/errors.d.ts +18 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +235 -40
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/oauth.d.ts +32 -0
- package/lib/typescript/src/oauth.d.ts.map +1 -0
- package/lib/typescript/src/platform.d.ts +39 -0
- package/lib/typescript/src/platform.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +190 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +9 -5
- package/plugin/withOauthEssentials.js +291 -0
- package/src/NativeOauthEssentials.ts +91 -17
- package/src/errors.ts +49 -0
- package/src/index.tsx +587 -80
- package/src/oauth.ts +140 -0
- package/src/platform.ts +121 -0
- package/src/types.ts +200 -1
package/README.md
CHANGED
|
@@ -1,181 +1,148 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/react-native-oauth-essentials)
|
|
9
|
+
[](https://www.npmjs.com/package/react-native-oauth-essentials)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+

|
|
12
|
+

|
|
13
|
+

|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
16
|
-
yarn add react-native-oauth-essentials
|
|
17
|
-
```
|
|
20
|
+
</div>
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
---
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
## ✨ Why this library?
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
> [!NOTE]
|
|
37
|
+
> Requires the **New Architecture**. Not supported in Expo Go: use a [development build](#-expo).
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
## 📦 Installation
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
return <Button title="Continue with Google" onPress={onPress} />;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
119
91
|
|
|
120
|
-
**
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
163
|
+
</details>
|
|
201
164
|
|
|
202
|
-
|
|
165
|
+
### Conventions
|
|
203
166
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
174
|
+
<details>
|
|
175
|
+
<summary><b>Constants</b> (feature detection)</summary>
|
|
214
176
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
187
|
+
</details>
|
|
222
188
|
|
|
223
|
-
|
|
189
|
+
### Google
|
|
224
190
|
|
|
225
|
-
|
|
191
|
+
#### `googleSignIn(clientId?, options?)`
|
|
226
192
|
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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(
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
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
|
-
#### `
|
|
222
|
+
#### `googleSilentSignIn(clientId?, options?)`
|
|
253
223
|
|
|
254
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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
|
-
|
|
235
|
+
#### `signOut()`
|
|
260
236
|
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
240
|
+
### Apple
|
|
266
241
|
|
|
267
|
-
|
|
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://
|
|
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
|
|
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
|
|
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
|
-
#### `
|
|
261
|
+
#### `getAppleIdCredentialState(userId)` (iOS)
|
|
286
262
|
|
|
287
|
-
|
|
263
|
+
Checks without UI whether the Apple ID `user` is still signed in to the app. Call it on app start.
|
|
288
264
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
272
|
+
### Passwords
|
|
294
273
|
|
|
295
|
-
|
|
274
|
+
#### `getPassword()`
|
|
296
275
|
|
|
297
|
-
**
|
|
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
|
|
301
|
-
|
|
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
|
-
#### `
|
|
290
|
+
#### `passwordSignIn(username, password)`
|
|
307
291
|
|
|
308
|
-
|
|
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
|
-
|
|
295
|
+
### Hybrid
|
|
311
296
|
|
|
312
|
-
|
|
297
|
+
#### `hybridSignIn(clientId?, options?)`
|
|
313
298
|
|
|
314
|
-
|
|
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
|
|
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
|
-
|
|
309
|
+
### Passkeys
|
|
330
310
|
|
|
331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
319
|
+
#### `getPassKey({ requestJson, mediation? })`
|
|
340
320
|
|
|
341
|
-
|
|
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
|
|
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
|
-
|
|
369
|
-
|
|
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
|
-
|
|
334
|
+
### Generic OAuth / OpenID Connect
|
|
372
335
|
|
|
373
|
-
|
|
336
|
+
#### `oauthSignIn(options)`
|
|
374
337
|
|
|
375
|
-
|
|
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
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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.
|
|
385
|
-
|
|
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
|
-
|
|
363
|
+
success.remove();
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Success and failure fire for `googleSignIn`, `googleSilentSignIn`, `getPassword`, `appleSignIn`, `hybridSignIn`,
|
|
367
|
+
`createPassKey` and `getPassKey`.
|
|
392
368
|
|
|
393
|
-
|
|
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
|
-
|
|
398
|
-
|
|
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
|
-
|
|
387
|
+
type AppleSignInOptions = { nonce?: string; state?: string }; // iOS
|
|
403
388
|
|
|
404
|
-
```typescript
|
|
405
389
|
type PasskeyOptions = {
|
|
406
|
-
requestJson: string;
|
|
407
|
-
preferImmediatelyAvailableCredentials?: boolean; // Android
|
|
408
|
-
|
|
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
|
-
|
|
407
|
+
</details>
|
|
415
408
|
|
|
416
|
-
|
|
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;
|
|
423
|
-
idToken: string;
|
|
424
|
-
displayName: string;
|
|
425
|
-
givenName: string;
|
|
426
|
-
familyName: string;
|
|
427
|
-
|
|
428
|
-
|
|
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;
|
|
440
|
-
authorizationCode: string;
|
|
441
|
-
user: string;
|
|
442
|
-
email: string;
|
|
443
|
-
fullName: string;
|
|
444
|
-
likelyReal: boolean;
|
|
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;
|
|
455
|
+
data: string; // WebAuthn response JSON
|
|
484
456
|
};
|
|
485
|
-
```
|
|
486
457
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
###
|
|
475
|
+
### Error codes
|
|
499
476
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
489
|
+
`isCredentialError(error, code?)` narrows an unknown error to `OauthEssentialsError` (`Error & { code }`):
|
|
514
490
|
|
|
515
491
|
```typescript
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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>
|