Skip to content

Guide: Cookie auth

Switch the basic app from JSON token delivery to cookie delivery, with the CSRF client pattern. No providers change — only config and the client.

Config

auth = FastAuth(FastAuthConfig(
    ...,
    token_delivery="cookie",
    cookie_samesite="lax",   # default
    # cookie_secure defaults to (not debug): True in prod, False in dev
    # debug=True,            # uncomment to make cookies work over http://localhost
))

After register/login, the response carries three Set-Cookie headers (access_token, refresh_token, csrf_token) and the body is {"message": "Authentication successful"}.

Browser-side

async function login(email, password) {
  await fetch("/auth/login", {
    method: "POST",
    credentials: "include",   // important: send and accept cookies
    headers: {"Content-Type": "application/json"},
    body: JSON.stringify({email, password}),
  });
  // csartifact_token cookie is readable by JS:
  window.__csrf = getCookie("csrf_token");
}

async function callProtected(route) {
  return fetch(route, {
    credentials: "include",
    headers: {"X-CSRF-Token": window.__csrf},
  });
}

function getCookie(name) {
  return document.cookie.split("; ").map(c => c.split("="))
    .find(([k]) => k === name)?.[1];
}

Refresh with cookies

await fetch("/auth/refresh", {
  method: "POST",
  credentials: "include",
  headers: {"X-CSRF-Token": window.__csrf},
});

If the body is empty, /auth/refresh reads the refresh cookie and runs the CSRF check. (Posting a JSON {"refresh_token": "..."} skips CSRF — only do that if you've moved the refresh token out of the cookie.)

CORS

Cross-origin SPAs need cors_origins:

config = FastAuthConfig(..., cors_origins=["https://app.example.com"])

The middleware is added with allow_credentials=True. Never use ["*"] with credentials — browsers reject it.

Testing

TestClient stores cookies between calls when used as a context manager:

from fastapi.testclient import TestClient

def test_login_and_protected(app, client):
    with TestClient(app) as c:
        r = c.post("/auth/login", json={"email": "a@b.com", "password": "s3cur3!"})
        assert r.status_code == 200
        csrf = c.cookies["csrf_token"]
        r2 = c.post("/api/submit", json={"x": 1}, headers={"X-CSRF-Token": csrf})
        assert r2.status_code == 200

See Cookie delivery for the full reference.