aim_server_jwt 0.0.1
aim_server_jwt: ^0.0.1 copied to clipboard
JWT authentication middleware for Aim framework. Provides stateless authentication with HS256 support, standard claims validation, and Bearer token verification.
aim_server_jwt #
JWT (JSON Web Token) authentication middleware for the Aim framework.
Overview #
aim_server_jwt provides JWT authentication and authorization middleware for the Aim framework. This package enables stateless authentication using JSON Web Tokens with full support for standard JWT claims and secure token verification.
Features #
- π JWT Verification - Automatic Bearer token validation middleware
- π« Token Generation - Easy-to-use token creation with standard claims
- π Secure by Default - Enforces minimum 32-character secret keys (RFC 7518)
- π Standard Claims - Full support for iss, sub, aud, exp, iat, nbf claims
- π‘οΈ Custom Claims - Add any custom payload data to tokens
- β‘ Path Exclusion - Skip authentication for specific routes (e.g., /login)
- π― Type-safe - Sealed class design with compile-time safety
- π HMAC Algorithms - HS256 support (RS256 and ECDSA coming soon)
Installation #
Add aim_server_jwt to your pubspec.yaml:
dependencies:
aim_server: ^0.0.6
aim_server_jwt: ^0.0.1
Then run:
dart pub get
Usage #
Basic Authentication #
import 'dart:io';
import 'package:aim_server/aim_server.dart';
import 'package:aim_server_jwt/aim_server_jwt.dart';
void main() {
final app = Aim<JwtEnv>(
envFactory: () => JwtEnv.create(
JwtOptions(
algorithm: HS256(
secretKey: SecretKey(secret: 'your-secret-key-at-least-32-chars'),
),
excludedPaths: ['/login'],
),
),
);
// Apply JWT middleware
app.use(jwt());
// Login endpoint (excluded from auth)
app.post('/login', (c) async {
// Validate credentials...
final token = Jwt(options: c.variables.jwtOptions).sign({
'user_id': 123,
'role': 'admin',
});
return c.json({'token': token});
});
// Protected endpoint
app.get('/profile', (c) async {
final payload = c.variables.jwtPayload;
return c.json({
'user_id': payload['user_id'],
'role': payload['role'],
});
});
app.serve(host: InternetAddress.anyIPv4, port: 8080);
}
Token Generation with Standard Claims #
final options = JwtOptions(
algorithm: HS256(
secretKey: SecretKey(secret: 'your-secret-key-at-least-32-chars'),
),
issuer: 'my-app',
audience: 'api.example.com',
expiration: Duration(hours: 24),
);
final jwt = Jwt(options: options);
final token = jwt.sign({
'user_id': 123,
'username': 'alice',
'permissions': ['read', 'write'],
});
Token Verification #
try {
final claims = jwt.verify(token);
print('User ID: ${claims['user_id']}');
print('Username: ${claims['username']}');
} on JwtException catch (e) {
print('Invalid token: ${e.message}');
}
Using Static Method #
final token = Jwt.createToken(
payload: {'user_id': 123, 'role': 'admin'},
options: options,
);
Path Exclusion #
Exclude specific paths from authentication:
final options = JwtOptions(
algorithm: HS256(
secretKey: SecretKey(secret: 'your-secret-key-at-least-32-chars'),
),
excludedPaths: ['/login', '/register', '/public'],
);
Complete Example with All Claims #
import 'dart:io';
import 'package:aim_server/aim_server.dart';
import 'package:aim_server_jwt/aim_server_jwt.dart';
void main() {
final jwtOptions = JwtOptions(
algorithm: HS256(
secretKey: SecretKey(secret: 'super-secret-key-change-in-production'),
),
issuer: 'my-app',
audience: 'api.example.com',
expiration: Duration(hours: 24),
notBefore: Duration(seconds: 0),
excludedPaths: ['/login', '/health'],
);
final app = Aim<JwtEnv>(
envFactory: () => JwtEnv.create(jwtOptions),
);
app.use(jwt());
// Health check (excluded)
app.get('/health', (c) async => c.text('OK'));
// Login (excluded)
app.post('/login', (c) async {
final body = await c.req.json();
// Validate credentials...
if (body['username'] == 'admin' && body['password'] == 'secret') {
final jwt = Jwt(options: c.variables.jwtOptions);
final token = jwt.sign({
'user_id': 1,
'username': body['username'],
'role': 'admin',
'permissions': ['read', 'write', 'delete'],
});
return c.json({'token': token});
}
return c.json({'error': 'Invalid credentials'}, statusCode: 401);
});
// Protected routes
app.get('/profile', (c) async {
final payload = c.variables.jwtPayload;
return c.json({
'user_id': payload['user_id'],
'username': payload['username'],
'role': payload['role'],
});
});
app.get('/admin', (c) async {
final payload = c.variables.jwtPayload;
if (payload['role'] != 'admin') {
return c.json({'error': 'Forbidden'}, statusCode: 403);
}
return c.json({'message': 'Admin access granted'});
});
app.serve(host: InternetAddress.anyIPv4, port: 8080);
print('Server running on http://localhost:8080');
}
Supported Algorithms #
Currently Supported #
- β HS256 (HMAC-SHA256) - Symmetric key algorithm
Coming Soon #
- π RS256 (RSA-SHA256) - Asymmetric key algorithm for microservices
- π ES256 (ECDSA-SHA256) - Elliptic curve algorithm
Security Best Practices #
-
Secret Key Length: Always use secrets of at least 32 characters for HS256
// β Good SecretKey(secret: 'super-secret-key-at-least-32-chars') // β Bad - Will throw error SecretKey(secret: 'short') -
Token Expiration: Always set an expiration time
JwtOptions( algorithm: HS256(...), expiration: Duration(hours: 24), // Recommended ) -
HTTPS Only: Use JWT authentication only over HTTPS in production
-
Secure Storage: Never store secrets in code - use environment variables
final secret = Platform.environment['JWT_SECRET'] ?? throw Exception('JWT_SECRET not set'); -
Excluded Paths: Be explicit about which paths don't require authentication
How It Works #
- Client Login: User sends credentials to
/login - Token Generation: Server validates credentials and creates a JWT token
- Client Storage: Client stores token (e.g., in localStorage)
- Authenticated Requests: Client sends token in
Authorization: Bearer <token>header - Middleware Verification: JWT middleware verifies signature and claims
- Access Granted: Valid token allows access to protected resources
Client Server
| |
|------ POST /login ----->|
| (credentials) | Validate credentials
| | Generate JWT
|<----- { token } --------|
| |
| Store token |
| |
|------ GET /profile ---->|
| Authorization: Bearer | Verify JWT
| <token> | Extract claims
| |
|<----- { user data } ----|
Error Handling #
The middleware returns 401 Unauthorized in these cases:
- Missing Authorization header
- Invalid Bearer token format
- Invalid signature
- Expired token (exp claim)
- Token not yet valid (nbf claim)
- Issuer mismatch
- Audience mismatch
app.use(jwt());
app.get('/protected', (c) async {
// If we reach here, token is valid
final payload = c.variables.jwtPayload;
return c.json({'user_id': payload['user_id']});
});
Contributing #
Contributions are welcome! Please see the main repository for contribution guidelines.
License #
See the LICENSE file in the main repository.