Local authentication¶
Use system.webapi.auth for Argon2id passwords, signed access tokens, and rotating refresh sessions. The account repository and persistent storage remain application-owned.
import system;
import system.webapi;
import system.webapi.auth;
class Accounts: AuthAccounts {
hash: string;
public constructor(hash: string) {
this.hash = hash;
}
public async unique fun find_login(login: string): Result<AuthAccount, AuthError> {
if login != "demo" {
return Result.Err(AuthError("EAUTH", "unknown account"));
}
return Result.Ok(AuthAccount("demo-user", this.hash, "read"));
}
public async unique fun link_identity(
issuer: string,
subject: string,
email: string,
email_verified: bool
): Result<AuthAccount, AuthError> {
return Result.Err(
AuthError("EAUTH", "external account linking requires application policy")
);
}
}
async fun main(): void {
let key = System.env("API_JWT_KEY").unwrap();
let password = System.env("DEMO_PASSWORD").unwrap();
let auth = AuthService(
JwtOptions("https://api.example.com", "example-api", key, key, JwtAlgorithm.HS256),
Accounts(Password.hash(password).unwrap()),
MemoryAuthStore()
);
let app = WebApp();
app.include_router(auth.router().unwrap()).unwrap();
app.use(auth.middleware()).unwrap();
app.run("127.0.0.1", 8000).await.unwrap();
}
Set API_JWT_KEY to an explicit secret containing at least 32 bytes and DEMO_PASSWORD to a chosen demonstration password. Do not use this in-memory repository for production accounts.
Endpoints¶
auth.router() adds these routes under the service prefix, /auth by default.
| Route | Request body | Effect |
|---|---|---|
POST /auth/login |
{"username": "...", "password": "..."} |
Checks the password and returns new tokens. |
POST /auth/refresh |
{"refresh_token": "..."} |
Consumes the refresh token and returns a new pair. |
POST /auth/logout |
{"refresh_token": "..."} |
Revokes the token's whole session family. |
Login and refresh return access_token, refresh_token, token_type, expires_in (seconds), and csrf_token. Send the access token as Authorization: Bearer <token>.
Each refresh token can be consumed once. Replay revokes the entire session family, including already-issued access tokens, because verification checks the backing session's active state.
Service options¶
AuthService(jwt, accounts, store, ...) takes three required values, then these optional ones in order.
| Argument | Default | Effect |
|---|---|---|
prefix |
"/auth" |
Path prefix for the routes above. |
access_seconds |
900 |
Access token lifetime. |
refresh_seconds |
604800 |
Refresh session lifetime (7 days). |
browser_sessions |
false |
Also issue a session cookie on login. |
browser_origin |
"" |
The exact Origin that browser logins must send; required with browser_sessions. |
JwtOptions argument |
Default | Meaning |
|---|---|---|
issuer |
Required | The iss claim written and checked. |
audience |
Required | The aud claim written and checked. |
signing_key |
Required | Private PEM key for RS256, or the shared secret for HS256. |
verification_key |
Required | Public PEM key for RS256, or the same shared secret for HS256. |
algorithm |
"RS256" |
RS256 or HS256. |
Issuer, audience, algorithm, signature, expiry, and session activity are verified. Configuration requires explicit keys; the framework never creates an application signing key.
Interfaces you implement¶
| Interface | Methods | Purpose |
|---|---|---|
AuthAccounts |
find_login(login), link_identity(issuer, subject, email, email_verified) |
Look up local accounts and decide how external logins map to them. Return an AuthAccount(subject, password_hash, scopes). |
AuthSessionStore |
Session and login-transaction storage | Persist refresh sessions. MemoryAuthStore implements it for tests. |
MemoryAuthStore is for tests and single-process examples. Implement AuthSessionStore for production persistence. rotate must atomically consume the old digest, insert its replacement, and revoke the family if the old digest was consumed. A read followed by a separate write is insufficient under concurrency. Store refresh digests rather than bearer secrets.
Passwords use Argon2id PHC strings. Verification returns an error for malformed or unsupported hash parameters and false for a password mismatch. The crypto host bounds password and PHC costs. Protect credential endpoints with deployment-appropriate request limits.
See the API lookup and complete example.
To issue local browser session cookies, set browser_sessions: true and an explicit browser_origin. Browser login requests must send that exact Origin; subsequent cookie-authenticated writes must send the session CSRF token.