UNPKG

wcz-layout

Version:

75 lines (60 loc) • 3.27 kB
# wcz-layout auth ## Rules - Everything is public by default; protection is opt-in on both layers. The route guard only hides UI, the middleware is the security boundary (see `routing` and `server-functions`). - `requireAuth` redirects a signed-out user to `/auth/login?returnTo=…`; a signed-in user without the permission gets an error, not a redirect. - `hasPermission(user, key)` is for conditional UI only (navigation, buttons), never a guard. - Read signed-in user from `Route.useRouteContext()` on client or `context.user` on server. - `userMiddleware` gives `context.user: User | null` without throwing. It is a single instance, deduped with `authMiddleware`; in server routes list both in the same `middleware` array so the caller is resolved once. - Permissions are evaluated per request, so moving an existing group between keys applies immediately. A group name new to `permissions.ts` applies only after the user signs in again (the cookie keeps only groups listed there). - AWS users have no groups, so `requireAuth(key)` / `authMiddleware(key)` always reject them. Use the bare form for routes AWS users must reach. - Offline, guards use a cached user snapshot from `localStorage` — UI state only, the server still authorizes every call. ```ts // src/lib/auth/permissions.ts — permission key -> group names (sAMAccountName) import type { Permissions } from "wcz-layout/models"; export const permissions = { admin: ["wcz-developers"], } as const satisfies Permissions; // src/lib/auth/scopes.ts — scope key -> one scope per API import type { Scopes } from "wcz-layout/models"; export const scopes = { graph: "User.Read", // bare string = Entra only file: { entra: "api://wistron.com/file-api/access_as_user", aws: "https://file-api.wistron.com/access_as_user", }, } as const satisfies Scopes; ``` ## Gates | Gate | Route `beforeLoad` | Server function / API route | | ----------------------- | ------------------ | --------------------------- | | Anyone (default) | — | — or `userMiddleware` | | Signed in, any provider | `requireAuth()` | `authMiddleware()` | | Signed in + permission | `requireAuth(key)` | `authMiddleware(key)` | ## Tokens for downstream APIs From `wcz-layout/auth`, server-only (call inside a server function or server route): - `getAccessToken(scopeKey)` — delegated token for the signed-in user from their session. Requires a browser session (a Bearer caller has none). An expired session clears the cookie and throws a 401 `Response`. - `getAppToken(scopeKey)` — app-only Entra token (client credentials, `<resource>/.default`), cached in memory. For background jobs and calls with no user. - Downstream APIs must trust both issuers to accept AWS users; Cognito tokens have no `aud` and are authorized by the `scope` claim. # Examples ```ts import { createServerFn } from "@tanstack/react-start"; import { authMiddleware } from "wcz-layout/middleware"; export const getData = createServerFn() .middleware([authMiddleware("admin")]) .handler(async () => { // user is guaranteed to have "admin" permission const user = context.user; return { data: "Secret admin information" }; }); ```