Provider Adapters
Overview
A provider adapter implements the AuthAdapter interface from auth-core. It handles the mechanics of how a user proves their identity — the host app never sees these details.
The AuthAdapter interface
type AuthAdapter = {
requestLogin(input: {
email: string;
redirectTo?: string;
request: Request;
}): Promise<{ redirectTo?: string }>;
completeLogin(input: {
token: string;
request: Request;
}): Promise<{ identity: AuthIdentity; session: unknown }>;
resolveSession(request: Request): Promise<AuthIdentity | null>;
logout(request: Request): Promise<void>;
};
The AuthIdentity type
Every provider returns the same identity shape:
type AuthIdentity = {
provider: 'magic-link' | 'firebase' | 'better-auth';
providerUserId: string;
email: string;
emailVerified: boolean;
};
This is stored in the auth_identities table, decoupled from the host app's product tables. A user can have multiple identities (one per provider) all mapping to the same host user ID.
Built-in providers
Magic-link (v1)
The default provider. No external dependencies.
How it works:
requestLogin→ normalizes email, generates token, hashes it, stores via StorageAdapter, sends emailcompleteLogin→ looks up hashed token, consumes atomically, creates session, returns identityresolveSession→ reads thex-jampress-session-idheader set by the framework adapter, loads session from storage, returns identitylogout→ reads the same header and deletes the session from storage
Config:
createMagicLinkProvider({
storage,
email,
tokenTtlSeconds: 900, // 15 minutes (default)
sessionTtlSeconds: 604800, // 7 days (default)
rateLimits: {
requestLogin: {
email: { threshold: 5, windowSeconds: 900 },
ip: { threshold: 20, windowSeconds: 900 },
},
completeLogin: {
ip: { threshold: 10, windowSeconds: 900 },
},
},
buildMagicLinkUrl(token, redirectTo) {
const url = new URL(redirectTo || '/api/auth/verify', 'https://myapp.com');
url.searchParams.set('token', token);
return url.toString();
},
})
Firebase Auth (v2, future)
Recommended for WordPress/hybrid projects and projects needing managed infrastructure.
Planned approach:
- Firebase Admin SDK verifies tokens server-side
- On login: Firebase handles the UI/email, issues a Firebase ID token
completeLoginverifies the Firebase token, extracts identity, creates SDK sessionresolveSessionchecks SDK session cookie (not Firebase session cookie)- Firebase session cookies (2-week server-side) as an alternative mode
Why this adapter exists: Firebase handles OAuth providers, MFA, SAML, and compliance out of the box. When a project outgrows magic links, Firebase is the path of least resistance for projects that already have Firebase infrastructure (like DesBio with its Firebase JWT auth).
Better Auth (v2, future)
Recommended for Cloudflare-native projects that need more than magic links but want to stay self-hosted.
Planned approach:
- Better Auth runs as a Worker or alongside the Astro app
- Database-backed sessions using the same D1 instance
- Built-in rate limiting, session management, schema tooling
completeLogincreates both a Better Auth session and an SDK sessionresolveSessiondelegates to Better Auth's session resolution
Why this adapter exists: Better Auth is TypeScript-native, self-hosted, and database-backed. For Cloudflare Workers projects, it's a more natural evolution from DIY magic links than Firebase — same runtime, same database, just more features.
Writing a new adapter
To add support for another auth provider:
- Create
packages/auth-provider-{name}/ - Implement the
AuthAdapterinterface - Return
AuthIdentityfromcompleteLoginwithprovider: '{name}' - Store sessions via the SDK's
StorageAdapter(don't bypass it) - Add a README with configuration instructions
- Add integration tests using the same test harness as magic-link
The key contract: your adapter MUST return a standard AuthIdentity. Everything after that — the resolveOrCreateViewer callback, the middleware, the cookie — is handled by the SDK.
Provider migration
See docs/ROADMAP.md v2 section for the dual-session migration approach.
The short version: both old and new provider sessions are valid simultaneously during a migration window. New logins use the new provider. Old sessions expire naturally. The auth_identities table maps both provider user IDs to the same host user.