Authentication Guards
Guards define how users are authenticated for each request. Khadem provides multiple built-in guards (API, Web) and allows you to create custom guards for specific authentication needs.
API Guard
Token-based authentication for APIs
Web Guard
Session-based authentication for web apps
Custom Guards
Build your own authentication logic
Understanding Guards
Guards are responsible for determining how users are authenticated. Each guard uses a driver (JWT, Token, Session) and a provider (users table, admins table) to authenticate requests.
dart
import 'package:khadem/khadem.dart';
/// Guard Architecture in Khadem
///
/// Guard -> Driver -> Provider -> User
/// | | | |
/// | | | └─> Authenticatable User Model
/// | | └─> Database/API/LDAP
/// | └─> JWT/Token/Session Strategy
/// └─> API/Web/Custom Guard
// Example: How a guard works
class GuardFlow {
static Future<void> authenticate() async {
// 1. AuthManager creates or gets guard
final auth = AuthManager(guard: 'api', provider: 'users');
// 2. Guard uses driver (JWT) to authenticate
// 3. Driver queries provider (users table)
// 4. Provider returns Authenticatable user
// 5. Driver creates tokens
// 6. AuthResponse returned to client
final response = await auth.attempt({
'email': 'user@example.com',
'password': 'password123',
});
print('Token: ${response.accessToken}');
print('User: ${response.user}');
}
}
Guard Components:
- Driver: How authentication is performed (JWT, Token, Session)
- Provider: Where user data comes from (users table, API, etc.)
- Strategy: Authentication logic and token management
API Guard - Token Authentication
The API Guard uses JWT or bearer tokens for stateless authentication. Perfect for REST APIs, mobile apps, and single-page applications.
dart
// config/auth.dart
const authConfig = {
'default': 'api', // Default guard for AuthManager()
'guards': {
'api': {
'driver': 'jwt', // Use JWT driver
'provider': 'users', // Use users provider
},
},
'providers': {
'users': {
'table': 'users',
'primary_key': 'id',
'fields': ['email'], // Authentication field
},
},
'jwt': {
'secret': env('JWT_SECRET'),
'algo': 'HS256',
'ttl': 3600, // 1 hour
'refresh_ttl': 1209600, // 2 weeks
},
};
dart
import 'package:khadem/khadem.dart';
class ApiAuthController {
/// Login with API guard (JWT tokens)
static Future<void> login(Request req, Response res) async {
final data = await req.validate({
'email': 'required|email',
'password': 'required|min:6',
});
// Use API guard (default)
final auth = AuthManager(guard: 'api');
final authResponse = await auth.attempt(data);
res.sendJson({
'success': true,
'data': {
'access_token': authResponse.accessToken,
'refresh_token': authResponse.refreshToken,
'token_type': 'Bearer',
'expires_in': authResponse.expiresIn,
'user': authResponse.user,
}
});
}
/// Verify JWT token
static Future<void> verify(Request req, Response res) async {
final token = req.header('authorization')?.replaceFirst('Bearer ', '');
if (token == null) {
return res.status(401).sendJson({'error': 'Token required'});
}
final auth = AuthManager(guard: 'api');
final isValid = await auth.check(token);
if (!isValid) {
return res.status(401).sendJson({'error': 'Invalid token'});
}
final user = await auth.user(token);
res.sendJson({
'valid': true,
'user': user.toAuthArray(),
});
}
/// Refresh JWT token
static Future<void> refresh(Request req, Response res) async {
final refreshToken = req.input('refresh_token');
if (refreshToken == null) {
return res.status(400).sendJson({'error': 'Refresh token required'});
}
final auth = AuthManager(guard: 'api');
final authResponse = await auth.refresh(refreshToken);
res.sendJson({
'success': true,
'data': authResponse.toMap(),
});
}
}
API Guard Features:
- JWT token generation and validation
- Automatic token refresh
- Token revocation and blacklisting
- Stateless authentication
- Perfect for microservices
Web Guard - Session Authentication
The Web Guard uses server-side sessions for traditional web applications. Sessions are stored server-side with only a session ID in the client cookie.
dart
// config/auth.dart
const authConfig = {
'default': 'web',
'guards': {
'web': {
'driver': 'session', // Use session driver
'provider': 'users',
},
},
'providers': {
'users': {
'table': 'users',
'primary_key': 'id',
'fields': ['email'],
},
},
'session': {
'lifetime': 120, // 120 minutes
'expire_on_close': false,
'encrypt': true,
'cookie_name': 'khadem_session',
},
};
dart
import 'package:khadem/khadem.dart';
class WebAuthController {
/// Login with web guard (sessions)
static Future<void> login(Request req, Response res) async {
final data = await req.validate({
'email': 'required|email',
'password': 'required|min:6',
'remember': 'nullable|boolean',
});
// Use web guard for session-based auth
final auth = AuthManager(guard: 'web');
final authResponse = await auth.attempt({
'email': data['email'],
'password': data['password'],
});
// Store user ID in session
req.session.set('user_id', authResponse.user['id']);
req.session.set('authenticated', true);
// Handle remember me
if (data['remember'] == true && authResponse.refreshToken != null) {
res.cookieHandler.set(
'remember_token',
authResponse.refreshToken!,
maxAge: const Duration(days: 30),
httpOnly: true,
secure: true,
);
}
// Regenerate session ID for security
req.session.regenerateId();
final intended = req.session.pull('url.intended') ?? '/dashboard';
res.redirect(intended.toString());
}
/// Check if user is authenticated
static Future<void> checkAuth(Request req, Response res) async {
final userId = req.session.get('user_id');
if (userId == null) {
return res.redirect('/login');
}
// Get user from database
final user = await User().query.where('id', '=', userId).first();
if (user == null) {
req.session.clear();
return res.redirect('/login');
}
res.view('dashboard', data: {'user': user.toJson()});
}
/// Logout
static Future<void> logout(Request req, Response res) async {
// Clear session
req.session.remove('user_id');
req.session.remove('authenticated');
// Clear remember token
res.cookieHandler.delete('remember_token');
// Regenerate session ID
req.session.regenerateId();
res.redirect('/login');
}
}
Web Guard Features:
- Server-side session storage
- CSRF protection
- Remember me functionality
- Session timeout and regeneration
- Perfect for traditional web apps
Using Multiple Guards
Your application can use multiple guards simultaneously. For example, use the API guard for mobile apps and the Web guard for your admin panel.
dart
// config/auth.dart
const authConfig = {
'default': 'api',
'guards': {
// API guard for mobile/SPA
'api': {
'driver': 'jwt',
'provider': 'users',
},
// Web guard for admin panel
'web': {
'driver': 'session',
'provider': 'admins',
},
// Admin API guard
'admin-api': {
'driver': 'jwt',
'provider': 'admins',
},
},
'providers': {
'users': {
'table': 'users',
'primary_key': 'id',
'fields': ['email'],
},
'admins': {
'table': 'admins',
'primary_key': 'id',
'fields': ['email', 'username'],
},
},
};
dart
import 'package:khadem/khadem.dart';
class MultiGuardController {
/// User login via API
static Future<void> userApiLogin(Request req, Response res) async {
final auth = AuthManager(guard: 'api', provider: 'users');
final authResponse = await auth.attempt(req.all());
res.sendJson({
'message': 'User authenticated via API',
'data': authResponse.toMap(),
});
}
/// Admin login via web
static Future<void> adminWebLogin(Request req, Response res) async {
final auth = AuthManager(guard: 'web', provider: 'admins');
final authResponse = await auth.attempt(req.all());
req.session.set('admin_id', authResponse.user['id']);
res.redirect('/admin/dashboard');
}
/// Admin login via API
static Future<void> adminApiLogin(Request req, Response res) async {
final auth = AuthManager(guard: 'admin-api', provider: 'admins');
final authResponse = await auth.attempt(req.all());
res.sendJson({
'message': 'Admin authenticated via API',
'data': authResponse.toMap(),
});
}
/// Get authenticated user from specific guard
static Future<void> getUser(Request req, Response res) async {
final guardName = req.input('guard') ?? 'api';
final token = req.header('authorization')?.replaceFirst('Bearer ', '');
if (token == null) {
return res.status(401).sendJson({'error': 'Token required'});
}
final auth = AuthManager(guard: guardName);
final user = await auth.user(token);
res.sendJson({
'guard': guardName,
'user': user.toAuthArray(),
});
}
}
Authentication Providers
Providers define where and how to retrieve user data. Configure different providers for different user types (users, admins, customers).
dart
// config/auth.dart
const authConfig = {
'guards': {
'api': {'driver': 'jwt', 'provider': 'users'},
'admin': {'driver': 'jwt', 'provider': 'admins'},
'customer': {'driver': 'jwt', 'provider': 'customers'},
},
'providers': {
'users': {
'table': 'users',
'primary_key': 'id',
'fields': ['email'], // Can login with email
},
'admins': {
'table': 'admins',
'primary_key': 'id',
'fields': ['email', 'username'], // Can login with email or username
},
'customers': {
'table': 'customers',
'primary_key': 'customer_id', // Custom primary key
'fields': ['email', 'phone'], // Can login with email or phone
},
},
};
dart
import 'package:khadem/khadem.dart';
class ProviderAuthController {
/// Login with email (users provider)
static Future<void> userLogin(Request req, Response res) async {
final auth = AuthManager(provider: 'users');
final authResponse = await auth.attempt({
'email': req.input('email'),
'password': req.input('password'),
});
res.sendJson(authResponse.toMap());
}
/// Login with email or username (admins provider)
static Future<void> adminLogin(Request req, Response res) async {
final auth = AuthManager(provider: 'admins');
// Can use email OR username
final identifier = req.input('identifier'); // email or username
final authResponse = await auth.attempt({
'identifier': identifier,
'password': req.input('password'),
});
res.sendJson(authResponse.toMap());
}
/// Login with email or phone (customers provider)
static Future<void> customerLogin(Request req, Response res) async {
final auth = AuthManager(provider: 'customers');
// Can use email OR phone
final authResponse = await auth.attempt({
'identifier': req.input('identifier'), // email or phone
'password': req.input('password'),
});
res.sendJson(authResponse.toMap());
}
}
Creating Custom Guards
Create custom guards for specific authentication requirements like OAuth, LDAP, or third-party authentication services.
dart
import 'package:khadem/khadem.dart';
/// Custom OAuth Guard Example
class OAuthGuard extends Guard {
final AuthConfig authConfig;
final String guardName;
final String providerKey;
OAuthGuard({
required this.authConfig,
required this.guardName,
required this.providerKey,
});
@override
Future<AuthResponse> attempt(Map<String, dynamic> credentials) async {
// OAuth flow implementation
final oauthProvider = credentials['provider']; // google, github, etc.
final oauthToken = credentials['oauth_token'];
// Verify OAuth token with provider
final userInfo = await _verifyOAuthToken(oauthProvider, oauthToken);
// Find or create user
final user = await _findOrCreateUser(userInfo);
// Generate app tokens
final accessToken = await _generateAccessToken(user);
final refreshToken = await _generateRefreshToken(user);
return AuthResponse(
user: user.toAuthArray(),
accessToken: accessToken,
refreshToken: refreshToken,
tokenType: 'Bearer',
expiresIn: 3600,
);
}
@override
Future<Authenticatable> user(String token) async {
// Verify and return user from token
final payload = await _verifyToken(token);
final userId = payload['user_id'];
final user = await User().query.where('id', '=', userId).first();
if (user == null) {
throw AuthException('User not found');
}
return user;
}
@override
Future<bool> check(String token) async {
try {
await _verifyToken(token);
return true;
} catch (e) {
return false;
}
}
@override
Future<AuthResponse> refresh(String refreshToken) async {
final payload = await _verifyRefreshToken(refreshToken);
final user = await user(payload['user_id']);
final newAccessToken = await _generateAccessToken(user);
return AuthResponse(
user: user.toAuthArray(),
accessToken: newAccessToken,
tokenType: 'Bearer',
expiresIn: 3600,
);
}
@override
Future<void> logout(String token) async {
// Invalidate token
await _invalidateToken(token);
}
@override
Future<void> logoutAll(dynamic userId) async {
// Invalidate all tokens for user
await _invalidateAllTokens(userId);
}
@override
Future<void> logoutOthers(dynamic userId, String currentToken) async {
// Invalidate all tokens except current
await _invalidateOtherTokens(userId, currentToken);
}
// Helper methods
Future<Map<String, dynamic>> _verifyOAuthToken(String provider, String token) async {
// Implement OAuth verification
throw UnimplementedError();
}
Future<User> _findOrCreateUser(Map<String, dynamic> userInfo) async {
throw UnimplementedError();
}
Future<String> _generateAccessToken(User user) async {
throw UnimplementedError();
}
Future<String> _generateRefreshToken(User user) async {
throw UnimplementedError();
}
Future<Map<String, dynamic>> _verifyToken(String token) async {
throw UnimplementedError();
}
Future<Map<String, dynamic>> _verifyRefreshToken(String token) async {
throw UnimplementedError();
}
Future<void> _invalidateToken(String token) async {
throw UnimplementedError();
}
Future<void> _invalidateAllTokens(dynamic userId) async {
throw UnimplementedError();
}
Future<void> _invalidateOtherTokens(dynamic userId, String currentToken) async {
throw UnimplementedError();
}
}
dart
import 'package:khadem/khadem.dart';
class AuthServiceProvider {
static void boot() {
// Register custom guard factory
AuthManager.registerGuardFactory('oauth', (config, guardName) {
return OAuthGuard(
authConfig: config,
guardName: guardName,
providerKey: 'users',
);
});
}
}
// Usage
class OAuthController {
static Future<void> login(Request req, Response res) async {
final auth = AuthManager(guard: 'oauth');
final authResponse = await auth.attempt({
'provider': 'google',
'oauth_token': req.input('oauth_token'),
});
res.sendJson({
'success': true,
'data': authResponse.toMap(),
});
}
}
Guard Methods Reference
All guards implement a common interface with these methods for authentication operations.
dart
import 'package:khadem/khadem.dart';
/// Complete Guard Interface Reference
abstract class Guard {
/// Attempt to authenticate with credentials
Future<AuthResponse> attempt(Map<String, dynamic> credentials);
/// Get authenticated user from token
Future<Authenticatable> user(String token);
/// Check if token is valid
Future<bool> check(String token);
/// Refresh access token using refresh token
Future<AuthResponse> refresh(String refreshToken);
/// Logout (invalidate single token)
Future<void> logout(String token);
/// Logout from all devices (invalidate all tokens for user)
Future<void> logoutAll(dynamic userId);
/// Logout from other devices (keep current token)
Future<void> logoutOthers(dynamic userId, String currentToken);
}
// Example: Using all guard methods
class GuardMethodsExample {
static Future<void> demonstrateAll() async {
final auth = AuthManager(guard: 'api');
// 1. Attempt authentication
final authResponse = await auth.attempt({
'email': 'user@example.com',
'password': 'password123',
});
print('Logged in: ${authResponse.accessToken}');
// 2. Get user from token
final user = await auth.user(authResponse.accessToken!);
print('User: ${user.toAuthArray()}');
// 3. Check token validity
final isValid = await auth.check(authResponse.accessToken!);
print('Token valid: $isValid');
// 4. Refresh token
final refreshed = await auth.refresh(authResponse.refreshToken!);
print('New access token: ${refreshed.accessToken}');
// 5. Logout current device
await auth.logout(authResponse.accessToken!);
print('Logged out from this device');
// 6. Logout from all devices
await auth.logoutAll(user.getAuthIdentifier());
print('Logged out from all devices');
// 7. Logout from other devices (keep current)
await auth.logoutOthers(
user.getAuthIdentifier(),
authResponse.accessToken!,
);
print('Logged out from other devices');
}
}
| Method | Description | Returns |
|---|---|---|
| attempt(credentials) | Authenticate with credentials | AuthResponse |
| user(token) | Get user from token | Authenticatable |
| check(token) | Verify token validity | bool |
| refresh(refreshToken) | Refresh access token | AuthResponse |
| logout(token) | Invalidate single token | void |
| logoutAll(userId) | Invalidate all user tokens | void |
| logoutOthers(userId, token) | Invalidate other tokens | void |
Guard Best Practices
Follow these best practices when working with authentication guards in your application.
✅ Do:
- Use API Guard for REST APIs and mobile apps
- Use Web Guard for traditional web applications
- Implement token refresh to extend sessions
- Set appropriate token expiration times
- Use different guards for different user types
- Implement proper logout mechanisms
❌ Don't:
- Mix session and token auth in same guard
- Store tokens in localStorage (XSS risk)
- Use short-lived tokens without refresh
- Forget to invalidate tokens on logout
- Hard-code guard names in controllers
- Skip token expiration validation
