Files
attune/docs/authentication/authentication.md
2026-02-04 17:46:30 -06:00

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

  1. 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
  2. Password Security

    • Passwords are hashed using Argon2id (industry-standard, memory-hard algorithm)
    • Password hashes are stored in the attributes JSONB field of the identity table
    • Minimum password length: 8 characters
  3. Middleware

    • require_auth: Middleware function that validates JWT tokens on protected routes
    • RequireAuth: 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

  1. JWT Secret

    • Use a strong, random secret (minimum 256 bits)
    • Never commit secrets to version control
    • Rotate secrets periodically in production
  2. 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
  3. Password Requirements

    • Minimum 8 characters (enforced by validation)
    • Consider implementing additional requirements (uppercase, numbers, symbols)
    • Implement rate limiting on login attempts (future enhancement)
  4. HTTPS

    • Always use HTTPS in production to protect tokens in transit
    • Configure proper TLS/SSL certificates
  5. 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

  1. Role-Based Access Control (RBAC)

    • Permission set assignments
    • Fine-grained authorization middleware
    • Resource-level permissions
  2. Multi-Factor Authentication (MFA)

    • TOTP support
    • SMS/Email verification codes
  3. OAuth/OIDC Integration

    • Support for external identity providers
    • Single Sign-On (SSO)
  4. Token Revocation

    • Blacklist/whitelist mechanisms
    • Force logout functionality
  5. Account Security

    • Password reset via email
    • Account lockout after failed attempts
    • Security audit logs
  6. 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

  1. "Missing authentication token"

    • Ensure you're including the Authorization header
    • Verify the header format: Bearer <token>
  2. "Authentication token expired"

    • Use the refresh token endpoint to get a new access token
    • Check token expiration configuration
  3. "Invalid login or password"

    • Verify credentials are correct
    • Check if the identity has a password set (some accounts may use external auth)
  4. "JWT_SECRET not set" warning

    • Set the JWT_SECRET environment variable before starting the server
    • Use a strong, random value in production

Debug Logging

Enable debug logging to troubleshoot authentication issues:

RUST_LOG=attune_api=debug cargo run --bin attune-api

References