4.5 KiB
create-provider
Purpose
Factory for the auth.provider half of The split. Returns an
AuthProviderAdapter (per @suaveplan/api-auth-provider) that mounts the
canonical PROVIDER_ROUTE_SURFACE (9 routes) under routePrefix (default
/auth). Each handler internally rewrites the incoming Request onto the
Better-Auth-native sub-path via ROUTE_TRANSLATION and delegates to
auth.handler(...). This indirection means downstream consumers of the
provider see the canonical foundation surface — never Better-Auth's idioms —
while Better-Auth still owns the precise wire response (cookies, 302
redirects, error envelopes).
Features
id: "better-auth";pathPrefixvalidated asAuthPathPrefix.- All 9 canonical route names registered:
POST /register→POST /sign-up/emailPOST /login→POST /sign-in/emailPOST /logout→POST /sign-outPOST /refresh→GET /get-sessionGET /oauth/:provider/start→POST /oauth2/sign-in/:providerGET /oauth/:provider/callback→GET /oauth2/callback/:providerGET /verify-email/:token→GET /verify-emailPOST /forgot-password→POST /forget-passwordPOST /reset-password→POST /reset-password
routePrefixcollision-checked against@suaveplan/api-host/reserved-prefix— refused withAUTH_BA.ROUTE_PREFIX_COLLISIONon collision.- Better-Auth instance materialised during
lifecycle.installso DB unreachable errors fail before traffic. - OAuth flow conclusions emit
oauth_flows_total{provider,outcome}. - Per-options
WeakMapso the same Better-Auth instance is shared withcreateBetterAuthSession(opts)when wired with the same options object.
Usage: Basic
import { createBetterAuthProvider } from "@suaveplan/api-auth-better-auth";
const provider = createBetterAuthProvider({
database: pool,
baseUrl: "https://api.example.com",
secret: process.env.BETTER_AUTH_SECRET!,
});
// Mount via the host's plugin lifecycle:
host.registerAuthProvider(provider);
Usage: Advanced
import { createBetterAuthProvider } from "@suaveplan/api-auth-better-auth";
import { createBetterAuthSession } from "@suaveplan/api-auth-better-auth";
// Operators MUST share the SAME options bag between the two factories to
// share the Better-Auth instance (and thus the DB pool):
const opts = {
database: pool,
baseUrl: "https://api.example.com",
secret: process.env.BETTER_AUTH_SECRET!,
routePrefix: "/api/auth",
oauth: {
providers: {
github: {
providerId: "github",
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
scopes: ["read:user", "user:email"],
authorizationUrl: "https://github.com/login/oauth/authorize",
tokenUrl: "https://github.com/login/oauth/access_token",
userInfoUrl: "https://api.github.com/user",
},
},
},
};
host.registerAuth({
resolver: createBetterAuthSession(opts),
csrf: true,
});
host.registerAuthProvider(createBetterAuthProvider(opts));
API Reference
| Export | Shape | Purpose |
|---|---|---|
createBetterAuthProvider(opts) |
(BetterAuthOptions) => AuthProviderAdapter |
Build the provider |
ROUTE_TRANSLATION |
Record<ProviderRouteName, NativeRoute> |
Re-exported for callers wanting to introspect the mapping |
buildNativeUrl(args) |
function | Re-exported for advanced wrappers |
Implementation Notes
The provider's routes(router, ctx) mounts every canonical entry from
PROVIDER_ROUTE_SURFACE with auth: null because the routes are mounted
INSIDE the auth provider's own prefix and the routes themselves are the
authentication flow — they MUST be reachable unauthenticated. The handler's
inner rewrite reads (ctx as { params }) because the abstract
RequestContext doesn't declare params, but the request pipeline driver
(@suaveplan/api-host/request-pipeline-driver.ts) attaches them at runtime.
The cast is documented inline. The OAuth outcome heuristic maps status codes
to labels — 2xx/3xx → success, 403 → provider_rejected, anything else
→ state_mismatch. The semantic split between "state_mismatch" and
"provider_rejected" is intentional even though a single 400 covers both
wire-wise: the conformance suite distinguishes the two via the underlying
mock IdP's primeError API. The per-options WeakMap is keyed by object
identity, not deep equality — sharing requires literal same-object reuse.