Pinned 2026 toolchains (Go 1.26, Rust 1.98/edition 2024, Python 3.14 + uv, Node 24, Zig 0.16, NixOS 26.05), postgres 18 / mongo 8, lockfiles built from, non-root runtimes, .dockerignore, per-project LICENSE, READMEs with the git.devai.io clone line, checkout@v7 CI. Security fixes in the legacy Rust APIs (any-password login, self-assigned admin, hard-coded JWT secret), JWT alg/exp/sub enforcement across the blog series, safe markdown links in the frontends, and many smaller bugs — every project was built, run and exercised end to end. Adds scripts/publish.sh + a CI publish job that splits every folder into its own repo at git.devai.io/templates/<folder>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01128fhuZbgivaSJvtMf4s1G
2.7 KiB
Auth via Auth0
auth0.zig replaces the local email+password auth with
Auth0: the app stops issuing tokens and instead verifies
Auth0 access tokens (RS256) against your tenant's JWKS — signature, exp,
nbf, issuer and audience — using only the standard library.
Setup
-
In the Auth0 dashboard create an API (Applications → APIs); its identifier is your audience. Your tenant domain (e.g.
your-tenant.us.auth0.com) is the issuer host. -
Copy
auth0.zigintosrc/and deletesrc/jwt.zig. -
src/auth.zigshrinks torequireAuth(dropregister,login,normalizeEmailand the argon2/jwt imports):pub fn requireAuth(app: *App, arena: Allocator, req: *http.Server.Request) ![]const u8 { const token = web.bearerToken(req) orelse return error.Unauthorized; return app.verifier.verify(arena, token) catch error.Unauthorized; } -
In
src/main.zig, remove the/auth/registerand/auth/loginroutes, addconst auth0 = @import("auth0.zig");, replace theauth_secretfield ofAppwithverifier: *auth0.Verifier, and replace theAUTH_SECRETlookup inmainwith:const domain = env.get("AUTH0_DOMAIN") orelse std.process.fatal("AUTH0_DOMAIN is required", .{}); const audience = env.get("AUTH0_AUDIENCE") orelse std.process.fatal("AUTH0_AUDIENCE is required", .{}); var verifier = try auth0.Verifier.init(gpa, io, domain, audience); defer verifier.deinit();and set
.verifier = &verifierwhereappis built. -
Auth0 user ids are strings (
auth0|...), not ObjectIds, and Auth0 is the user store now. Insrc/db.zig, storeauthor_idas a string: increatePostdrop theauthorObjectId and append it withappendStr(doc, "author_id", author_id), and read it withgetStr(arena, doc, "author_id")inparsePost. DeleteUser,createUser,getUserByEmailand theusersindex inensureIndexes.src/posts.zigalready treats user ids as strings. -
Set
AUTH0_DOMAIN=your-tenant.us.auth0.comandAUTH0_AUDIENCE=https://api.example.cominstead ofAUTH_SECRET.
Clients obtain access tokens through one of Auth0's flows (Authorization Code
- PKCE for SPAs; Client Credentials for a quick server-side test) and send
them as
Authorization: Bearer <token>. Request the token with your API's audience, or it is rejected.
Notes
- The JWKS is fetched over HTTPS with
std.http.Client, which needs the system CA bundle — the runtime image installsca-certificates. - Keys are cached in memory. A token with an unknown
kidtriggers a refetch (at most once a minute), so key rotations need no restart.