Authentication (vania_auth)
The vania_auth package provides a complete authentication system: JWT token generation and verification, password hashing, guard-based multi-auth, personal access tokens, and authorization gates.
Installation
dependencies:
vania_auth: ^1.0.0
Setup
1. Create the Personal Access Tokens Table
vania make:auth
This generates a migration for the personal_access_tokens table where token hashes are stored.
Run the migration:
vania migrate
2. Configure the Auth Service Provider
In your bin/server.dart or a dedicated provider:
import 'package:vania_auth/vania_auth.dart';
void main() async {
registerMySqlDriver();
await AuthServiceProvider().register(
jwtSecretKey: env('APP_KEY'),
tokenStore: ModelPersonalAccessTokenStore(model: PersonalAccessToken()),
userProvider: ModelUserProvider(model: User()),
);
await Application().initialize(config: config);
}
3. Configure Auth Guards
In lib/config/auth.dart:
import 'package:my_app/app/models/user.dart';
Map<String, dynamic> authConfig = {
'guards': {
'default': {
'provider': User(),
},
},
};
Authenticating Users
Login (Issue a Token)
class AuthController extends Controller {
Future<Response> login(Request req) async {
req.validate({
'email': 'required|email',
'password': 'required',
});
final user = await User().query
.firstWhere('email', '=', req.input('email'));
if (user == null || !Auth().hash.verify(req.input('password'), user['password'])) {
return Response.json({'message': 'Invalid credentials'}, 401);
}
Auth().login(user);
final tokenData = await Auth().createToken(expiresIn: Duration(days: 30));
return Response.json({
'token': tokenData['access_token'],
'user': user,
});
}
}
Protecting Routes
Use the Authenticate middleware:
Router.group(() {
Router.get('/profile', userController.profile);
Router.put('/profile', userController.updateProfile);
Router.post('/logout', authController.logout);
}, middleware: [Authenticate()]);
Accessing the Authenticated User
Future<Response> profile(Request req) async {
var user = req.user;
return Response.json(user);
}
Token Verification
bool valid = await Auth().check(token);
Refresh Tokens
Future<Response> refresh(Request req) async {
final refreshToken = req.input('refresh_token');
final tokenData = await Auth().createTokenByRefreshToken(refreshToken);
return Response.json(tokenData);
}
Logout (Revoke Token)
Future<Response> logout(Request req) async {
final token = req.header('authorization')?.replaceFirst('Bearer ', '');
if (token != null) {
await Auth().revokeToken(token);
}
return Response.json({'message': 'Logged out'});
}
// Revoke all tokens for a user
await Auth().revokeAllTokens(userId);
Password Hashing
// Hash a password
String hashed = Auth().hash.make('my_password');
// Verify a password
bool matches = Auth().hash.verify('my_password', hashed);
The default hasher uses HMAC-SHA512 with 600,000 iterations.
Authorization (Gates)
Define abilities and check them:
Abilities are named boolean checks registered on Gate(). Register them once at startup, then check them by name:
// Define an ability (the callback returns a bool)
Gate().define('edit-posts', () => Auth().user?['role'] == 'editor');
// Check it
if (Auth().authorize('edit-posts')) {
// allowed
}
if (Auth().denies('edit-posts')) {
// not allowed
}
authorize and denies take only the ability name; the ability's own closure decides the result (typically by reading Auth().user).
Guard-Based Multi-Auth
Support multiple authentication contexts (e.g., users and admins):
// Configure multiple guards
Auth().configureGuard('admin',
tokenStore: ModelPersonalAccessTokenStore(model: PersonalAccessToken()),
userProvider: ModelUserProvider(model: Admin()),
);
// Login as admin
Auth().guard('admin').login(adminUser);
var token = await Auth().guard('admin').createToken();
// Check admin auth
Auth().guard('admin').check(token);
Middleware Options
Authenticate
Validates bearer tokens and loads the user:
Router.get('/api/me', handler).middleware([Authenticate()]);
RedirectIfAuthenticated
For guest-only routes (login, register pages):
Router.get('/login', authController.showLogin)
.middleware([RedirectIfAuthenticated()]);
Token Store Customization
The default ModelPersonalAccessTokenStore stores tokens in the database. You can customize column mapping:
ModelPersonalAccessTokenStore(
columns: TokenStoreColumns(
id: 'id',
tokenableType: 'tokenable_type',
tokenableId: 'tokenable_id',
name: 'name',
token: 'token',
abilities: 'abilities',
lastUsedAt: 'last_used_at',
expiresAt: 'expires_at',
createdAt: 'created_at',
updatedAt: 'updated_at',
),
);
User Provider
The ModelUserProvider works with any ORM model. Customize the columns:
ModelUserProvider(
model: User(),
emailColumn: 'email',
passwordColumn: 'password',
);
Or implement UserProvider directly for custom logic:
class CustomUserProvider implements UserProvider {
@override
Future<Map<String, dynamic>?> findById(dynamic id) async { ... }
@override
Future<Map<String, dynamic>?> findByEmail(String email) async { ... }
@override
bool validatePassword(String plain, String hashed) { ... }
@override
Future<Map<String, dynamic>> create(Map<String, dynamic> data) async { ... }
@override
String get idKey => 'id';
}