npm package: @rpcbase/auth
Description: UI helpers + API routes for authentication.
Latest version: 0.122.0

# `@rpcbase/auth`

UI helpers + API routes for authentication.

### 1) Register auth routes in your API

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.

### 2) Configure providers

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",
  },
})
```

### 3) Start the OAuth flow from the client

```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")
```

### Optional: declare OAuth routes yourself

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)
}
```

### 4) What happens on callback

On a successful callback:

- tokens are exchanged (`/token`)
- userinfo is fetched (when supported by the provider)
- the matching `RBUser` is created on first-seen identity (and the OAuth credentials are saved under `RBUser.oauthProviders[provider]`)
- the session is signed in, and the user is redirected to `/onboarding`

If the callback handler is configured with `createUserOnFirstSignIn: false` and no matching user is found, it redirects to `missingUserRedirectPath` (when provided).

### Apple (helper)

Apple requires a JWT `clientSecret`. You can generate it server-side:

```ts
import { createAppleClientSecret } from "@rpcbase/auth/oauth"
```

### Mock provider (dev/tests)

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).
- Override the port with `RB_OAUTH_MOCK_SERVER_PORT` (default `9400`)