@lovable.dev/lovable-auth-js
v1.11.0
Published
Lovable Auth JS
Readme
@lovable.dev/lovable-auth-js
OIDC authentication for TanStack Start apps. Works with the Lovable platform identity, Lovable's OIDC server, or any OpenID Connect provider (Okta, Entra, Auth0, …).
- Installation
- Setups
- Quick start (
lovable-auth) - Custom OIDC provider (
custom-oidc) - Lovable SSO (
lovable-sso) - Mounting at a non-default path
- Client API
- Server API
- Session hook
- Caching
Installation
npm install @lovable.dev/lovable-auth-jsReleases follow semver: depend on ^1.0.0 and every 1.x release is a drop-in
update; breaking changes only ship in a new major.
Setups
createAuthServer takes a setup that selects where your app's users come from:
| Setup | What it means |
| --- | --- |
| "lovable-sso" | Use lovable.dev's SSO capabilities — your app's users are lovable.dev users |
| "lovable-auth" | Lovable provisions an OIDC server for your app — it has its own user base |
| "custom-oidc" | Integrate with a custom OIDC IdP (Okta, Entra, Auth0, …) |
Quick start (lovable-auth)
Define the auth server once and reuse the same instance for routes and server-side identity reads (see Server API for the options and their env-var defaults):
// app/lib/auth.server.ts
import { createAuthServer } from "@lovable.dev/lovable-auth-js/server";
export const auth = createAuthServer({ setup: "lovable-auth" });TanStack Start route:
// app/routes/api/auth.$.ts
import { createFileRoute } from "@tanstack/react-router";
import { auth } from "~/lib/auth.server";
export const Route = createFileRoute("/api/auth/$")(auth.routes());Server-side identity:
import { auth } from "~/lib/auth.server";
const session = await auth.getSession({ request }); // Session | null
// Or request a short-lived access token for a downstream service:
const result = await auth.requestAccessToken({
request,
audience: "urn:lovable:service:example_gateway",
resource: "https://gateway-example.lovable.dev",
scope: "example_gateway:invoke",
}); // { accessToken, expiresAt } | nullClient:
import { newAuthClient } from "@lovable.dev/lovable-auth-js";
export const authClient = newAuthClient();
await authClient.signIn({ callbackURL: "/account" });lovable-auth setup binds lovableAppProvider() (override with provider),
which reads the following env vars by default:
| Env var | Maps to |
| --- | --- |
| LOVABLE_AUTH_TENANT_ID | OIDCProvider.issuer = https://<tenantId>.auth.lovable.app; the main Lovable tenant is rejected — use lovableDevProvider() |
| LOVABLE_AUTH_CLIENT_ID | OIDCProvider.clientId |
| LOVABLE_AUTH_CLIENT_SECRET | OIDCProvider.clientSecret |
Override any of them by passing explicit arguments:
lovableAppProvider({
tenantId: "abc123",
clientId: "...",
clientSecret: "...",
scopes: ["openid", "email"], // optional; defaults to ["openid", "offline", "email", "profile"]
});Custom OIDC provider (custom-oidc)
Construct an OIDCProvider and pass it to createAuthServer. Discovery
resolves to <issuer>/.well-known/openid-configuration.
import { createAuthServer, type OIDCProvider } from "@lovable.dev/lovable-auth-js/server";
const oktaProvider: OIDCProvider = {
id: "okta",
issuer: "https://example.okta.com/oauth2/default",
clientId: process.env.OKTA_CLIENT_ID!,
clientSecret: process.env.OKTA_CLIENT_SECRET!,
scopes: ["openid", "profile", "email", "offline_access"],
};
export const auth = createAuthServer({
setup: "custom-oidc",
provider: oktaProvider,
secret: process.env.AUTH_SECRET!,
trustedOrigins: ["https://my-app.example.com"],
});The optional revokeEndpoint field revokes the provider session on sign-out;
leave it unset for providers without one — sign-out then completes locally.
To support explicit signup requests, define OIDCProvider.onSignUpAuthorizeHook,
for example onSignUpAuthorizeHook: () => ({ screen_hint: "signup" }).
The SDK calls it on each signup request and adds its returned authorization
query parameters; required OAuth parameters take precedence.
To accept what the app collected before sign-in (loginHint, loginMethod,
flow, see SignInHints), define OIDCProvider.onSignInAuthorizeHook. It
receives the hints the app passed and returns the authorization parameters
your provider understands, for example
onSignInAuthorizeHook: ({ loginHint, loginMethod }) => ({ login_hint: loginHint ?? "", connection: loginMethod ?? "" }).
Without the hook the SDK forwards loginHint as the standard login_hint
and rejects loginMethod and flow with a 400, so an app never silently
loses the choice its user made. Signup-hook and required OAuth parameters
take precedence over the sign-in hook's.
Lovable SSO (lovable-sso)
Your app's users are lovable.dev users. Visitors who are signed in to lovable.dev are recognized automatically — reading the session needs no sign-in flow in the app:
export const auth = createAuthServer({ setup: "lovable-sso" });
const session = await auth.getSession({ request });export const authClient = newAuthClient({ setup: "lovable-sso" });signIn() runs an OAuth sign-in with the visitor's Lovable account, for
visitors the platform doesn't recognize automatically; signOut() signs out
of the app only and stops automatic recognition on that browser until the
next sign-in.
Mounting at a non-default path
If you mount the route at something other than /api/auth/$, pass the same
basePath on both sides so server-generated callback URLs and client requests
agree:
export const auth = createAuthServer({ setup: "lovable-auth", basePath: "/custom-auth" });
// ...
newAuthClient({ basePath: "/custom-auth" });Where to call what in TanStack Start
The mounted route is the only wiring; there is no middleware to add. Pick the half that matches where the code runs:
| Where the code runs | Session | Access token |
| --- | --- | --- |
| Browser (components, handlers) | authClient.useSession() / authClient.getSession() | authClient.requestAccessToken({ audience, scope }) |
| Server (loaders, beforeLoad, server functions, SSR) | auth.getSession({ request }) | auth.requestAccessToken({ request, audience, scope }) |
| API exposed to OAuth clients (bearer access token) | auth.getSession({ accessToken }) | auth.requestAccessToken({ accessToken, audience, scope }) |
authClient methods are cookie-carrying fetch calls against the mounted
routes, so they work only in the browser — a loader running during SSR has no
cookie jar. Route guards therefore read the session through a server function:
import { getRequest, setResponseHeader } from "@tanstack/react-start/server";
const getClientSessionFn = createServerFn().handler(async () => {
setResponseHeader("Cache-Control", "private, no-store");
return auth.getClientSession({ request: getRequest() });
});
export const Route = createFileRoute("/dashboard")({
beforeLoad: async () => {
const session = await getClientSessionFn();
if (!session) throw redirect({ to: "/" });
return { session };
},
});Use getClientSession for anything a route returns: TanStack Start serializes
beforeLoad and loader results into the page, and getSession includes the
session token. The Cache-Control header keeps shared caches from storing one
user's session response and serving it to another.
Server-rendered session
On its own, authClient.useSession() renders as pending during SSR and fetches
the session after hydration. Load the session in the root route's beforeLoad
and pass it to AuthSessionProvider: the server render already shows the
signed-in state, and child guards read context.session instead of fetching the
session again. Mount the provider at the root, above every component that calls
useSession().
This is TanStack Start's recommended pattern. The root beforeLoad runs on
every navigation and preload, so each one makes a small server call; in return,
guards always see the current session.
import { AuthSessionProvider } from "@lovable.dev/lovable-auth-js";
// getClientSessionFn as in the guard example above, with its Cache-Control header.
export const Route = createRootRoute({
beforeLoad: async () => ({ session: await getClientSessionFn() }),
headers: () => ({ "Cache-Control": "private, no-store" }),
component: () => (
<AuthSessionProvider client={authClient} session={Route.useRouteContext().session}>
<Outlet />
</AuthSessionProvider>
),
});getClientSession drops the session token, so the session is safe to render
into the page. A page that renders the session is personal: send it with
Cache-Control: private, no-store so no shared cache serves it to another
user.
After hydration the browser still fetches the session once in the background,
so the server can renew the session cookie and a page rendered without the
visitor's cookies (prerendered or cached) corrects itself. A signed-in session
shows no pending state during that fetch, and its session.token is absent
until it completes. A null session renders as pending, on the server too, until
the fetch confirms it, so a client-side redirect never acts on an unconfirmed
null. Sign-in and sign-out update useSession() on their own;
router.invalidate() only refreshes the route context.
authClient.requestAccessToken POSTs to the mounted /access-token route: the
server exchanges the current session (RFC 8693) and returns the minted
downstream token plus its expiry — the session's own token never reaches the
browser, and the minted token is treated as opaque (expiry comes from the
exchange response, never from decoding the token).
For repeated calls to one API, hold an accessTokenProvider — it caches the
token per instance, refreshes ~30s before expiry, and dedupes concurrent mints:
const tokenProvider = authClient.accessTokenProvider({
audience: "urn:lovable:service:example-gateway",
scope: "example-gateway:invoke",
});
const response = await fetch(apiUrl, {
headers: { Authorization: `Bearer ${await tokenProvider.get()}` },
});Client libraries that take a token callback can be handed () => tokenProvider.get().
Client API
newAuthClient(options?: AuthClientOptions): AuthClient
AuthClient members:
| Member | Returns | Description |
| --- | --- | --- |
| signIn(options?) | Promise<AuthResult> | Start the OAuth sign-in flow. Picks redirect outside iframes and popup in live preview; override with mode. The popup only performs the login: it always finishes on the SDK's completion page, which hands the session to the opener as a sealed, 60-second handoff (the opener claims it in its own cookie partition, which matters inside a live preview) and closes. Any navigation, such as a hook redirect, happens in the opener. |
| signUp(options?) | Promise<AuthResult> | Request the provider's signup flow. Accepts the same options as signIn and requires provider signup support. |
| getAccountURL() | string | Local SDK route that redirects to the provider's hosted account page using OIDC discovery. Use it as a link or navigation target. |
| openAccount(options?) | OpenAccountResult | Open the hosted account page in its own window, synchronously. mode: "auto" (the default) picks a popup inside an iframe such as the editor preview, and a new tab otherwise; pass "popup" or "tab" to force one. A blocked popup falls back to a tab. Returns { error, mode }, where mode is the window opened. |
| signOut(options?) | Promise<SignOutResult> | With setup: "lovable-auth", clear the app session and run RP-initiated logout: a popup in embedded previews, a redirect in standalone apps. Other setups keep local sign-out and their optional revokeEndpoint hook. callbackURL selects the app page to return to. |
| getSession() | Promise<SessionState> | Fetch the current session from the auth server. |
| requestAccessToken(options?) | Promise<AccessTokenResult \| null> | Request a short-lived access token for resource/audience/scope — the auth server exchanges the current session (RFC 8693) and returns { accessToken, expiresAt }, the expiry taken from the exchange response's expires_in (the token stays opaque). null when anonymous or denied. |
| accessTokenProvider(options) | AccessTokenProvider | Cached, single-flight token getter for one target API. Synchronous to construct; get() mints via the same exchange and refreshes ~30s before the expiry the exchange reported. Equal options return the same instance (one cache per target); caches are dropped on sign-in/sign-out. |
| useSession | UseSession | React hook returning the current session reactively. Starts from the server session when AuthSessionProvider is mounted (see Server-rendered session). |
For managed Lovable Auth logout, including embedded previews:
await authClient.signOut({ callbackURL: "/signed-out" });Register <app-origin><basePath>/sign-out/callback in the OAuth client's
post_logout_redirect_uris (the default basePath is /api/auth). The SDK
validates the logout return, then redirects to callbackURL, which defaults
to the current page. The provider must advertise end_session_endpoint.
The browser flow replaces the server-side revocation hook for this call.
Call popup logout directly from a click handler so the browser can open it.
The popup returns through the same registered callback, notifies the opener,
and closes. The opener then navigates to the same-origin callbackURL.
Closing or blocking the popup returns an error. Once the local session is
cleared, the app remains signed out even if provider logout is canceled.
The callback does not clear a newer session created while logout was pending.
A logout return the SDK cannot match to a pending logout (expired, replayed, or
missing its state cookie) sends the user to the app root; they are already signed out.
The lovable-sso setup keeps app-only sign-out so it does not sign the user out of the Lovable editor.
signIn example:
const result = await authClient.signIn({
callbackURL: "/account",
scopes: ["openid", "email"],
});
if (result.error) {
// handle Error
}To request the provider's signup flow:
await authClient.signUp({ callbackURL: "/account" });signIn(options) starts the normal sign-in flow; signUp(options) requests
registration. Neither changes the provider's registration policy.
lovableAppProvider() returns prompt=create and flow=signup from its
signup hook. Providers without onSignUpAuthorizeHook, including the default
lovable-sso provider, reject explicit signup requests. newUserCallbackURL
only controls where a newly registered user returns after authentication.
Sign-in hints
The hosted page owns every credential step, but the app may own the first
screen. signIn and signUp accept what the app already collected and the
provider continues from there:
| Option | Meaning | Lovable parameter |
| --- | --- | --- |
| loginHint | Identifier the user typed in the app, usually an email. | login_hint (standard OIDC) |
| loginMethod | Method the user picked in the app: "google", "apple", "microsoft", "lovable", "email". | login_method |
| flow | Flow to run: "signin" or "signup". signUp() sets it for you. | flow |
// Email collected in the app, account creation on the hosted page.
await authClient.signIn({ callbackURL: "/onboarding", loginHint: email, flow: "signup" });
// "Continue with Google" button rendered in the app.
await authClient.signIn({ callbackURL: "/account", loginMethod: "google" });
// Email sign-in with the email collected in the app; the hosted page handles the credential.
await authClient.signIn({ callbackURL: "/account", loginMethod: "email", loginHint: email });loginHint works with every provider. loginMethod and flow need
OIDCProvider.onSignInAuthorizeHook, which lovableAppProvider() defines;
other providers reject them with a 400 unless you add the hook. The hints
are strings so any IdP's names fit; for Lovable the accepted values are the
LovableLoginMethod and LovableSignInFlow unions (also exported as the
LOVABLE_LOGIN_METHODS and LOVABLE_SIGN_IN_FLOWS arrays from
@lovable.dev/lovable-auth-js/server), and lovableAppProvider() rejects any
other value with a 400 at the signIn call, before the redirect. A hook may
throw for the same effect. The hints
only steer the hosted page: it still verifies the credential, and a method
the project has not enabled ends the flow with login_method_unavailable at
errorCallbackURL. Passwords, one-time codes and password resets never run
in the app.
To open the hosted account page, call openAccount() from a click handler:
<button
type="button"
onClick={() => {
const { error } = authClient.openAccount();
setAccountError(error?.message ?? null);
}}
>
Manage account
</button>Inside an embedded preview it opens a popup, because the hosted page refuses
to be framed. Outside one it opens a new tab, so the app stays open. Each
click opens its own window, named lovable-account-<id>, so a repeat click
never reloads an account window that is already open. error is set only when the browser blocks
both the popup and the tab. The hosted page shows a Done button
that closes the window when it was opened this way. Call it before awaiting
anything, or the browser blocks the window.
For a plain link, use getAccountURL():
<a href={authClient.getAccountURL()} target="_blank" rel="noopener noreferrer">Manage account</a>For same-tab navigation, use window.location.assign(authClient.getAccountURL()).
The SDK's /account route passes the provider's full OIDC discovery document
to OIDCProvider.onAccountURLHook, after validating its issuer. The hook
returns an account URL or null when unavailable. A missing hook or null
returns HTTP 404. The URL must be absolute HTTPS (HTTP loopback URLs are
allowed for local development). The SDK sends no app tokens or return URL;
the hosted page handles its own sign-in.
lovableAppProvider() reads the non-standard account_endpoint field in
its hook. Custom providers can interpret their own discovery fields:
onAccountURLHook: (wellKnown) =>
typeof wellKnown.profile_url === "string" ? wellKnown.profile_url : nullLovable Auth advertises its login UI base URL plus /v1/account. The public
base follows the tenant's vanity URL when configured; local development uses
DEV_OAUTH_LOGIN_UI_BASE_URL.
Apps using getAccountURL() pick up changes through discovery, which the SDK
caches for 10 minutes. The default Lovable SSO provider has no account hook.
getSession returns a discriminated union — when error is non-null,
data is null, and vice versa:
const result = await authClient.getSession();
if (result.error) {
console.error(result.error);
} else if (result.data) {
console.log("signed in as", result.data.user.email);
}AuthClientOptions:
| Field | Default | Description |
| --- | --- | --- |
| setup | — | Must match the server's setup. |
| logger | built-in console logger | Replaces the console logger (console, pino, … work). Without logLevel, its own info/debug presence decides verbosity. |
| logLevel | "warn" | Lowest level to emit: "error", "warn", "info", "debug", or "none" for silence. |
| baseURL | current window origin | Origin of the auth server. |
| basePath | /api/auth | Path prefix for the auth endpoints. |
| signInMode | "auto" | Default sign-in mode when signIn is called without mode. |
| popupFeatures | centred 520×720 | window.open features string. |
| popupTimeoutMs | 120_000 | How long to wait for the popup to complete. |
When sign-in fails
A failed sign-in lands on the sign-in's errorCallbackURL with an error code
and, when the provider or the session hook gave a reason,
error_description. In popup mode signIn resolves with the error instead.
A failure with no errorCallbackURL to go to lands on the server's errorURL,
or the app root when that is unset. This covers sign-ins that passed none and
expired or replayed attempts (state_mismatch, state_expired,
state_not_found); in a popup, the popup closes and signIn resolves with an
error. Show a message and a way to sign in again on those pages. The error
value is a code, not a sentence.
Server API
createAuthServer(options: AuthServerOptions): AuthServer
Returns a configured auth server for the given setup. Hold the result in module scope and reuse it across requests:
| Member | Returns | Description |
| --- | --- | --- |
| routes() | RegisteredAuthRoutes | TanStack Start route configuration for createFileRoute("/api/auth/$"). |
| getSession(options) | Promise<Session \| null> | Resolve the session ({ user, session }) from the app's session cookie (request or headers), or from an explicit bearer accessToken (see Access tokens); one or the other. null when anonymous or the access token is invalid. |
| getClientSession(options) | Promise<ClientSession \| null> | getSession without the session token, safe to render into the page; pass it to AuthSessionProvider. Same options. |
| getUser(options) | Promise<User \| null> | Fetch the user's profile from the provider's userinfo endpoint with the session's access token. Explicit and remote, unlike getSession; same options. Returns the stored session user when the token lacks the openid scope or the session has no access token. null when anonymous or the provider rejects the token. |
| requestAccessToken(options) | Promise<AccessTokenResult \| null> | Request a short-lived access token for resource/audience — an RFC 8693 exchange of the session's access token (or, when the lovable-sso session comes from the platform identity, the platform credential itself: the lovable-auth cookie value or the injected identity token), of a verified bearer accessToken, or of a caller-supplied subjectToken (+ optional subjectTokenType). Returns { accessToken, expiresAt }; null when there is nothing to exchange. |
| accessTokenProvider(options) | ServerAccessTokenProvider | Stateless token getter bound to one target API — get({ request }) exchanges that session on every call (same options as getSession). Synchronous to construct, safe at module scope. |
AuthServerOptions is a union discriminated by setup. All setups accept:
| Field | Default | Description |
| --- | --- | --- |
| provider | lovableDevProvider() in lovable-sso; lovableAppProvider() in lovable-auth; required in custom-oidc | OIDC provider the server is bound to. |
| secret | LOVABLE_AUTH_SECRET env var | Signs and encrypts session cookies. createAuthServer throws when neither is set. |
| basePath | /api/auth | Path prefix the routes are mounted at; must match the client's basePath. |
| trustedOrigins | LOVABLE_AUTH_TRUSTED_ORIGINS env var (comma-separated) | Additional origins accepted as request hosts, Origin headers, and redirect targets. |
| fallbackBaseURL | LOVABLE_AUTH_BASE_URL env var | Base URL used when the request host is not a trusted origin. The OAuth routes require this, a non-wildcard trustedOrigins entry, or trustRequestOrigin. |
| trustRequestOrigin | true in lovable-sso, else false | Use the request's own origin. If cross-origin requests are needed, use trustedOrigins. |
| errorURL | the app root | App page, usually the sign-in page, for sign-in failures that have no errorCallbackURL: the user lands there with ?error=<code> (plus error_description when there is one). A path or a trusted origin. See When sign-in fails. |
| logger | built-in console logger | Replaces the console logger (console, pino, … work). Without logLevel, its own info/debug presence decides verbosity. |
| logLevel | "warn" | Lowest level to emit: "error", "warn", "info", "debug", or "none" for silence. |
| cache | Built-in cache | CacheStore backend (e.g. Redis when self-hosting); null disables caching. See Caching. |
| hooks.session.create.before | — | Runs once a user is authenticated, before their session is handed out. Can redirect or deny. See Session hook. |
const gatewayTokens = auth.accessTokenProvider({ audience: "urn:lovable:service:example_gateway" });
// per request:
const token = await gatewayTokens.get({ request });Unlike the client provider, the server provider never caches a minted token on
the instance: a module-scope instance is shared across users, so an instance
cache would serve one user's token to another. The built-in exchange cache
already dedups per subject. Callers passing subjectToken/subjectTokenType
keep using requestAccessToken directly.
lovableAppProvider(options?: LovableAppProviderOptions): OIDCProvider
Convenience helper that produces an OIDCProvider pre-configured for a
Lovable app's auth tenant; the lovable-auth setup default. See
Quick start for env-var defaults.
lovableDevProvider(options: LovableDevProviderOptions): OIDCProvider
Same, but for the main Lovable tenant (Lovable users themselves): the issuer
defaults to https://lovable.dev/oauth and no tenant id is involved.
clientId / clientSecret fall back to LOVABLE_AUTH_CLIENT_ID /
LOVABLE_AUTH_CLIENT_SECRET. This provider declares no revoke endpoint —
sign-out completes locally. The lovable-sso setup binds it automatically for
token exchanges.
Access tokens
An API your app exposes to OAuth clients receives an Authorization: Bearer
access token the client obtained from your auth tenant (or from Lovable SSO in the
lovable-sso setup) instead of a session cookie. Take the token out of the header
and pass it as accessToken; it is then the only session proof. request/headers
always mean the app's own session cookie and never read the Authorization header.
export async function handleApi(request: Request) {
const accessToken = request.headers.get("authorization")?.match(/^Bearer\s+(.+)$/i)?.[1];
const session = accessToken ? await auth.getSession({ accessToken }) : null;
if (!session) {
return new Response(null, { status: 401, headers: { "www-authenticate": 'Bearer error="invalid_token"' } });
}
const downstream = await auth.requestAccessToken({ accessToken, audience: "urn:lovable:service:example_gateway" });
// call the downstream API as session.user ...
}The token is verified against the provider's published keys before anything else:
a wrong signature, wrong issuer, or an expired token yields null, and
requestAccessToken never forwards such a token to the exchange. Only RFC 9068
access tokens (typ: at+jwt) are accepted, so an ID token cannot be replayed as one.
accessTokenValidators adds your own checks on the claims, e.g.
requireAudience(apiUrl) to accept only tokens issued for your API.
A session started from an access token is built from the token alone; nothing is
fetched. getUser fetches the user's data from the IdP's userinfo endpoint:
const user = await auth.getUser({ accessToken });Session hook
hooks.session.create.before runs on the server once a user is authenticated,
before their session is handed out. Use it to provision app data (a profiles
row, with a getOrCreate), to send incomplete profiles to onboarding, to refuse
accounts that are not invited or on an approved domain, or to audit sign-ins.
import { APIError, createAuthServer } from "@lovable.dev/lovable-auth-js/server";
export const auth = createAuthServer({
setup: "lovable-auth",
hooks: {
session: {
create: {
before: async ({ user }, ctx) => {
if (!(await invitations.has(user.email))) throw new APIError(403, "not_invited");
const profile = await profiles.getOrCreate(user.id, { email: user.email, name: user.name });
if (!profile.complete) throw ctx.redirect(`/onboarding?returnTo=${encodeURIComponent(ctx.callbackURL)}`);
},
},
},
},
});ctx carries the request, callbackURL, and redirect(url).
| Outcome | Effect |
| --- | --- |
| returns nothing | Continue to callbackURL. |
| throw ctx.redirect(url) | Go to url instead; it must pass the same trust check as callbackURL. |
| return false / throw new APIError(status, message) | Refuse: the session is discarded and the user lands on errorCallbackURL with error=access_denied. |
| any other error | Logged; the sign-in continues. |
Under lovable-sso, automatically recognized visitors never sign in, so the
hook runs the first time their identity is seen on a session read, cached per
user. A refusal hides them; a redirect cannot apply there, so onboarding also
needs a route guard.
Caching
Exchanged access tokens, OIDC discovery documents, and signing keys are
cached. The cache option controls where:
- Omitted — the built-in cache: shared and persistent on Lovable hosting, in-memory elsewhere.
- A
CacheStore— your backend (e.g. Redis when self-hosting): null— caching disabled.
const auth = createAuthServer({
setup: "lovable-auth",
cache: {
get: (key) => redis.get(key),
set: async (key, value, options) => {
await redis.set(key, value, { EX: options?.ttl ?? 300 });
},
delete: async (key) => {
await redis.del(key);
},
},
});Cache failures are silent: a fresh fetch or exchange is always the fallback.
Types
Type declarations ship with the package — client types from the top-level
entry, server types from /server, shared types (Session, AuthSetup,
Logger, OIDCProvider) from both.
