Guides

Protect a route

A session token, one middleware call, and role checks. This is the pattern to copy into a new project.

Resolve the session

auth.getSession takes the raw token and returns the user, or null when it is missing or expired. Tokens arrive either in a cookie or as a bearer header.

yatta/func/session.tsts
import type { AuthSession } from "yatta/auth";import { auth } from "./auth"; /** Reads the session from the Authorization header, or the cookie. */export async function getSession(req: Request): Promise<AuthSession | null> {  const bearer = req.headers.get("Authorization")?.replace(/^Bearer\s+/i, "");  const cookie = readCookie(req.headers.get("Cookie"), "session");   return auth.getSession(bearer ?? cookie);} function readCookie(header: string | null, name: string): string | null {  if (!header) return null;  for (const part of header.split(";")) {    const [k, ...rest] = part.trim().split("=");    if (k === name) return rest.join("=");  }  return null;}

Require auth on one route

yatta/backend/me.tsts
import { API, createAPI } from "yatta/api";import { getSession } from "../func/session"; const api = createAPI(); api.get(async (ctx) => {  const session = await getSession(ctx.req);   if (!session) {    return API.json({ error: "Unauthorized" }, { status: 401 });  }   return API.json({ user: session.user });}); export default api;

Require auth everywhere

Middleware runs before every handler in the same file. Return early to short-circuit.

yatta/backend/dashboard/index.tsts
import { API, createAPI } from "yatta/api";import { getSession } from "../../func/session"; const api = createAPI(); // Applies to every route in this file.api.use(async (ctx, next) => {  const session = await getSession(ctx.req);   if (!session) {    // Send browsers to a login page; APIs get a 401.    if (ctx.req.headers.get("accept")?.includes("text/html")) {      return Response.redirect("/login", 302);    }    return API.json({ error: "Unauthorized" }, { status: 401 });  }   // Shared with handlers below.  ctx.state.session = session;   return next();}); api.get(async (ctx) => {  return API.json({ user: ctx.state.session.user });}); export default api;
Note
ctx.state is a plain bag for middleware to pass values downstream. It is typed loosely, so give ctx.state.session a known shape at the top of the file.

Check roles

Users carry a roles array. The permission helper in Authentication checks a role or a full permission string.

rolests
const { user } = ctx.state.session; // simple role checkif (!user.roles.includes("admin")) {  return API.json({ error: "Forbidden" }, { status: 403 });} // permission check, if you configured rolesconst allowed = auth.permissions.check(user.roles, "delete", "posts"); // ownership: only the author may edit their own postif (post.authorId !== user.id && !user.roles.includes("admin")) {  return API.json({ error: "Forbidden" }, { status: 403 });}

Put the check in middleware when every route needs the same role:

admin-onlyts
api.use(async (ctx, next) => {  const session = await getSession(ctx.req);  if (!session) return API.json({ error: "Unauthorized" }, { status: 401 });   if (!session.user.roles.includes("admin")) {    return API.json({ error: "Forbidden" }, { status: 403 });  }   ctx.state.user = session.user;  return next();});

Setting the session cookie

Login and OAuth both return cookies and a toResponse helper that sets them for you.

yatta/backend/login.tsts
import { API, createAPI } from "yatta/api";import { auth } from "../func/auth"; const api = createAPI(); api.post(async (ctx) => {  const { email, password } = await ctx.json();   const result = await auth.signIn({ email, password, req: ctx.req });   // Sets the HttpOnly session cookie and returns the user.  return result.toResponse();}); export default api;

CSRF

The session cookie is HttpOnly, so JavaScript cannot read it. For cookie-authenticated state-changing requests, check the Origin header as well.

origin checkts
const SAFE = new Set(["https://yourdomain.com", "http://localhost:4000"]); api.post(async (ctx) => {  const origin = ctx.req.headers.get("Origin");  if (origin && !SAFE.has(origin)) {    return API.json({ error: "Forbidden origin" }, { status: 403 });  }   // …});
Note
SameSite=Lax (the default on issued cookies) already blocks cross-site POSTs from other origins. The Origin check is cheap insurance, not a replacement.