UI helpers + API routes for authentication.
npm install @rpcbase/auth@rpcbase/authUI helpers + API routes for authentication.
Example (like sample-app):
``ts
import { initApi } from "@rpcbase/api"
import { routes as authRoutes } from "@rpcbase/auth/routes"
const routes = {
...authRoutes,
...import.meta.glob("./**/handler.ts"),
}
export const runApi = async ({ app }) => {
await initApi({ app, routes })
}
`
The OAuth request (state + PKCE verifier) is stored server-side in RBOAuthRequest (global model). A short-lived HttpOnly cookie binds state to the initiating browser (SameSite=None; Secure on HTTPS). The callback handler uses an Express session only to sign the user in.
Provider configuration is provided by the host app (server-side) at runtime.
Call configureOAuthProviders() before initializing your API routes:
`ts
import { initApi } from "@rpcbase/api"
import { configureOAuthProviders } from "@rpcbase/auth/oauth"
import { routes as authRoutes } from "@rpcbase/auth/routes"
configureOAuthProviders({
mock: {
issuer: "http://localhost:9400",
clientId: "rpcbase-sample-app",
scope: "openid email profile",
callbackPath: "/api/auth/oauth/mock/callback",
},
})
const routes = {
...authRoutes,
...import.meta.glob("./**/handler.ts"),
}
export const runApi = async ({ app }) => {
await initApi({ app, routes })
}
`
The app is responsible for sourcing secrets (env, vault, etc.) and passing them to configureOAuthProviders().
Each provider must define a scope and a callbackPath so the generated redirect_uri matches your routing:
`ts`
configureOAuthProviders({
google: {
issuer: "https://accounts.google.com",
clientId: process.env.GOOGLE_CLIENT_ID!,
scope: "openid email profile",
callbackPath: "/api/auth/oauth/google/callback",
},
})
`ts`
window.location.assign("/api/auth/oauth/mock/start")
You can optionally pass a return path:
`ts`
window.location.assign("/api/auth/oauth/mock/start?returnTo=/app")
If you don’t want to mount the full authRoutes, you can register the OAuth handlers manually:
`ts
import { createOAuthCallbackHandler, createOAuthStartHandler } from "@rpcbase/auth/oauth"
export default (api) => {
api.get("/api/auth/oauth/:provider/start", createOAuthStartHandler({
getAuthorizationParams: (providerId) =>
providerId === "apple"
? { response_mode: "form_post" }
: undefined,
}))
const callback = createOAuthCallbackHandler({
createUserOnFirstSignIn: false,
missingUserRedirectPath: "/auth/sign-up",
successRedirectPath: "/app",
})
api.get("/api/auth/oauth/:provider/callback", callback)
api.post("/api/auth/oauth/:provider/callback", callback)
}
`
On a successful callback:
- tokens are exchanged (/token)RBUser
- userinfo is fetched (when supported by the provider)
- the matching is created on first-seen identity (and the OAuth credentials are saved under RBUser.oauthProviders[provider])/onboarding
- the session is signed in, and the user is redirected to
If the callback handler is configured with createUserOnFirstSignIn: false and no matching user is found, it redirects to missingUserRedirectPath (when provided).
Apple requires a JWT clientSecret. You can generate it server-side:
`ts`
import { createAppleClientSecret } from "@rpcbase/auth/oauth"
This repo uses oauth2-mock-server in sample-app/server.js to spin up a local OIDC provider for development and Playwright tests.
Notes:
- oauth2-mock-server simulates auth and immediately redirects back on /authorize (it does not provide a UI to pick an account).RB_OAUTH_MOCK_SERVER_PORT
- Override the port with (default 9400`)