Autenticación
Descripción general
Sección titulada «Descripción general»El módulo client.auth gestiona la autenticación de usuarios, la administración de tokens y la persistencia de sesiones. Una vez que un usuario inicia sesión, todas las solicitudes de datos posteriores incluyen automáticamente el JWT.
El SDK persiste las sesiones en localStorage de forma predeterminada y actualiza automáticamente los tokens antes de que expiren.
Correo electrónico / Contraseña
Sección titulada «Correo electrónico / Contraseña»Iniciar sesión
Sección titulada «Iniciar sesión»const { user, accessToken, refreshToken } = await client.auth.signInWithEmail( "user@example.com", "password");console.log(user.uid, user.email);Registrarse
Sección titulada «Registrarse»const { user, confirmationRequired } = await client.auth.signUp( "user@example.com", "password", "Jane Doe" // optional displayName);Con requireEmailVerification activado,
no hay sesión hasta que se confirma la dirección: confirmationRequired es
true y user es null, así que indícale a la persona que abra el correo. En caso contrario,
confirmationRequired es false y user es la cuenta que inició sesión. En 0.23
signUp resuelve { user, accessToken, refreshToken } y siempre inicia sesión.
Proveedores OAuth
Sección titulada «Proveedores OAuth»El SDK incluye métodos dedicados para proveedores OAuth populares, además de un signInWithOAuth() genérico para cualquier proveedor personalizado.
Admite tres estilos de invocación:
// ID-token flow (One Tap / Sign In With Google button)await client.auth.signInWithGoogle({ idToken: googleIdToken });
// Access-token flow (popup)await client.auth.signInWithGoogle({ accessToken: googleAccessToken });
// Authorization code flow (most secure, server-side exchange)await client.auth.signInWithGoogle({ code: authCode, redirectUri: "https://..." });Otros proveedores
Sección titulada «Otros proveedores»Cada proveedor sigue el flujo de código de autorización con (code, redirectUri):
await client.auth.signInWithGitHub(code, redirectUri);await client.auth.signInWithMicrosoft(code, redirectUri);await client.auth.signInWithFacebook(code, redirectUri);await client.auth.signInWithLinkedin(code, redirectUri);await client.auth.signInWithDiscord(code, redirectUri);await client.auth.signInWithGitLab(code, redirectUri);await client.auth.signInWithBitbucket(code, redirectUri);await client.auth.signInWithSlack(code, redirectUri);await client.auth.signInWithSpotify(code, redirectUri);Apple y Twitter requieren parámetros adicionales:
// Apple — optional user info from first sign-inawait client.auth.signInWithApple(code, redirectUri, { name: { firstName: "Jane", lastName: "Doe" }, email: "jane@example.com"});
// Twitter — requires PKCE code verifierawait client.auth.signInWithTwitter(code, redirectUri, codeVerifier);OAuth genérico
Sección titulada «OAuth genérico»Para cualquier proveedor registrado en el backend:
await client.auth.signInWithOAuth("custom-provider", { code: authCode, redirectUri: "https://myapp.com/callback"});Enlaces mágicos (Magic Links)
Sección titulada «Enlaces mágicos (Magic Links)»Un enlace de inicio de sesión de un solo clic por correo electrónico. El enlace siempre es
<base>/auth/magic-link?token=…, donde <base> es el email.magicLinkUrl del
backend, o su base de restablecimiento de contraseña (FRONTEND_URL en el
runtime) cuando eso no está establecido. No hay un redirectTo por solicitud.
Sirve esa ruta en tu frontend, y devuelve el token para canjearlo por una
sesión. El CMS lo sirve, iniciando sesión (a través del paso de
código cuando hay un segundo factor); otros frontends necesitan una página de
aterrizaje.
// 1. Ask for the link.await client.auth.sendMagicLink("user@example.com");
// 2. On the landing page, trade the token for a session.const token = new URLSearchParams(location.search).get("token")!;const { user } = await client.auth.verifyMagicLink(token);sendMagicLink responde lo mismo independientemente de si la dirección tiene o no una cuenta. Esto es deliberado: un endpoint que respondiera “no existe dicho usuario” sería un oráculo de enumeración de cuentas, así que no uses el resultado para decirle a una persona si está registrada — no lo sabe.
Ambos necesitan un servicio de correo electrónico configurado en el backend, o responderán 503 EMAIL_NOT_CONFIGURED.
Códigos de un solo uso
Sección titulada «Códigos de un solo uso»Un código de seis dígitos por correo electrónico, para los casos en los que un enlace resulta incómodo: una aplicación nativa, un segundo dispositivo o un navegador que altere los enlaces.
const { expiresInSeconds } = await client.auth.sendEmailOtp("user@example.com");
// The address goes back with the code, because the code is only valid for it.const { user } = await client.auth.verifyEmailOtp("user@example.com", "418293");Enviar la dirección junto con el código es lo que hace que un intento de adivinar los seis dígitos sea contra una sola cuenta en lugar de contra todas las cuentas a la vez.
Sesiones anónimas
Sección titulada «Sesiones anónimas»Inicia la sesión de un visitante sin ninguna credencial, para que pueda comenzar a usar la aplicación antes de tener un motivo para registrarse:
const { user } = await client.auth.signInAnonymously();user.isAnonymous; // trueLa cuenta es real: tiene un id, roles y una sesión, por lo que la seguridad a nivel de fila (row-level security) delimita sus filas exactamente como lo haría con las de un usuario registrado. Lo que no tiene es una forma de volver: nadie puede iniciar sesión como esa cuenta por segunda vez, por lo que todo lo que le pertenece se pierde con la sesión.
linkAnonymous es la forma en que deja de ser desechable. El usuario conserva su id, por lo que todo lo que creó mientras era anónimo sigue siendo suyo:
await client.auth.linkAnonymous("user@example.com", "correct-horse-battery");| Fallo | Significado |
|---|---|
ANONYMOUS_AUTH_DISABLED (403) |
El backend no ha habilitado la autenticación anónima |
NOT_ANONYMOUS (400) |
La sesión actual pertenece a una cuenta ordinaria |
EMAIL_EXISTS (409) |
La dirección ya tiene una cuenta — inicia sesión en esa en su lugar |
Vincular un proveedor a una cuenta existente
Sección titulada «Vincular un proveedor a una cuenta existente»signInWithGoogle y métodos similares inician la sesión de un usuario. linkProvider vincula una identidad de proveedor a la cuenta que ya ha iniciado sesión, para que la misma persona pueda volver a través de cualquiera de las dos vías:
await client.auth.linkProvider("google", { idToken });La sesión ya demuestra la propiedad de la cuenta, por lo que, a diferencia del inicio de sesión, esto no requiere que el proveedor haya verificado el correo electrónico y las dos direcciones no necesitan coincidir. Tiene éxito de forma idempotente (alreadyLinked: true) cuando esa identidad ya está en esta cuenta, y rechaza con IDENTITY_ALREADY_LINKED (409) cuando pertenece a una diferente.
Buscar un usuario por correo electrónico
Sección titulada «Buscar un usuario por correo electrónico»const profile = await client.auth.findUserByEmail("user@example.com");// { uid, displayName, photoURL } | nullTres campos no sensibles y nada más — lo suficiente para mostrar “estás invitando a Jane” antes de enviar una invitación.
Autenticación multifactor
Sección titulada «Autenticación multifactor»Factores TOTP — una aplicación de autenticación — más el desafío que eleva una sesión de aal1 a aal2.
Registrar un factor
Sección titulada «Registrar un factor»const { factor, totp, recoveryCodes } = await client.auth.mfa.enroll({ friendlyName: "Phone"});
showQrCode(totp.uri); // otpauth://… — what the authenticator scansif (recoveryCodes) showRecoveryCodes(recoveryCodes);Muestra los códigos de recuperación una sola vez y nunca más. Solo se almacenan sus hashes, por lo que nada podrá mostrarlos más tarde. Vienen con el
primer factor de la cuenta. Añadir otro factor conserva los códigos que la
cuenta ya tiene, y recoveryCodes es null. Iniciar un registro y
abandonarlo nunca los toca.
Para reemplazarlos, después de usar varios o perder la copia impresa, llama a
regenerateRecoveryCodes() desde una sesión aal2. Los códigos antiguos dejan
de funcionar:
const { recoveryCodes } = await client.auth.mfa.regenerateRecoveryCodes();A quien haya perdido tanto el autenticador como los códigos, un administrador
puede dejarlo entrar de nuevo: client.admin.resetMfa(uid)
(DELETE /api/admin/users/:uid/mfa, users:write) elimina los factores y
códigos de la cuenta y termina sus sesiones, de modo que la contraseña vuelve
a iniciar sesión sin un segundo factor. En 0.23 cada registro devuelve un
nuevo conjunto de códigos que sustituye al anterior, y no existen ni
regenerateRecoveryCodes() ni resetMfa.
El factor no se puede utilizar hasta que el usuario demuestre que su aplicación de autenticación produjo un código a partir de ese secreto:
await client.auth.mfa.verify(factor.id, "418293");Iniciar sesión con MFA
Sección titulada «Iniciar sesión con MFA»Un inicio de sesión contra una cuenta con MFA registrado no devuelve ninguna sesión. Se rechaza con 401 MFA_REQUIRED, y los details del error incluyen un mfaToken y los factors verificados de la cuenta. Pasa ese token a challenge y verifyChallenge para obtener la sesión:
import { RebaseApiError } from "@rebasepro/client";
type MfaRequired = { mfaToken: string; factors: { id: string; factorType: string; friendlyName?: string }[];};
try { await client.auth.signInWithEmail(email, password);} catch (e) { if (!(e instanceof RebaseApiError) || e.code !== "MFA_REQUIRED") throw e; const { mfaToken, factors } = e.details as MfaRequired;
const { challengeId } = await client.auth.mfa.challenge(factors[0].id, { mfaToken });
// A TOTP code, or one of the recovery codes. const { user } = await client.auth.mfa.verifyChallenge(challengeId, "418293", { mfaToken });}El mfaToken se envía solo en esas dos peticiones y nunca se instala en el cliente. verifyChallenge genera la sesión aal2, este cliente la adopta y emite SIGNED_IN como en cualquier otro inicio de sesión. El mfaToken expira cinco minutos después del inicio de sesión que lo devolvió, y un desafío cinco minutos después de abrirse. Un desafío que ha alcanzado su límite de intentos permanece inhabilitado durante el resto de su vida útil; de lo contrario, un desafío abierto permitiría intentos ilimitados para adivinar los seis dígitos.
Sin mfaToken, las dos llamadas elevan la sesión que este cliente ya tiene de aal1 a aal2.
Eliminar un factor
Sección titulada «Eliminar un factor»await client.auth.mfa.unenroll(factorId);Requiere una sesión aal2 — una que ya haya respondido a un desafío —, de modo que un token aal1 robado no pueda desactivar MFA. Eliminar el último factor verificado también descarta los códigos de recuperación.
Cerrar sesión
Sección titulada «Cerrar sesión»await client.auth.signOut();Esto revoca el token de actualización en el servidor, borra la sesión local y emite un evento SIGNED_OUT.
Gestión de sesiones
Sección titulada «Gestión de sesiones»Obtener la sesión actual
Sección titulada «Obtener la sesión actual»const session = client.auth.getSession();// { accessToken, refreshToken, expiresAt, user } | nullObtener el usuario actual (verificado por el servidor)
Sección titulada «Obtener el usuario actual (verificado por el servidor)»const user = await client.auth.getUser();// Fetches the user from the backend (GET /auth/me)Actualizar el perfil del usuario
Sección titulada «Actualizar el perfil del usuario»const updatedUser = await client.auth.updateUser({ displayName: "Jane Doe", photoURL: "https://example.com/avatar.jpg"});Actualizar token
Sección titulada «Actualizar token»La actualización del token se realiza automáticamente, pero puedes activarla manualmente:
const session = await client.auth.refreshSession();Dónde reside la sesión: authFlowMode
Sección titulada «Dónde reside la sesión: authFlowMode»const client = createRebaseClient({ baseUrl: API_URL, auth: { authFlowMode: "cookie" }});| Modo | Dónde está el refresh token | Cuándo usarlo |
|---|---|---|
"json" (predeterminado) |
Devuelto en el cuerpo de la respuesta, conservado en localStorage |
Una aplicación nativa, un script, cualquier cosa sin el almacén de cookies de un navegador |
"cookie" |
Una cookie HttpOnly establecida por el backend | Una aplicación de navegador. Los scripts que se ejecutan en tu página no pueden leerla, lo que la hace segura contra ataques XSS |
El modo cookie necesita auth.cookieAuth en el backend, y es el que utiliza la plantilla de frontend generada.
Esperar a que la sesión se restaure
Sección titulada «Esperar a que la sesión se restaure»Una sesión restaurada no está disponible en el primer renderizado. getSession() es síncrono, por lo que al cargar la página devuelve null mientras la restauración aún está en curso — y en el modo cookie una restauración siempre está en curso, porque el token de actualización está en una cookie que la página no puede leer, por lo que el cliente tiene que pedirle al servidor un nuevo token de acceso.
Leerlo sincrónicamente es lo que produce un parpadeo de sesión cerrada en cada recarga:
// Wrong: renders the signed-out view for one round trip, every reload.const session = client.auth.getSession();if (!session) return <SignIn />;isInitialized() se resuelve una vez que el cliente ha terminado de intentarlo, haya encontrado una sesión o no:
async function currentUser() { await client.auth.isInitialized(); return client.auth.getSession()?.user ?? null;}En React, esto es un efecto:
import { useEffect, useState } from "react";
function useCurrentUser() { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true);
useEffect(() => { let cancelled = false; client.auth.isInitialized().then(() => { if (cancelled) return; setUser(client.auth.getSession()?.user ?? null); setLoading(false); }); return () => { cancelled = true; }; }, []);
return { user, loading };}useRebaseAuthController en @rebasepro/app ya hace esto, por lo que una aplicación construida sobre la plantilla generada lo obtiene de forma automática.
Una restauración exitosa también llega a onAuthStateChange como TOKEN_REFRESHED — es una actualización —, pero un escuchador por sí solo no puede indicarte que la restauración ha finalizado: un arranque sin sesión no emite absolutamente nada, lo cual es indistinguible de uno que aún está en curso. Espera con await isInitialized() para esa comprobación y utiliza el escuchador para los cambios posteriores.
Escuchador del estado de autenticación
Sección titulada «Escuchador del estado de autenticación»Reacciona a los cambios de autenticación en toda tu aplicación:
const unsubscribe = client.auth.onAuthStateChange((event, session) => { // event: "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "USER_UPDATED" console.log("Auth event:", event); console.log("Session:", session?.user?.email);});
// Stop listeningunsubscribe();| Evento | Cuándo |
|---|---|
SIGNED_IN |
Se completó un inicio de sesión o registro |
TOKEN_REFRESHED |
El token de acceso fue renovado — incluida la renovación silenciosa que restaura una sesión al cargar la página |
USER_UPDATED |
updateUser() modificó el perfil, o confirmEmailChange() trasladó esta cuenta a su nueva dirección |
SIGNED_OUT |
Un cierre de sesión, o una actualización que falló definitivamente |
Gestión de contraseñas
Sección titulada «Gestión de contraseñas»Olvidé mi contraseña
Sección titulada «Olvidé mi contraseña»const { success, message } = await client.auth.resetPasswordForEmail( "user@example.com");Restablecer contraseña (con token)
Sección titulada «Restablecer contraseña (con token)»const { success, message } = await client.auth.resetPassword( resetToken, "newSecurePassword");Cambiar contraseña (autenticado)
Sección titulada «Cambiar contraseña (autenticado)»const { success, message } = await client.auth.changePassword( "oldPassword", "newPassword");Toda otra sesión de la cuenta termina: quien tuviera otra tiene que iniciar
sesión de nuevo con la nueva contraseña. Este dispositivo permanece conectado.
El servidor responde con una sesión nueva, y el cliente la adopta (emitiendo
TOKEN_REFRESHED).
Verificación de correo electrónico
Sección titulada «Verificación de correo electrónico»El registro envía por correo a la nueva cuenta su enlace de verificación
cuando el correo electrónico está configurado. sendVerificationEmail() lo
envía de nuevo.
// Send verification email to the current userawait client.auth.sendVerificationEmail();
// Verify with the token from the email link. Signed in as that account, this// keeps everything on it.const { passwordRemoved } = await client.auth.verifyEmail(token);
// Signed out, an account that holds a password answers PROOF_REQUIRED:try { await client.auth.verifyEmail(token);} catch (e) { if (e instanceof Error && "code" in e && e.code === "PROOF_REQUIRED") { // Keep the password, and sign in: await client.auth.verifyEmail(token, { password }); // …or verify without it, which removes it: // await client.auth.verifyEmail(token, { removeUnproven: true }); }}El enlace demuestra el acceso a la bandeja de entrada, no quién registró la dirección, por lo que una contraseña que ni la sesión ni la llamada demuestran se elimina en lugar de conservarse — consulta Verificación de correo electrónico.
En un backend con requireEmailVerification, signUp() se resuelve con
{ confirmationRequired: true, user: null } y sin sesión: la cuenta inicia
sesión una vez que se sigue el enlace con su contraseña.
Cambiar la dirección de correo electrónico
Sección titulada «Cambiar la dirección de correo electrónico»Un usuario que ha iniciado sesión traslada su propia cuenta a otra dirección. Nada cambia hasta que la nueva dirección responde:
// Mails a link to the new address, and a notice to the current oneconst { pendingEmail, expiresAt } = await client.auth.changeEmail("jane@new.example");
// On the page the link opens (<frontend>/confirm-email-change?token=…):const { email, removedProviders } = await client.auth.confirmEmailChange(token);El enlace vive 24 horas; getUser() informa la dirección en espera como
pendingEmail. Confirmar no necesita sesión, y un cliente que ha iniciado
sesión como esa cuenta adopta la nueva dirección y emite USER_UPDATED. Los
rechazos se enumeran en Cambiar una dirección de correo
electrónico.
Gestión de sesiones (multidispositivo)
Sección titulada «Gestión de sesiones (multidispositivo)»// List all active sessions, one per sign-inconst sessions = await client.auth.getSessions();// [{ id, userAgent, ipAddress, createdAt, isCurrentSession }, …]
// Revoke a specific sessionawait client.auth.revokeSession(sessionId);
// Revoke ALL sessions (logs out everywhere)await client.auth.revokeAllSessions();Cada token de acceso nombra la sesión a la que pertenece, de modo que
exactamente una entrada tiene isCurrentSession: true: el dispositivo que
pregunta. Revocar una sesión termina el refresh token de ese dispositivo y su
token de acceso juntos, a partir de la siguiente solicitud, tanto por HTTP
como por un socket de tiempo real abierto. Cerrar sesión (signOut()) hace lo
mismo para el dispositivo que cierra sesión. Un token de acceso emitido antes
de esta versión no nombra ninguna sesión; sigue funcionando hasta que expira,
dentro de la hora.
Configuración de autenticación
Sección titulada «Configuración de autenticación»Consulta la configuración de autenticación del backend:
const config = await client.auth.getAuthConfig();// {// hasBuiltInAuthRoutes: boolean,// emailPasswordLogin: boolean,// registrationEnabled: boolean, // open right now, bootstrap window included// passwordReset: boolean, // needs an email service// adminPasswordReset: boolean,// sessionManagement: boolean,// profileUpdate: boolean,// emailVerification: boolean,// magicLink: boolean,// anonymousLogin: boolean,// enabledProviders: string[],// needsSetup: boolean// }Almacenamiento de sesión personalizado
Sección titulada «Almacenamiento de sesión personalizado»De forma predeterminada, las sesiones se almacenan en localStorage. Puedes personalizar esto con la opción auth:
import { createRebaseClient, createCookieStorage } from "@rebasepro/client";
// Use cookies instead of localStorageconst client = createRebaseClient({ baseUrl: import.meta.env.VITE_API_URL, auth: { storage: createCookieStorage({ path: "/", sameSite: "Lax", secure: true }), autoRefresh: true, // default: true persistSession: true // default: true }});Estructura del objeto de usuario
Sección titulada «Estructura del objeto de usuario»// Canonical type — import from @rebasepro/typesinterface User { uid: string; email: string | null; displayName: string | null; photoURL: string | null; providerId: string; isAnonymous: boolean; emailVerified?: boolean; pendingEmail?: string | null; // an address change waiting for its link roles?: string[]; // text[] from the users table metadata?: Record<string, unknown>;}Próximos pasos
Sección titulada «Próximos pasos»- Consulta de datos — Operaciones CRUD y constructor de consultas
- Suscripciones en tiempo real — Datos en vivo con WebSockets
- Backend de autenticación — Configuración de autenticación del lado del servidor