Zig 0.17.0

auth (HS256 tokens)

On this page:

  • spider.auth: Tokens signed with a secret of the app (HS256) and the cookie helpers around them. For a login with the app's own users, spider.session is the ready-made one.

spider.auth

HS256 tokens and the older cookie login (spider.auth): signing and checking a JWT with a shared secret, helpers for a cookie named "token", and the Auth middleware over them. New apps use spider.session, which signs and verifies with jwtSign and jwtPayload from this file.

spider.auth.Claims

zig
pub const Claims = struct

The claims the Auth middleware expects in a token: a numeric user id, email, name and expiry. Sign one with jwtSign(alloc, Claims{...}, secret).

Fields

  • sub: i32: The user id.
  • email: []const u8
  • name: []const u8
  • exp: i64: Expiry, in seconds since the epoch. 0 or less: jwtVerify never treats the token as expired.

spider.auth.JwtError

zig
pub const JwtError = error

What checking a token fails with.

Errors

  • InvalidFormat: Not three parts, not an HS256 header as jwtSign writes it, or a payload that is not the expected claims.
  • InvalidSignature: Signed with another secret, or changed after signing.
  • Expired: exp is in the past.

spider.auth.jwtSign

zig
pub fn jwtSign(alloc: std.mem.Allocator, claims: anytype, secret: []const u8) ![]u8

A token (JWT, HS256) carrying claims, signed with secret. claims is any struct that serializes to JSON; nothing is added to it, so put exp in it yourself. The caller owns the result (allocated with alloc).

zig
const token = try spider.auth.jwtSign(c.arena, .{ .sub = "7", .exp = expires }, secret);

spider.auth.jwtPayload

zig
pub fn jwtPayload(alloc: std.mem.Allocator, token: []const u8, secret: []const u8) ![]u8

The JSON payload of token once its HS256 signature is checked against secret; the caller owns it. Does not look at the claims (not at exp either): jwtVerify and spider.session do. Errors: JwtError.InvalidFormat (not three parts, a header other than the one jwtSign writes, a payload that is not base64url) and JwtError.InvalidSignature.

spider.auth.jwtVerify

zig
pub fn jwtVerify(comptime T: type, alloc: std.mem.Allocator, io: std.Io, token: []const u8, secret: []const u8) !T

Checks the signature of token and its exp, and returns its payload parsed as T (a struct with at least sub and exp).

Safe with T = Claims, or a T whose only strings are fields named email, name and locale: those three (which must be []const u8) are copied with alloc. Every other string or slice of the result (a string sub, a list of roles, the strings of a nested struct) points into memory that is freed before this function returns. For your own claims use jwtPayload and parse the payload yourself, as spider.session does.

With Claims, email and name are copies allocated with alloc (the caller owns them). The header must be, byte for byte, the one jwtSign writes: an HS256 token another library signed with the same secret can fail here. Errors: JwtError.InvalidFormat (also when the payload has a field T does not, or lacks one it requires), JwtError.InvalidSignature, JwtError.Expired (exp greater than 0 and earlier than the current second; a token is still accepted during the second its exp names).

zig
pub const COOKIE_NAME = "token"

The cookie the cookie* helpers below write and read.

spider.auth.cookieSet

zig
pub fn cookieSet(alloc: std.mem.Allocator, token: []const u8) ![]u8

A Set-Cookie value that stores token in the "token" cookie: HttpOnly, SameSite=Lax, Path=/, one day, without Secure. The caller owns it.

spider.auth.cookieSetSecure

zig
pub fn cookieSetSecure(alloc: std.mem.Allocator, token: []const u8, secure: bool) ![]u8

cookieSet, with the Secure attribute when secure is true.

spider.auth.cookieGet

zig
pub fn cookieGet(cookie_header: []const u8) ?[]const u8

The token in a Cookie request header value, or null when it has no "token" cookie. A slice of cookie_header, not a copy.

spider.auth.cookieClear

zig
pub fn cookieClear(alloc: std.mem.Allocator) ![]u8

A Set-Cookie value that empties and expires the "token" cookie (to log out). The caller owns it.

zig
const cookie = try spider.auth.cookieClear(c.arena);

spider.auth.AuthConfig

zig
pub const AuthConfig = struct

What Auth.init takes.

Fields

  • secret: []const u8: What the tokens were signed with (jwtSign). No default.
  • public_paths: []const []const u8 = &.{}: Paths let through without a token: an exact match, or a prefix when the entry ends in * ("/assets/*" covers everything that starts with "/assets/"). Compared with the path of the request; its query string is ignored ("/login" also lets "/login?next=/x" through). Routes marked .public pass too. Default: none.
  • cookie_name: []const u8 = COOKIE_NAME: Cookie the middleware reads the token from. Auth.cookieFor and Auth.cookieCleared write it; the free cookie* functions always use "token", whatever is set here.
  • redirect_to: []const u8 = "/login": Where a request without a valid token is redirected.
  • secure_cookie: bool = true: Whether Auth.cookieFor and Auth.cookieCleared add Secure. Turn it off only where the app is reached over plain http (a developer's machine): a browser drops a Secure cookie that arrives over http.

spider.auth.Auth

zig
pub const Auth = struct

The middleware of the cookie login: a request whose cookie holds a valid Claims token goes on, with the user in the params _user_id, _user_email and _user_name (c.userId() reads the first); any other request is redirected (302) to redirect_to, except on a route marked .public or a path in public_paths, which pass without a check. The Authorization header is not read.

Fields

  • config: AuthConfig: The config given to init. Its strings are not copied.

auth.Auth.init

zig
pub fn init(config: AuthConfig) Auth

Keeps config; nothing is allocated.

auth.Auth.cookieFor

zig
pub fn cookieFor(self: *const Auth, alloc: std.mem.Allocator, token: []const u8) ![]u8

A Set-Cookie value that stores token in the cookie this Auth reads (cookie_name): HttpOnly, SameSite=Lax, Path=/, one day, and Secure unless secure_cookie is off. The caller owns it.

zig
const cookie = try auth.cookieFor(c.arena, token);

auth.Auth.cookieCleared

zig
pub fn cookieCleared(self: *const Auth, alloc: std.mem.Allocator) ![]u8

A Set-Cookie value that empties and expires that cookie (to log out). The caller owns it.

auth.Auth.middleware

zig
pub fn middleware(self: *const Auth, c: *Ctx, next: NextFn) !Response

The check itself. Not a MiddlewareFn (it takes self): give asFn() to server.use.

auth.Auth.asFn

zig
pub fn asFn(self: *const Auth) MiddlewareFn

The middleware to register: server.use(auth.asFn()). It keeps a pointer to this Auth, which must stay valid while the server runs, in one static slot: a process can use one Auth; a second call replaces the first.