Auth Controller
AuthController is an abstract class that extends Controller. It provides the authentication and authorization contract for protecting routes. You implement it once per application and attach it to any route that requires login.
Abstract Methods
| Method | When it runs | What to return |
|---|---|---|
auth() |
On every request to a protected route | true to allow, false to reject (and handle the response) |
authApi() |
On requests to protected /api/ endpoints |
true to allow |
checkLogin() |
Called by auth() to validate the session |
A record ({bool success, String message, T? user}) |
checkPermission() |
After auth() passes, checks route.permissions |
true if user has the required permission |
loginPost() |
Handles POST to the login form route | HTML response |
logout() |
Handles the logout route | Redirect or HTML |
updateAuth(email, password, user) |
After successful login | Store session/cookie |
removeAuth() |
On logout | Clear session/cookie |
Complete Example
This is the auth controller from the Finch example project:
import 'package:finch/finch_route.dart';
import 'package:finch/finch_tools.dart';
import '../models/mock_user_model.dart';
import '../route/web_route.dart';
class AppAuthController extends AuthController<MockUserModel> {
MockUserModel? userLogined;
@override
Future<bool> auth() async {
var res = await checkLogin();
if (!res.success) {
// API requests get a JSON 403, web requests get a redirect
if (rq.isApiEndpoint) {
await rq.renderError(
403,
toData: true,
params: {'message': 'Please login.', 'success': false},
);
} else {
await rq.redirect('/example/form');
}
return false;
}
updateAuth(res.user!.email, res.user!.password, res.user!);
return true;
}
@override
Future<bool> authApi() async {
var auth = rq.authorization;
var mockUser = MockUserModel();
if (auth.type == AuthType.basic) {
String email = auth.getBasicUsername();
String password = auth.getBasicPassword();
return email == mockUser.email && password == mockUser.password;
} else if (auth.type == AuthType.bearer) {
return auth.value == '${mockUser.email} ${mockUser.password}';
}
return false;
}
@override
Future<({bool success, String message, MockUserModel? user})>
checkLogin() async {
var mockUser = MockUserModel();
var userSession = rq.getSession('user', def: '');
if (userSession == mockUser.email) {
return (success: true, message: 'Success.', user: mockUser);
}
return (success: false, message: 'Please login.', user: mockUser);
}
@override
Future<bool> checkPermission() async {
if (rq.route == null || userLogined == null) return false;
var permission = userLogined!.permission;
if (rq.route!.permissions.isNotEmpty &&
!rq.route!.permissions.contains(permission)) {
return false;
}
return true;
}
@override
Future<String> loginPost() async {
var formLogin = LoginForm();
await formLogin.check(
onInvalid: (p0) {},
onValid: (p0) {
var mockUser = MockUserModel();
var email = formLogin.get<String>('email', def: '');
var password = formLogin.get<String>('password', def: '');
if (email == mockUser.email && password == mockUser.password) {
updateAuth(email, password, mockUser);
} else {
rq.addParam('errorLogin', 'form.validation.loginError'.tr);
}
},
);
return homeController.renderView('example/form');
}
@override
Future<String> logout() {
removeAuth();
return rq.redirect('/example/form');
}
@override
Future<String> newUser() => throw UnimplementedError();
@override
Future<String> register() => throw UnimplementedError();
@override
void removeAuth() {
rq.session.remove('user');
rq.removeCookie('user');
userLogined = null;
}
@override
void updateAuth(String email, String password, MockUserModel user) {
userLogined = user;
rq.addSession('user', email);
}
}
Attaching to a Route
final authController = AppAuthController();
FinchRoute(
key: 'example.panel',
path: 'panel',
methods: Methods.ALL,
auth: authController,
permissions: ['admin'],
index: homeController.exampleAuth,
),
Request flow for a protected route:
- Route matched →
auth()called → if returnsfalse, request stopscheckPermission()called → if returnsfalse, request stops- Controller / index handler executes
Authorization Header
For API routes, read the Authorization header via rq.authorization:
var auth = rq.authorization;
if (auth.type == AuthType.bearer) {
String token = auth.value;
// validate token
}
if (auth.type == AuthType.basic) {
String user = auth.getBasicUsername();
String pass = auth.getBasicPassword();
// validate credentials
}