Auth
How these pieces fit together is explained in Security model.
StudioAuth
Section titled “StudioAuth”Interface for studio authentication strategies.
abstract class StudioAuth { bool isAuthenticated(Map<String, String> headers); String? authenticate(String username, String password); void revokeToken(String token); String? currentToken(Map<String, String> headers); String setCookieHeader(String token, {String prefix = ''}); String clearCookieHeader({String prefix = ''}); String? weaknessReason() => null;}When to use: implement it to check credentials against something other than one fixed
username and password, such as your users table or LDAP. All signed-in users still get full
access to every registered table. For the common case, use
SessionStudioAuth or JwtStudioAuth.
| Member | Description |
|---|---|
isAuthenticated(headers) |
true if the request headers (lower-case keys) carry a valid credential. Called on every request except login/. |
authenticate(username, password) |
Returns a token to store in the cookie, or null for bad credentials. Synchronous. |
revokeToken(token) |
Called on logout. |
currentToken(headers) |
The raw token from the cookie or header, or null. The CSRF token is derived from it. |
setCookieHeader(token, {prefix}) |
Set-Cookie value installing the token, scoped to prefix (/ when empty). The studio appends Secure on HTTPS. |
clearCookieHeader({prefix}) |
Set-Cookie value clearing the cookie. |
weaknessReason() |
Why the credentials are too weak to serve, or null. The studio refuses to serve while this is non-null, unless allowInsecure. Defaults to null. |
Example
import 'dart:convert';import 'dart:math';
import 'package:gisila_studio/gisila_studio.dart';
/// Accepts any staff member from an in-memory map of username -> password.class StaffAuth extends StudioAuth { StaffAuth(this.staff); final Map<String, String> staff; final _sessions = <String>{}; final _rng = Random.secure();
@override String? authenticate(String username, String password) { final expected = staff[username]; if (expected == null || !constantTimeEquals(password, expected)) return null; final token = base64Url.encode(List.generate(32, (_) => _rng.nextInt(256))); _sessions.add(token); return token; }
@override bool isAuthenticated(Map<String, String> headers) { final t = currentToken(headers); return t != null && _sessions.contains(t); }
@override String? currentToken(Map<String, String> headers) { for (final part in (headers['cookie'] ?? '').split(';')) { final p = part.trim(); if (p.startsWith('staff_sid=')) return p.substring('staff_sid='.length); } return null; }
@override void revokeToken(String token) => _sessions.remove(token);
@override String setCookieHeader(String token, {String prefix = ''}) => 'staff_sid=$token; Path=${prefix.isEmpty ? '/' : prefix}; HttpOnly; SameSite=Lax';
@override String clearCookieHeader({String prefix = ''}) => 'staff_sid=; Path=${prefix.isEmpty ? '/' : prefix}; HttpOnly; SameSite=Lax; Max-Age=0';
@override String? weaknessReason() { for (final e in staff.entries) { final reason = credentialWeakness(e.key, e.value); if (reason != null) return '${e.key}: $reason'; } return null; }}SessionStudioAuth
Section titled “SessionStudioAuth”In-memory sessions with a cookie.
class SessionStudioAuth extends StudioAuth { SessionStudioAuth({required this.username, required this.password}); final String username; final String password;}When to use: a single-isolate server where losing sessions on restart is acceptable. It is
what GisilaStudio builds from username/password when no secret is given. For several
isolates or instances use JwtStudioAuth.
- Sign-in creates a random 32-byte token (base64url) stored in a server-side set and in the
_studio_sidcookie (HttpOnly; SameSite=Lax,Path= prefix). - Logout removes the token from the set.
- Sessions do not expire on their own; they last until logout or restart.
weaknessReason()iscredentialWeakness(username, password).
Example
final studio = GisilaStudio( db: db, auth: SessionStudioAuth(username: 'admin', password: 'correct-horse-battery'),);JwtStudioAuth
Section titled “JwtStudioAuth”Stateless HS256 JWTs.
class JwtStudioAuth extends StudioAuth { JwtStudioAuth({ required this.username, required this.password, required this.secret, this.expiry = const Duration(hours: 8), this.httpOnly = true, });}When to use: whenever the studio runs on more than one isolate or instance, or must keep
admins signed in across restarts. GisilaStudio builds one when you pass secret (or set
STUDIO_SECRET).
| Parameter | Type | Default | Description |
|---|---|---|---|
username |
String |
required | Admin username. |
password |
String |
required | Admin password. |
secret |
String |
required | HMAC-SHA256 signing key. At least 32 characters, or the studio refuses to serve. |
expiry |
Duration |
8 hours | Token lifetime (exp claim). |
httpOnly |
bool |
true |
When false, the _studio_jwt cookie is readable by page scripts. |
- The token carries
sub(the username),iatandexp. A token is valid when its signature matches andexpis in the future. - Tokens are read from
Authorization: Bearer <token>first, then from the_studio_jwtcookie. Bearer requests skip the CSRF check. revokeTokendoes nothing: logout clears the cookie, but a copied token stays valid until it expires. Keepexpiryshort, or overrideisAuthenticatedto consult a block list.weaknessReason()checks the credentials, thensecretWeakness(secret).
Example
import 'dart:io';
final studio = GisilaStudio( db: db, auth: JwtStudioAuth( username: 'admin', password: Platform.environment['STUDIO_PASSWORD']!, secret: Platform.environment['STUDIO_SECRET']!, expiry: const Duration(hours: 12), ),);credentialWeakness
Section titled “credentialWeakness”String? credentialWeakness(String username, String password);Returns why a username/password pair is too weak for the studio, or null.
When to use: in a custom StudioAuth.weaknessReason, or to validate
credentials in a setup script. Rejects an empty username, a password shorter than
minStudioPasswordLength, a password equal to the username
(ignoring case), and passwords containing changeme, changethis, yoursecret or
yourpassword (ignoring case and punctuation).
credentialWeakness('admin', 'admin'); // 'the password is shorter than 12 characters'credentialWeakness('admin', 'correct-horse-battery'); // nullsecretWeakness
Section titled “secretWeakness”String? secretWeakness(String secret);Returns why a signing secret is too weak, or null.
When to use: in a custom strategy that signs tokens. Rejects secrets shorter than
minStudioSecretLength and placeholder values.
minStudioPasswordLength
Section titled “minStudioPasswordLength”const minStudioPasswordLength = 12;Shortest password credentialWeakness accepts.
minStudioSecretLength
Section titled “minStudioSecretLength”const minStudioSecretLength = 32;Shortest signing secret secretWeakness accepts.
studioCsrfToken
Section titled “studioCsrfToken”String studioCsrfToken(String token);The CSRF token for a session token: base64url SHA-256 of 'gisila-studio-csrf:' + token,
without padding.
When to use: when you render your own POST form that is submitted to a studio route, or to
your own route that checks the studio session. Put it in a hidden _csrf field. The studio adds
the field to its own forms automatically.
final token = auth.currentToken(request.headers);final field = token == null ? '' : '<input type="hidden" name="_csrf" value="${studioCsrfToken(token)}">';constantTimeEquals
Section titled “constantTimeEquals”bool constantTimeEquals(String a, String b);Compares two strings in time that does not depend on where they differ (both are SHA-256 hashed first, so lengths do not leak either).
When to use: comparing passwords, tokens or CSRF values in a custom StudioAuth or custom
route.