@pj21/expo-social-auth
@pj21/expo-social-auth
Google, Apple, and Facebook sign-in for Expo / React Native apps with one unified, fully-typed API.
- One result shape for every provider — a discriminated union (
success/cancelled/error), no try/catch needed - Returns the provider
idTokenready to send to your backend for verification - Safe in Expo Go — native modules are loaded lazily, so nothing crashes; you get a clean
module_not_availableerror instead - Single install — the Google, Apple, and Facebook SDKs come bundled and are autolinked automatically
Installation
npx expo install @pj21/expo-social-auth
That's it — @react-native-google-signin/google-signin, expo-apple-authentication, and react-native-fbsdk-next are installed with it and picked up by Expo autolinking (SDK 52+).
The providers require native code, so you need a development build (
npx expo run:ios/run:android). They do not work in Expo Go.On an older Expo SDK, align the Apple module with your SDK after installing:
npx expo install expo-apple-authentication.
Using only one provider
If you don't use every provider, only enable the ones you need in the config plugin — disabled providers get no native configuration. To additionally keep an unused SDK's native code out of your app binary, use Expo autolinking's official exclude list — add it to your app's package.json (not app.json) with any of expo-apple-authentication, @react-native-google-signin/google-signin, react-native-fbsdk-next:
{
"expo": {
"autolinking": {
"exclude": ["expo-apple-authentication"]
}
}
}
…or to skip Google instead:
{
"expo": {
"autolinking": {
"exclude": ["@react-native-google-signin/google-signin"]
}
}
}
Then rebuild (npx expo prebuild --clean / npx expo run:ios). The excluded module's native code is no longer compiled into your app — only its few-KB JS wrapper remains in node_modules. Calling the excluded provider at runtime safely returns a module_not_available error result instead of crashing.
Setup
What's required vs optional
Google — required:
| # | Step | Where |
|---|---|---|
| 1 | Create a Web OAuth client ID (produces the idToken) |
Google Cloud Console |
| 2 | Create an iOS OAuth client ID | Google Cloud Console |
| 3 | Create an Android OAuth client ID with your signing key's SHA-1 (or register the SHA-1 in Firebase) | Google Cloud Console / Firebase console |
| 4 | Register the iOS redirect URL scheme | Config plugin (google.iosUrlScheme) — or automatic with Firebase / manual in bare workflow |
| 5 | Call configureGoogleSignIn({ webClientId }) at app startup |
Your code |
Google — optional: iosClientId (omit when using Firebase — read from GoogleService-Info.plist), scopes, offlineAccess, hostedDomain, profileImageSize.
Apple — required:
| # | Step | Where |
|---|---|---|
| 1 | Enable the Sign In with Apple capability for your app identifier | Apple Developer portal |
| 2 | Add the capability/entitlement to the app | Config plugin ("apple": true) — or Xcode in bare workflow |
Apple — optional: requestedScopes on signInWithApple() (defaults to both fullName and email). No startup configuration is needed.
Facebook — required:
| # | Step | Where |
|---|---|---|
| 1 | Create an app and get the App ID and Client Token | Meta for Developers |
| 2 | Pass appID, clientToken, displayName, and scheme (fb + App ID) to the config plugin |
Config plugin (facebook option) — or native files in bare workflow |
Facebook — optional: permissions on signInWithFacebook() (defaults to ["public_profile", "email"]).
Apple's App Store review requires offering Sign In with Apple if your iOS app offers any other third-party login (like Google) — plan to ship both on iOS.
How much native setup you need depends on your workflow:
- Managed workflow / CNG (no committed
ios/&android/folders —npx expo prebuildgenerates them): use the config plugins below. - Bare workflow (committed
ios/&android/folders): skip the plugin and configure the native projects directly — see Bare workflow.
Config plugin (managed workflow)
One plugin entry configures everything. Add it to app.json / app.config.js:
{
"expo": {
"plugins": [
[
"@pj21/expo-social-auth",
{
"google": {
"iosUrlScheme": "com.googleusercontent.apps.YOUR_IOS_CLIENT_ID"
},
"apple": true,
"facebook": {
"appID": "YOUR_FB_APP_ID",
"clientToken": "YOUR_FB_CLIENT_TOKEN",
"displayName": "YourApp",
"scheme": "fbYOUR_FB_APP_ID"
}
}
]
]
}
}
google option
| Value | Effect |
|---|---|
{ "iosUrlScheme": "com.googleusercontent.apps.…" } |
Standalone setup — adds the Google redirect URL scheme to Info.plist |
{ "firebase": true } |
Firebase setup — wires up your googleServicesFile on both platforms (skip if @react-native-firebase/app already does this) |
true |
No native changes — for projects already configured |
false (default) |
Skip Google configuration |
apple option
| Value | Effect |
|---|---|
true |
Adds the Sign In with Apple capability/entitlement |
false (default) |
Skip Apple configuration |
facebook option
| Value | Effect |
|---|---|
{ "appID", "clientToken", "displayName", "scheme", … } |
Configures the Facebook SDK on both platforms (options are passed through to the react-native-fbsdk-next plugin) |
false (default) |
Skip Facebook configuration |
Only the providers you explicitly enable get configured — omitting an option means that provider's native setup is skipped. Pick one of the google object modes: iosUrlScheme for standalone setups, firebase: true for Firebase setups.
You still need the accounts-side setup from the checklist above, and to configure Google once at app startup (e.g. in your root layout):
import { configureGoogleSignIn } from "@pj21/expo-social-auth";
configureGoogleSignIn({
webClientId: "YOUR_WEB_CLIENT_ID.apps.googleusercontent.com",
iosClientId: "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com", // omit when using Firebase
});
Bare workflow
If your ios/ and android/ folders are committed, config plugins don't run — configure the native projects instead:
- Apple: in Xcode, add the Sign In with Apple capability to your target (adds
com.apple.developer.applesigninto your.entitlementsfile). - Google (iOS): add your reversed iOS client ID (
com.googleusercontent.apps.YOUR_IOS_CLIENT_ID) as a URL scheme inInfo.plistunderCFBundleURLTypes. If you use Firebase, this is theREVERSED_CLIENT_IDfromGoogleService-Info.plist, and the SDK picks up the client ID from that file automatically. - Google (Android): no native changes needed — just make sure your signing key's SHA-1 fingerprint is registered (in the Firebase console or in the Android OAuth client in Google Cloud Console).
- Facebook: follow the fbsdk-next manual setup — App ID/Client Token in
Info.plistandstrings.xml/AndroidManifest.xml, plus thefb…URL scheme on iOS.
Either way, you still call configureGoogleSignIn({ webClientId }) at startup — the web client ID is what makes Google return an idToken you can verify on your backend.
Usage
With the hook
import { useSocialAuth } from "@pj21/expo-social-auth";
function LoginButtons() {
const { signIn, loading, activeProvider } = useSocialAuth();
const handle = async (provider: "google" | "apple" | "facebook") => {
const result = await signIn(provider);
switch (result.type) {
case "success":
// Verify this JWT on your backend
await api.loginSocial({ token: result.idToken, platform: provider });
break;
case "cancelled":
break; // user backed out — usually nothing to do
case "error":
console.warn(result.code, result.message);
break;
}
};
return (
<>
<Button
title="Continue with Google"
disabled={loading}
onPress={() => handle("google")}
/>
<Button
title="Continue with Apple"
disabled={loading}
onPress={() => handle("apple")}
/>
<Button
title="Continue with Facebook"
disabled={loading}
onPress={() => handle("facebook")}
/>
</>
);
}
Without the hook
Every function also works standalone, outside React:
import {
signInWithGoogle,
signInWithApple,
signOutFromGoogle,
isAppleSignInAvailable,
} from "@pj21/expo-social-auth";
const result = await signInWithGoogle();
if (result.type === "success") {
console.log(result.idToken, result.accessToken, result.user.email);
}
// Only show the Apple button where it's supported (iOS 13+)
const showApple = await isAppleSignInAvailable();
API
Functions
| Function | Description |
|---|---|
configureGoogleSignIn(config) |
Store Google config; applied lazily on first sign-in. Safe to call anywhere, including Expo Go. |
signInWithGoogle() |
Runs the Google flow → Promise<SocialAuthResult> |
signOutFromGoogle() |
Revokes access and signs out. Never throws. |
isGoogleSignInAvailable() |
true if the native Google module is installed/linked |
signInWithApple(options?) |
Runs the Apple flow → Promise<SocialAuthResult> |
isAppleSignInAvailable() |
Promise<boolean> — iOS 13+ with the module installed |
signInWithFacebook(options?) |
Runs the Facebook flow → Promise<SocialAuthResult> |
signOutFromFacebook() |
Logs out of Facebook. Never throws. |
isFacebookSignInAvailable() |
true if the native Facebook module is installed/linked |
useSocialAuth() |
React hook wrapping all of the above with loading state |
configureGoogleSignIn(config) options
| Option | Required | Description |
|---|---|---|
webClientId |
Yes | Web client ID from Google Cloud Console — without it Google returns no idToken |
iosClientId |
No | iOS client ID; omit when using Firebase (read from GoogleService-Info.plist) |
scopes |
No | Extra OAuth scopes (default: email, profile) |
offlineAccess |
No | Request a server auth code for offline access |
hostedDomain |
No | Restrict sign-in to a Google Workspace domain |
profileImageSize |
No | Requested avatar size in pixels |
signInWithApple(options?) options
| Option | Required | Description |
|---|---|---|
requestedScopes |
No | Array of "fullName" / "email" (default: both) |
signInWithFacebook(options?) options
| Option | Required | Description |
|---|---|---|
permissions |
No | Facebook permissions to request (default: ["public_profile", "email"]) |
SocialAuthResult
type SocialAuthResult =
| {
type: "success";
provider: "google" | "apple" | "facebook";
idToken: string; // the token to verify on your backend
accessToken?: string; // Google & Facebook
authorizationCode?: string; // Apple only
user: SocialUser;
}
| { type: "cancelled"; provider: "google" | "apple" | "facebook" }
| {
type: "error";
provider: "google" | "apple" | "facebook";
code: SocialAuthErrorCode;
message: string;
underlyingError?: unknown;
};
Error codes: module_not_available, play_services_not_available, in_progress, not_supported, no_id_token, unknown.
SocialUser
type SocialUser = {
id: string;
email: string | null; // Facebook: needs the "email" permission granted
name: string | null;
givenName: string | null;
familyName: string | null;
photo: string | null; // always null for Apple
};
Apple only returns
idTokenclaims.
Backend verification
Never trust the client — always verify the idToken server-side:
- Google: verify the JWT against
https://www.googleapis.com/oauth2/v3/certsand checkaudequals your web client ID. - Apple: verify against
https://appleid.apple.com/auth/keysand checkaudequals your bundle ID. - Facebook: the token is an opaque access token, not a JWT — validate it with
GET https://graph.facebook.com/debug_token?input_token=TOKEN&access_token=APP_ID|APP_SECRETand checkapp_idandis_valid.
License
MIT