expo-auth-session & OAuth
Authorization Code com PKCE é o padrão OAuth para aplicativos Expo. Esta página cobre a configuração de URI de redirecionamento, expo-auth-session, e peculiaridades específicas de provedores para Google e Apple no SDK 57.
Busque em todas as páginas da documentação
Authorization Code com PKCE é o padrão OAuth para aplicativos Expo. Esta página cobre a configuração de URI de redirecionamento, expo-auth-session, e peculiaridades específicas de provedores para Google e Apple no SDK 57.
npx expo install expo-auth-session expo-crypto expo-web-browserexpo-web-browser abre o navegador do sistema (ou ASWebAuthenticationSession no iOS) - prefira-o em vez de WebViews incorporadas para OAuth.
Registre um esquema de URL personalizado em app.config.ts:
// app.config.ts
export default {
expo: {
name: "MyApp",
slug: "my-app",
scheme: "myapp",
ios: { bundleIdentifier: "com.example.myapp" },
android: { package: "com.example.myapp" },
},
};Ferramentas: Estes exemplos visam Expo SDK 57 (
expo~57.0.4), React Native 0.86.0, e React 19.2.3.
PKCE (Proof Key for Code Exchange) impede a interceptação do código de autorização em clientes móveis públicos.
import * as Crypto from "expo-crypto";
function base64UrlEncode(bytes: Uint8Array) {
const base64 = btoa(String.fromCharCode(...bytes));
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
export async function createPkcePair() {
const verifierBytes = await Crypto.getRandomBytesAsync(32);
const codeVerifier = base64UrlEncode(verifierBytes);
const digest = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
codeVerifier,
{ encoding: Crypto.CryptoEncoding.BASE64 },
);
const codeChallenge = digest
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
return { codeVerifier, codeChallenge };
}expo-auth-session gera pares PKCE automaticamente quando usePKCE: true - a geração manual é para backends personalizadosresponse_type=token) - tokens de acesso em URLs de redirecionamento vazam através de logs e cabeçalhos referenciadoresRelacionado: Noções Básicas de Autenticação Móvel - normalizando tokens OAuth para o seu tipo
Session
As URIs de redirecionamento devem corresponder exatamente ao que você registra com cada provedor OAuth.
import { makeRedirectUri } from "expo-auth-session";
import * as AuthSession from "expo-auth-session";
const redirectUri = makeRedirectUri({
scheme: "myapp",
path: "oauth",
});
// Exemplos por ambiente:
// Cliente de desenvolvimento / standalone: myapp://oauth
// Expo Go (proxy): https://auth.expo.io/@owner/slug (legado - evite para produção)
const discovery = {
authorizationEndpoint: "https://accounts.example.com/oauth/authorize",
tokenEndpoint: "https://accounts.example.com/oauth/token",
};
const [request, response, promptAsync] = AuthSession.useAuthRequest(
{
clientId: process.env.EXPO_PUBLIC_OAUTH_CLIENT_ID!,
redirectUri,
scopes: ["openid", "profile", "email"],
usePKCE: true,
responseType: AuthSession.ResponseType.Code,
},
discovery,
);makeRedirectUri deriva a URI correta do seu scheme, build native/development, e configuração do Exporedirect_uri_mismatchmyapp://) para produção - não exp:// ou URLs de proxy do Expo GoredirectUri uma vez em desenvolvimento para copiar no Google Cloud Console e no portal do Apple Developerimport { useEffect } from "react";
import { Button, Text, View } from "react-native";
import * as AuthSession from "expo-auth-session";
import { makeRedirectUri } from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
import * as SecureStore from "expo-secure-store";
WebBrowser.maybeCompleteAuthSession();
const redirectUri = makeRedirectUri({ scheme: "myapp", path: "oauth" });
const discovery: AuthSession.DiscoveryDocument = {
authorizationEndpoint: `${process.env.EXPO_PUBLIC_API_URL}/oauth/authorize`,
tokenEndpoint: `${process.env.EXPO_PUBLIC_API_URL}/oauth/token`,
};
export function OAuthSignInButton() {
const [request, response, promptAsync] = AuthSession.useAuthRequest(
{
clientId: process.env.EXPO_PUBLIC_OAUTH_CLIENT_ID!,
redirectUri,
scopes: ["openid", "profile", "email"],
usePKCE: true,
},
discovery,
);
useEffect(() => {
if (response?.type !== "success") return;
const { code } = response.params;
exchangeCodeOnBackend(code, request?.codeVerifier);
}, [response]);
return (
<View>
<Button
title="Sign in"
disabled={!request}
onPress={() => promptAsync({ preferEphemeralSession: true })}
/>
</View>
);
}
async function exchangeCodeOnBackend(code: string, codeVerifier?: string) {
const res = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/auth/oauth/callback`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code, codeVerifier, redirectUri }),
});
if (!res.ok) throw new Error("OAuth exchange failed");
const { refreshToken, accessToken, expiresAt, userId } = await res.json();
await SecureStore.setItemAsync("auth.refresh_token", refreshToken, {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
// Hidrata o estado da sessão do AuthProvider com { accessToken, refreshToken, expiresAt, userId }
}WebBrowser.maybeCompleteAuthSession() descarta a aba do navegador quando o redirecionamento ocorre - chame uma vez no escopo do módulopreferEphemeralSession: true no iOS evita o compartilhamento de cookies com o Safari - bom para dispositivos compartilhadosRelacionado: ../expo-rules/security-rules-for-mobile/security-rules-for-mobile.md - nunca envie segredos de cliente OAuth no bundle
O Google exige IDs de cliente OAuth separados por plataforma.
Google Cloud Console → APIs & Services → Credentials
┌─────────────────┬──────────────────────────────────────────────┐
│ Tipo de cliente │ Usado para │
├─────────────────┼──────────────────────────────────────────────┤
│ iOS │ Bundle ID: com.example.myapp │
│ Android │ Pacote + impressão digital do certificado de assinatura SHA-1 │
│ Web │ Troca de código do backend (segredo do cliente) │
└─────────────────┴──────────────────────────────────────────────┘// Use o ID do cliente iOS ou Android no aplicativo - NÃO o segredo do cliente web
const GOOGLE_CLIENT_ID = Platform.select({
ios: process.env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID,
android: process.env.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID,
default: process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
});expo-auth-session, o scheme do seu aplicativo lida com o redirecionamentoErros comuns:
| Erro | Correção |
|---|---|
redirect_uri_mismatch | Copie a saída exata de makeRedirectUri() para o console do Google |
invalid_client no Android | Adicione o SHA-1 correto para o perfil de build que você está testando |
access_denied | Tela de consentimento OAuth no modo de Teste - adicione usuários de teste |
O Apple Sign In é obrigatório quando você oferece login de terceiros no iOS para aplicativos em certas categorias.
{
"expo": {
"ios": {
"usesAppleSignIn": true,
"bundleIdentifier": "com.example.myapp"
},
"plugins": ["expo-apple-authentication"]
}
}npx expo install expo-apple-authenticationimport * as AppleAuthentication from "expo-apple-authentication";
import { Platform } from "react-native";
export function AppleSignInButton() {
if (Platform.OS !== "ios") return null;
return (
<AppleAuthentication.AppleAuthenticationButton
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
buttonStyle={AppleAuthentication.AppleAuthenticationButtonStyle.BLACK}
cornerRadius={8}
style={{ width: "100%", height: 44 }}
onPress={async () => {
const credential = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL,
],
});
await sendAppleCredentialToBackend(credential);
}}
/>
);
}@privaterelay.appleid.comexpo-apple-authentication usa o Sign in with Apple nativo - distinto do fluxo genérico do navegador OAuthPara Auth0, Okta ou Keycloak, use a descoberta integrada:
import * as AuthSession from "expo-auth-session";
const discovery = await AuthSession.fetchDiscoveryAsync(
"https://your-tenant.auth0.com",
);
const [request, response, promptAsync] = AuthSession.useAuthRequest(
{
clientId: process.env.EXPO_PUBLIC_AUTH0_CLIENT_ID!,
redirectUri: makeRedirectUri({ scheme: "myapp" }),
scopes: ["openid", "profile", "offline_access"],
usePKCE: true,
extraParams: { audience: "https://api.example.com" },
},
discovery,
);offline_access é necessário em muitos IdPs para receber um token de atualizaçãoaudience (Auth0) garante que os tokens de acesso sejam emitidos para sua API - não apenas para o endpoint de informações do usuário do IdPauthorizationEndpoint e tokenEndpoint no JSON de descobertaAntes de enviar o OAuth:
□ redirectUri registrado com o esquema de produção (myapp://)
□ SHA-1 do Google Android corresponde ao keystore de produção do EAS
□ Entitlement do Apple Sign In presente no perfil de provisionamento
□ Backend valida code_verifier (PKCE)
□ Tokens de atualização armazenados em SecureStore - não AsyncStorage
□ Testado em dispositivo físico com build de desenvolvimento - não apenas Expo Go
□ OAuth testado após fechar o aplicativo e reabri-lo (inicialização a frio de atualização)expo-dev-client) com seu ID de bundle real antes de testar a integração OAuthRelacionado: Ciclo de Vida da Sessão de Autenticação - atualizar após OAuth ao retomar o aplicativo
expo-auth-session + expo-web-browser (navegador do sistema / ASWebAuthenticationSession).eas credentials e adicione a impressão digital correta ao Google Cloud Console.myapp://) são suficientes para a maioria dos aplicativos.https://app.example.com/oauth) adicionam complexidade, mas evitam colisão de esquemas - opcional para empresas.response.type === "cancel" ou "dismiss" - não exiba nada ou exiba uma mensagem suave; não trate como um erro que vale a pena registrar no Sentry.Versões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0, e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026