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.sessionis 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
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 u8name: []const u8exp: i64: Expiry, in seconds since the epoch. 0 or less:jwtVerifynever treats the token as expired.
spider.auth.JwtError
pub const JwtError = error
What checking a token fails with.
Errors
InvalidFormat: Not three parts, not an HS256 header asjwtSignwrites it, or a payload that is not the expected claims.InvalidSignature: Signed with another secret, or changed after signing.Expired:expis in the past.
spider.auth.jwtSign
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).
const token = try spider.auth.jwtSign(c.arena, .{ .sub = "7", .exp = expires }, secret);
spider.auth.jwtPayload
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
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).
spider.auth.COOKIE_NAME
pub const COOKIE_NAME = "token"
The cookie the cookie* helpers below write and read.
spider.auth.cookieSet
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
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
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
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.
const cookie = try spider.auth.cookieClear(c.arena);
spider.auth.AuthConfig
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.publicpass too. Default: none.cookie_name: []const u8 = COOKIE_NAME: Cookie the middleware reads the token from.Auth.cookieForandAuth.cookieClearedwrite it; the freecookie*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: WhetherAuth.cookieForandAuth.cookieClearedaddSecure. 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
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 toinit. Its strings are not copied.
auth.Auth.init
pub fn init(config: AuthConfig) Auth
Keeps config; nothing is allocated.
auth.Auth.cookieFor
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.
const cookie = try auth.cookieFor(c.arena, token);
auth.Auth.cookieCleared
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
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
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.