Ir al contenido

Adaptadores de autenticación personalizados

Rebase incluye su propio sistema de autenticación — configúralo aquí. Esta página aborda el otro caso: un proveedor de identidad que ya utilizas o por el que ya pagas.

Adaptadores de autenticación personalizados

Sección titulada «Adaptadores de autenticación personalizados»

Rebase permite el reemplazo completo del sistema de autenticación integrado mediante una arquitectura de autenticación conectable (pluggable). Esto desacopla la verificación de autenticación de la base de datos y de las capas REST/WebSocket, lo que permite una integración fluida con proveedores externos como Clerk, Auth0, Firebase Auth o servicios de identidad JWT personalizados.

Puedes implementar la interfaz AuthAdapter directamente para obtener un control total. La interfaz, de forma resumida (la definición completa, con los hooks opcionales de administración de usuarios y transformAuthResponse, es AuthAdapter en @rebasepro/types):

import { Hono } from "hono";
import type { HonoEnv } from "@rebasepro/server";
import { AuthenticatedUser, AuthAdapterCapabilities, UserManagementAdapter, UserCreationPrepareResult, UserCreationFinalizeResult } from "@rebasepro/types";
export interface AuthAdapter {
/** Unique identifier for this auth adapter (e.g., "clerk", "custom") */
readonly id: string;
/**
* Verifies an incoming HTTP request and returns the authenticated user payload.
* Called by Hono authentication middleware on every REST endpoint.
*/
verifyRequest(request: Request): Promise<AuthenticatedUser | null>;
/**
* Verifies a raw token string (e.g. for WebSocket connection handshake phase 1).
* If omitted, a synthetic request is automatically constructed.
*/
verifyToken?(token: string): Promise<AuthenticatedUser | null>;
/**
* The identity an account would carry right now, with no credential.
* Without it, a request that impersonates another user is refused.
*/
resolveUser?(uid: string): Promise<AuthenticatedUser | null>;
/** Optional user management operations (CRUD) for the panel */
userManagement?: UserManagementAdapter;
/** Optional: Mount adapter-specific custom public routes (e.g. callback paths) */
createAuthRoutes?(): Hono<any, any, any> | undefined;
/** Optional: Mount adapter-specific admin-only routes */
createAdminRoutes?(): Hono<any, any, any> | undefined;
/** Advertise supported capabilities (to customize what the panel shows) */
getCapabilities(): AuthAdapterCapabilities | Promise<AuthAdapterCapabilities>;
/** Lifecycle hooks called during backend start and graceful shutdown */
initialize?(): Promise<void>;
destroy?(): Promise<void>;
/** Custom user lifecycle hooks (e.g., hash passwords before collection writes) */
prepareUserCreation?(
values: Record<string, unknown>,
collectionAuth?: unknown
): Promise<UserCreationPrepareResult>;
finalizeUserCreation?(
entity: { id: string; values: Record<string, unknown> },
clearPassword?: string
): Promise<UserCreationFinalizeResult>;
/** Static service key to bypass checks for server-to-server calls */
serviceKey?: string;
}

Independientemente del proveedor de autenticación externo elegido, tu adaptador debe resolver las verificaciones de token exitosas en un objeto AuthenticatedUser uniforme. El RLS Scope Injector de Rebase asigna estos valores directamente a variables de sesión de PostgreSQL dentro de las transacciones:

export interface AuthenticatedUser {
uid: string; // Maps to pg local 'app.uid' -> rebase.uid()
email: string; // User email address
displayName?: string | null; // Optional display name
photoUrl?: string | null; // Optional avatar URL
roles: string[]; // Maps to pg local 'app.user_roles' -> rebase.roles()
isAdmin: boolean; // Informational: authorization reads `roles` (give the `admin` role)
isAnonymous?: boolean; // A guest session. Maps to 'app.is_anonymous' -> rebase.is_anonymous()
rawToken?: string; // The original token string (for downstream forwarding)
claims?: Record<string, any>; // Custom claims/metadata (available in rebase.jwt())
scopes?: string[]; // Narrower than the user's roles (set for an API key)
}

Integración rápida mediante createCustomAuthAdapter

Sección titulada «Integración rápida mediante createCustomAuthAdapter»

Para escenarios estándar (como validar JWTs de un servicio de terceros), puedes usar la utilidad createCustomAuthAdapter. Esta utilidad gestiona los valores predeterminados de las capacidades e implementa la validación de tokens de WebSocket de forma predeterminada envolviendo tu implementación de verifyRequest.

Para conectar un backend de Rebase con Clerk, puedes verificar los tokens JWT de Clerk utilizando el JSON Web Key Set (JWKS) de Clerk:

import { initializeRebaseBackend } from "@rebasepro/server";
import { createCustomAuthAdapter } from "@rebasepro/server";
import { createRemoteJWKSet, jwtVerify } from "jose";
// Clerk JWKS URL
const CLERK_JWKS_URL = "https://clerk.your-domain.com/.well-known/jwks.json";
const JWKS = createRemoteJWKSet(new URL(CLERK_JWKS_URL));
const clerkAuthAdapter = createCustomAuthAdapter({
serviceKey: process.env.REBASE_SERVICE_KEY,
verifyRequest: async (request) => {
const authHeader = request.headers.get("Authorization");
const token = authHeader?.replace("Bearer ", "");
if (!token) return null;
try {
// Verify Clerk JWT token against JWKS
const { payload } = await jwtVerify(token, JWKS);
const metadata = payload.metadata as Record<string, unknown> | undefined;
const roles = Array.isArray(metadata?.roles) ? metadata.roles as string[] : [];
return {
uid: payload.sub!,
email: (payload as Record<string, unknown>).email as string || "",
displayName: (payload as Record<string, unknown>).name as string || null,
roles: roles,
isAdmin: roles.includes("admin"),
claims: payload as Record<string, unknown>
};
} catch (error) {
console.error("Clerk token verification failed:", error);
return null; // Fail-closed
}
},
capabilities: {
hasBuiltInAuthRoutes: false, // Login is managed by Clerk UI
emailPasswordLogin: false,
registrationEnabled: false,
passwordReset: false,
profileUpdate: false,
sessionManagement: false
}
});
const backend = await initializeRebaseBackend({
auth: clerkAuthAdapter,
// ...
});

Para verificar los tokens de Firebase Auth utilizando los certificados públicos de Firebase:

import { initializeRebaseBackend } from "@rebasepro/server";
import { createCustomAuthAdapter } from "@rebasepro/server";
import { createRemoteJWKSet, jwtVerify } from "jose";
const FIREBASE_JWKS_URL = "https://www.googleapis.com/robot/v1/metadata/jwk/securetoken@system.gserviceaccount.com";
const JWKS = createRemoteJWKSet(new URL(FIREBASE_JWKS_URL));
const FIREBASE_PROJECT_ID = "my-firebase-project-id";
const firebaseAuthAdapter = createCustomAuthAdapter({
serviceKey: process.env.REBASE_SERVICE_KEY,
verifyRequest: async (request) => {
const authHeader = request.headers.get("Authorization");
const token = authHeader?.replace("Bearer ", "");
if (!token) return null;
try {
const { payload } = await jwtVerify(token, JWKS, {
issuer: `https://securetoken.google.com/${FIREBASE_PROJECT_ID}`,
audience: FIREBASE_PROJECT_ID
});
const roles = Array.isArray((payload as Record<string, unknown>).roles) ? (payload as Record<string, unknown>).roles as string[] : [];
return {
uid: payload.sub!,
email: (payload as Record<string, unknown>).email as string || "",
displayName: (payload as Record<string, unknown>).name as string || null,
photoUrl: (payload as Record<string, unknown>).picture as string || null,
roles: roles,
isAdmin: roles.includes("admin"),
claims: payload as Record<string, unknown>
};
} catch (error) {
console.error("Firebase token verification failed:", error);
return null;
}
}
});
const backend = await initializeRebaseBackend({
auth: firebaseAuthAdapter,
// ...
});

Antes de la versión 0.25, el getter autenticaba únicamente las solicitudes HTTP, y el socket en tiempo real permanecía anónimo.

El adaptador verifica el token del proveedor; el cliente se encarga de enviarlo. Proporciona al cliente de Rebase un getter para este token, una sola vez:

client.setAuthTokenGetter(() => clerk.session?.getToken() ?? Promise.resolve(null));

Con Firebase Auth el getter es () => auth.currentUser?.getIdToken() ?? Promise.resolve(null). El cliente lo consulta para cada solicitud HTTP y cada vez que el socket en tiempo real se autentica, de modo que las consultas en vivo (live queries) se ejecutan con el mismo usuario que las llamadas REST. En el panel de administración, establécelo en el cliente que pasas a <Rebase client>. Consulta Tu propio proveedor de autenticación.


Montaje de rutas de autenticación y acciones del panel

Sección titulada «Montaje de rutas de autenticación y acciones del panel»

Si tu proveedor de autenticación personalizado requiere montar endpoints de redirección (como rutas de callback de OAuth o flujos de inicio de sesión de SAML), implementa el método createAuthRoutes en tu adaptador:

const myOauthAdapter: AuthAdapter = {
id: "custom-oauth",
verifyRequest: async (req) => ({
// validate the token, then return the caller
uid: "…",
email: "user@example.com",
roles: [],
isAdmin: false
}),
getCapabilities: () => ({
hasBuiltInAuthRoutes: true,
emailPasswordLogin: false,
registrationEnabled: false,
passwordReset: false,
adminPasswordReset: false,
sessionManagement: false,
profileUpdate: false,
emailVerification: false,
magicLink: false,
anonymousLogin: false,
enabledProviders: []
}),
createAuthRoutes: () => {
const app = new Hono<HonoEnv>();
// Mounted automatically under /api/auth/callback
app.get("/callback", async (c) => {
const code = c.req.query("code");
// Exchange code for provider tokens and set cookies/redirect
return c.redirect("/dashboard");
});
return app;
}
};

Si deseas permitir operaciones CRUD de usuarios directamente dentro del panel, implementa el helper userManagement dentro de las opciones del adaptador, el cual proporciona hooks para listUsers, getUserById, createUser, updateUser, deleteUser, getUserRoles y setUserRoles.