8.7 KiB
Authentication & Authorization
Overview
Attune uses JWT (JSON Web Token) based authentication for securing API endpoints. The authentication system supports user registration, login, token refresh, and password management.
Architecture
Components
-
JWT Tokens
- Access Tokens: Short-lived tokens (default: 1 hour) used for API authentication
- Refresh Tokens: Long-lived tokens (default: 7 days) used to obtain new access tokens
-
Password Security
- Passwords are hashed using Argon2id (industry-standard, memory-hard algorithm)
- Password hashes are stored in the
attributesJSONB field of theidentitytable - Minimum password length: 8 characters
-
Middleware
require_auth: Middleware function that validates JWT tokens on protected routesRequireAuth: Extractor for accessing authenticated user information in handlers
Configuration
Authentication is configured via environment variables:
# JWT Secret Key (REQUIRED in production)
JWT_SECRET=your-secret-key-here
# Token Expiration (in seconds)
JWT_ACCESS_EXPIRATION=3600 # 1 hour (default)
JWT_REFRESH_EXPIRATION=604800 # 7 days (default)
Security Warning: Always set a strong, random JWT_SECRET in production. The default value is insecure and should only be used for development.
API Endpoints
Public Endpoints (No Authentication Required)
Register a New User
POST /auth/register
Content-Type: application/json
{
"login": "username",
"password": "securepassword123",
"display_name": "John Doe" // optional
}
Response:
{
"data": {
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600
}
}
Login
POST /auth/login
Content-Type: application/json
{
"login": "username",
"password": "securepassword123"
}
Response:
{
"data": {
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600
}
}
Refresh Access Token
POST /auth/refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGc..."
}
Response:
{
"data": {
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600
}
}
Protected Endpoints (Authentication Required)
All protected endpoints require an Authorization header with a valid access token:
Authorization: Bearer <access_token>
Get Current User
GET /auth/me
Authorization: Bearer eyJhbGc...
Response:
{
"data": {
"id": 1,
"login": "username",
"display_name": "John Doe"
}
}
Change Password
POST /auth/change-password
Authorization: Bearer eyJhbGc...
Content-Type: application/json
{
"current_password": "oldpassword123",
"new_password": "newpassword456"
}
Response:
{
"data": {
"success": true,
"message": "Password changed successfully"
}
}
Error Responses
Authentication errors return appropriate HTTP status codes:
- 400 Bad Request: Invalid request format or validation errors
- 401 Unauthorized: Missing, invalid, or expired token; invalid credentials
- 403 Forbidden: Insufficient permissions (future RBAC implementation)
- 409 Conflict: Username already exists during registration
Example error response:
{
"error": "Invalid authentication token",
"code": "UNAUTHORIZED"
}
Usage in Route Handlers
Protecting Routes
Add the authentication middleware to routes that require authentication:
use crate::auth::middleware::RequireAuth;
async fn protected_handler(
RequireAuth(user): RequireAuth,
) -> Result<Json<ApiResponse<MyData>>, ApiError> {
let identity_id = user.identity_id()?;
let login = user.login();
// Your handler logic here
Ok(Json(ApiResponse::new(data)))
}
Accessing User Information
The RequireAuth extractor provides access to the authenticated user's claims:
pub struct AuthenticatedUser {
pub claims: Claims,
}
impl AuthenticatedUser {
pub fn identity_id(&self) -> Result<i64, ParseIntError>
pub fn login(&self) -> &str
}
Database Schema
Identity Table
The identity table stores user authentication information:
CREATE TABLE attune.identity (
id BIGSERIAL PRIMARY KEY,
login TEXT NOT NULL UNIQUE,
display_name TEXT,
attributes JSONB NOT NULL DEFAULT '{}'::jsonb,
password_hash TEXT, -- Added in migration 20240102000001
created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Note: The password_hash column is optional to support:
- External authentication providers (OAuth, SAML, etc.)
- Service accounts that don't use password authentication
- API key-based authentication (future implementation)
Security Best Practices
-
JWT Secret
- Use a strong, random secret (minimum 256 bits)
- Never commit secrets to version control
- Rotate secrets periodically in production
-
Token Storage (Client-Side)
- Store tokens securely (e.g., httpOnly cookies or secure storage)
- Never expose tokens in URLs or localStorage (if using web clients)
- Clear tokens on logout
-
Password Requirements
- Minimum 8 characters (enforced by validation)
- Consider implementing additional requirements (uppercase, numbers, symbols)
- Implement rate limiting on login attempts (future enhancement)
-
HTTPS
- Always use HTTPS in production to protect tokens in transit
- Configure proper TLS/SSL certificates
-
Token Expiration
- Keep access tokens short-lived (1 hour recommended)
- Use refresh tokens for long-lived sessions
- Implement token revocation for logout (future enhancement)
Future Enhancements
Planned Features
-
Role-Based Access Control (RBAC)
- Permission set assignments
- Fine-grained authorization middleware
- Resource-level permissions
-
Multi-Factor Authentication (MFA)
- TOTP support
- SMS/Email verification codes
-
OAuth/OIDC Integration
- Support for external identity providers
- Single Sign-On (SSO)
-
Token Revocation
- Blacklist/whitelist mechanisms
- Force logout functionality
-
Account Security
- Password reset via email
- Account lockout after failed attempts
- Security audit logs
-
API Keys
- Service-to-service authentication
- Scoped API keys for automation
Testing
Manual Testing with cURL
# Register a new user
curl -X POST http://localhost:8080/auth/register \
-H "Content-Type: application/json" \
-d '{
"login": "testuser",
"password": "testpass123",
"display_name": "Test User"
}'
# Login
curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{
"login": "testuser",
"password": "testpass123"
}'
# Get current user (replace TOKEN with actual access token)
curl http://localhost:8080/auth/me \
-H "Authorization: Bearer TOKEN"
# Change password
curl -X POST http://localhost:8080/auth/change-password \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{
"current_password": "testpass123",
"new_password": "newpass456"
}'
# Refresh token
curl -X POST http://localhost:8080/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "REFRESH_TOKEN"
}'
Unit Tests
Password hashing and JWT utilities include comprehensive unit tests:
# Run auth-related tests
cargo test --package attune-api password
cargo test --package attune-api jwt
cargo test --package attune-api middleware
Troubleshooting
Common Issues
-
"Missing authentication token"
- Ensure you're including the
Authorizationheader - Verify the header format:
Bearer <token>
- Ensure you're including the
-
"Authentication token expired"
- Use the refresh token endpoint to get a new access token
- Check token expiration configuration
-
"Invalid login or password"
- Verify credentials are correct
- Check if the identity has a password set (some accounts may use external auth)
-
"JWT_SECRET not set" warning
- Set the
JWT_SECRETenvironment variable before starting the server - Use a strong, random value in production
- Set the
Debug Logging
Enable debug logging to troubleshoot authentication issues:
RUST_LOG=attune_api=debug cargo run --bin attune-api