Zig 0.17.0

session

On this page:

  • spider.session: Login sessions for an app's own users: a signed cookie (or bearer token), start, end, middleware().

spider.session

Login sessions for an app with its own users: a signed cookie.

zig
// after checking the password
return c.redirectWith("/", try spider.session.start(c, .{
    .id = user_id, .email = user.email, .name = user.name,
}));

// main.zig
server.use(spider.session.middleware());

// logging out
return c.redirectWith("/", try spider.session.end(c));

The cookie holds a token signed with the app's secret (HS256), so the server keeps no table of sessions: it trusts what it signed. The middleware reads it on every request and fills in the request's user, which is what .authenticated and .roles on a route check, and what c.userId() returns. A route that is not .public answers 401 to a request with no session (error.Unauthorized: an app's error handler turns that into a redirect to the login page).

An API sends the same token in Authorization: Bearer <token>; get it with spider.session.token(c, user).

The secret is JWT_SECRET, from the environment or .env (spider new writes a random one). A session cannot be taken back before it expires: there is nothing on the server to delete. Changing the secret ends all of them.

spider.session.User

zig
pub const User = struct

Who is logged in.

Fields

  • id: []const u8: What c.userId() returns on later requests. Must not be empty: a token with an empty id is refused when it comes back.
  • email: []const u8 = ""
  • name: []const u8 = ""
  • roles: []const []const u8 = &.{}: What .roles on a route and c.hasRole check.

spider.session.Options

zig
pub const Options = struct

The settings of the session cookie and its token. Change them through spider.session.options before the server starts.

Fields

  • secret: ?[]const u8 = null: What tokens are signed with. Null: JWT_SECRET from the environment or .env, read once. When that is not set, is empty or is still "change_me_in_production", a release build fails with error.SessionSecretMissing, and a Debug build signs with a random secret made up for this run: logins end when the app restarts.
  • cookie: []const u8 = "session": Name of the cookie that carries the token.
  • max_age: u32 = 14 * 24 * 60 * 60: How long a login lasts, in seconds. Default: 14 days.
  • secure: bool = true: The cookie's Secure attribute. Browsers still send a Secure cookie to http://localhost; turn it off to test from another machine over plain http.

spider.session.options

zig
pub var options: Options = .{}

Set before the server starts to change the defaults.

spider.session.Error

zig
pub const Error = error

What start, token and the middleware fail with when there is no secret.

Errors

  • SessionSecretMissing: No JWT_SECRET (or it is still the placeholder) in a release build.

spider.session.start

zig
pub fn start(c: *Ctx, user: User) !ResponseOptions

Response options that log user in: a Set-Cookie with the signed token (HttpOnly, SameSite=Lax, Path=/, kept for options.max_age seconds). Allocated in the request arena. Fails with Error when there is no secret.

spider.session.token

zig
pub fn token(c: *Ctx, user: User) ![]const u8

The signed token alone, for an API whose clients send it back in Authorization: Bearer <token>. It expires options.max_age seconds from now. Allocated in the request arena.

spider.session.end

zig
pub fn end(c: *Ctx) !ResponseOptions

Response options that log the user out: the cookie, emptied and expired. The token itself stays valid until it expires (see the top of this file): a client that kept a copy can still send it.

spider.session.middleware

zig
pub fn middleware() MiddlewareFn

The middleware: server.use(spider.session.middleware()). It takes the token from Authorization: Bearer <token> and, when that is not there or is not a valid token, from the cookie, and sets the request's user and roles from the first valid one. A forged, expired or malformed token counts as no token. A request left without a user fails with error.Unauthorized (401), unless its route is .public or no route matched it (that one goes on to its 404).