Six config blocks in /etc/qnt/server.toml decide how
people sign in to your dashboard and how ops tooling talks to the
control plane. The auth model: per-user JWT access tokens plus a
rotating refresh cookie, with the static admin token reserved for
machines.
The [web_auth] block governs the dashboard's
end-user login. Its keystone is the signing key: every access
token issued to a browser is signed with it.
Write a random secret to a file and lock it down. Point
jwt_secret_path at it. If the path is empty, the
entire /auth/* surface is disabled.
head -c 48 /dev/urandom | base64 | tr -d '\n' > /etc/qnt/jwt.key
chmod 600 /etc/qnt/jwt.key [web_auth]
jwt_secret_path = "/etc/qnt/jwt.key"
dashboard_base_url = "https://login.example.com" jwt_secret_path — file holding the JWT
signing key. Empty disables /auth/*.dashboard_base_url — base URL for the
dashboard, used in OAuth redirects and emails. Has a default.Access tokens are HS256 with a 15-minute TTL; the long-lived half is an opaque, rotating refresh token in an HttpOnly cookie. Passwords are hashed with argon2id. Optional TOTP MFA backed by 10 recovery codes is available per user.
The signing key is the root of trust for every session. Replace the file and every issued access token is invalidated at once — all logged-in users are signed out and must re-authenticate. Treat rotation as a deliberate, announced event, and back the key up alongside your other secrets.
The [api] block guards the control-plane API. By
default it demands bearer auth on every /api/* route
except /health.
The static admin token authenticates service-to-service and ops
callers. Keep it in a file — one line, whitespace-stripped — and
point admin_token_path at it.
[api]
require_auth = true
admin_token_path = "/etc/qnt/admin.token" require_auth — require bearer auth on
/api/* except /health. Defaults to
true in production; set false only for local dev.admin_token_path — file containing the static
admin token.admin_token — optional inline override. Avoid
in production; prefer the file.allowed_origin — optional CORS allowed origin.This token is ops-only and is never sent by browsers. Dashboard users authenticate with their per-user JWT; the admin token is for machines and operators talking to the API directly.
Social login lives under [oauth], with one
sub-table per provider. Google is the standard flow.
[oauth.google]
client_id = "your-google-client-id.apps.googleusercontent.com"
client_secret_path = "/etc/qnt/google-oauth.secret" client_id — leaving it empty disables Google
login.client_secret_path — path to the file holding
the client secret.
At the Google provider, register the callback against your
dashboard_base_url. The path shape is
/api/auth/<provider>/callback:
https://login.example.com/api/auth/google/callback
GitHub is the second provider sub-table under
[oauth]. Same shape, one wrinkle.
[oauth.github]
client_id = "your-github-client-id"
client_secret_path = "/etc/qnt/github-oauth.secret" client_id — leaving it empty disables GitHub
login.client_secret_path — path to the file holding
the client secret.GitHub OAuth uses no PKCE; Google is the standard flow.
As with Google, the callback points at your
dashboard_base_url with the
/api/auth/<provider>/callback shape:
https://login.example.com/api/auth/github/callback
Signup sends a one-time verification code, so transactional email
has to work for self-service registration to complete. The
[resend] block wires it up.
[resend]
api_key_path = "/etc/qnt/resend.key"
from_address = "noreply@example.com"
from_name = "21tunnel" api_key_path — path to the Resend API key
file. Empty turns off the email features of
/auth/*.from_address — a verified sender address.from_name — the display name on outgoing
mail.Without a working key, signup OTP and verification emails can't be delivered, so email-based registration won't complete.
Two final settings tune how the refresh cookie is set and which origin the browser API will talk to.
[web_auth]
secure_cookies = true
[api]
allowed_origin = "https://login.example.com" secure_cookies (in [web_auth]) —
defaults to true and requires HTTPS. Set it false only for
local dev over plain HTTP.allowed_origin (in [api]) — the
CORS allowed origin for browser calls.
Leaving secure_cookies true in production is what
keeps the HttpOnly refresh cookie from being sent over plain
HTTP.
Auth wired up. Carry on through the admin guide.