Authly is a robust Python framework for Authentication and User Token Handling built on FastAPI. It provides secure, scalable, and easy-to-integrate authentication services with features like JWT token management, user sessions, and secure password handling.
-
Secure Authentication
- JWT-based authentication with access and refresh tokens
- Password hashing with bcrypt
- Rate limiting and brute force protection
- Secure token storage and management
-
User Management
- User registration and verification
- Role-based access control (admin/user)
- User profile management
- Session management
-
Token Management
- Automatic token rotation
- Token invalidation and cleanup
- Refresh token handling
- Token blacklisting
-
Security Features
- Secure secret management
- CORS protection
- Security headers
- Rate limiting
-
Database Integration
- PostgreSQL support with psycopg
- Connection pooling
- Transaction management
- Vector support (pgvector)
pip install authly
Or with Poetry:
poetry add authly
- Create a new project and install dependencies:
poetry new myproject
cd myproject
poetry add authly
- Set up your environment variables:
# .env
JWT_SECRET_KEY="your-production-secret-here"
JWT_REFRESH_SECRET_KEY="your-refresh-secret-key-here"
JWT_ALGORITHM="HS256"
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=7
- Create your FastAPI application:
from fastapi import FastAPI
from authly import Authly, AuthlyConfig
from authly.config import EnvSecretProvider
from authly.api import auth_router, users_router
# Initialize configuration
secret_provider = EnvSecretProvider()
config = AuthlyConfig.load(secret_provider)
# Create database pool
pool = AsyncConnectionPool(
"postgresql://user:password@localhost:5432/db"
)
# Initialize Authly
authly = Authly.initialize(pool, config)
# Create FastAPI app
app = FastAPI()
# Include Authly routers
app.include_router(auth_router, prefix="/api/v1")
app.include_router(users_router, prefix="/api/v1")
Login and obtain access token.
Request Body:
{
"username": "string",
"password": "string",
"grant_type": "password"
}
Response:
{
"access_token": "string",
"refresh_token": "string",
"token_type": "Bearer",
"expires_in": 1800
}
Status Codes:
- 200: Successful login
- 400: Invalid request body
- 401: Invalid credentials
- 403: Account not verified/inactive
- 429: Too many requests
Refresh access token using refresh token.
Request Body:
{
"refresh_token": "string",
"grant_type": "refresh_token"
}
Response:
{
"access_token": "string",
"refresh_token": "string",
"token_type": "Bearer",
"expires_in": 1800
}
Status Codes:
- 200: Token refreshed successfully
- 400: Invalid refresh token
- 401: Invalid or expired refresh token
Logout and invalidate all active tokens.
Headers:
- Authorization: Bearer {access_token}
Response:
{
"message": "Successfully logged out",
"invalidated_tokens": 2
}
Status Codes:
- 200: Successful logout
- 401: Invalid token
- 500: Server error
Create a new user account.
Request Body:
{
"username": "string",
"email": "string",
"password": "string"
}
Response:
{
"id": "uuid",
"username": "string",
"email": "string",
"created_at": "datetime",
"updated_at": "datetime",
"last_login": "datetime",
"is_active": true,
"is_verified": false,
"is_admin": false
}
Status Codes:
- 201: User created successfully
- 400: Invalid request body or duplicate username/email
- 500: Server error
Get current user information.
Headers:
- Authorization: Bearer {access_token}
Response:
{
"id": "uuid",
"username": "string",
"email": "string",
"created_at": "datetime",
"updated_at": "datetime",
"last_login": "datetime",
"is_active": true,
"is_verified": true,
"is_admin": false
}
Status Codes:
- 200: Success
- 401: Not authenticated
- 403: Forbidden
Get user information by ID.
Headers:
- Authorization: Bearer {access_token}
Parameters:
- user_id: UUID of the user
Response:
{
"id": "uuid",
"username": "string",
"email": "string",
"created_at": "datetime",
"updated_at": "datetime",
"last_login": "datetime",
"is_active": true,
"is_verified": true,
"is_admin": false
}
Status Codes:
- 200: Success
- 404: User not found
- 401: Not authenticated
Update user information.
Headers:
- Authorization: Bearer {access_token}
Parameters:
- user_id: UUID of the user
Request Body:
{
"username": "string",
"email": "string",
"password": "string",
"is_active": true,
"is_verified": true,
"is_admin": false
}
Response:
{
"id": "uuid",
"username": "string",
"email": "string",
"created_at": "datetime",
"updated_at": "datetime",
"last_login": "datetime",
"is_active": true,
"is_verified": true,
"is_admin": false
}
Status Codes:
- 200: Success
- 400: Invalid request body
- 401: Not authenticated
- 403: Forbidden
- 404: User not found
Delete a user account.
Headers:
- Authorization: Bearer {access_token}
Parameters:
- user_id: UUID of the user
Status Codes:
- 204: Successfully deleted
- 401: Not authenticated
- 403: Forbidden
- 404: User not found
Verify a user account.
Headers:
- Authorization: Bearer {access_token}
Parameters:
- user_id: UUID of the user
Response:
{
"id": "uuid",
"username": "string",
"email": "string",
"created_at": "datetime",
"updated_at": "datetime",
"last_login": "datetime",
"is_active": true,
"is_verified": true,
"is_admin": false
}
Status Codes:
- 200: Successfully verified
- 401: Not authenticated
- 403: Forbidden
- 404: User not found
Authly can be configured through environment variables or configuration providers:
from authly.config import FileSecretProvider, StaticSecretProvider
# Using environment variables
provider = EnvSecretProvider()
# Using file-based secrets
provider = FileSecretProvider(Path("secrets.json"))
# Using static secrets (for testing)
provider = StaticSecretProvider(
secret_key="test-secret-key",
refresh_secret_key="test-refresh-key"
)
- Create the required database and user:
CREATE USER authly WITH PASSWORD 'your_password';
CREATE DATABASE authly_db;
GRANT ALL PRIVILEGES ON DATABASE authly_db TO authly;
- Run the initialization scripts:
-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Create tables
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
-- Additional fields...
);
CREATE TABLE tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_jti VARCHAR(64) NOT NULL UNIQUE,
-- Additional fields...
);
Authly uses bcrypt for secure password hashing with the following security measures:
- Automatic salt generation for each password
- Configurable work factor for future-proofing against hardware improvements
- Memory-hard hashing algorithm resistant to GPU/ASIC attacks
Example implementation:
from authly.auth import get_password_hash, verify_password
# Hash password with bcrypt
hashed = get_password_hash("user_password")
# Verify password - timing-safe comparison
is_valid = verify_password("user_password", hashed)
This guide shows how to generate bcrypt password hashes using Terminal commands, similar to what authentication libraries do under the hood.
htpasswd -nbB username password
This generates a bcrypt hash in the format: username:$2y$05$...
If htpasswd is not available:
brew install apache2
htpasswd -nbBC 12 username password
For reference, this is equivalent to:
import bcrypt
bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))
The output hash follows this structure:
$2y$
- bcrypt algorithm identifierXX$
- cost factor (05 for htpasswd default, 12 for Python default)- Rest of string - salt and hash combined
- Default cost factor in htpasswd is 5
- Default cost factor in Python's bcrypt is 12
- Both use the same underlying Blowfish-based bcrypt algorithm
- Higher cost factors are more secure but slower to compute
Authly implements a comprehensive token security system:
- Short-lived access tokens (configurable, default 30 minutes)
- Separate refresh tokens with longer lifetime
- JTI (JWT ID) claim for token revocation
- Token payload encryption for sensitive data
- Automatic token rotation on refresh
# Token creation with JTI
access_token = create_access_token(
data={"sub": user_id, "jti": token_jti},
secret_key=config.secret_key,
algorithm="HS256",
expires_delta=30 # minutes
)
# Token validation
try:
payload = decode_token(token, secret_key, algorithm="HS256")
is_valid = await token_service.is_token_valid(payload["jti"])
except JWTError:
raise InvalidToken()
- Automatic cleanup of expired tokens
- Token blacklisting for immediate revocation
- Database-backed token storage for persistence
- Transaction-safe token operations
Comprehensive protection against automated attacks:
from authly.api.rate_limiter import RateLimiter
# Configure rate limits
limiter = RateLimiter(
max_requests=5, # Maximum requests
window_seconds=60 # Time window
)
# Usage in endpoint
async def login_endpoint():
await limiter.check_rate_limit(f"login:{username}")
- Progressive delays on failed attempts
- Account lockout after multiple failures
- IP-based rate limiting
- User agent tracking
- Geographic location monitoring (optional)
Robust session handling features:
- Secure session creation and validation
- Session timeout management
- Concurrent session control
- Forced logout capabilities
- Session activity tracking
Secure database operations:
- Prepared statements for SQL injection prevention
- Connection pooling with SSL/TLS
- Transaction isolation
- Automatic connection encryption
- Least privilege database users
Comprehensive security headers implementation:
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response = await call_next(request)
response.headers.update({
"X-Content-Type-Options": "nosniff",
"Strict-Transport-Security": "max-age=31536000",
"X-Frame-Options": "DENY",
"Content-Security-Policy": "default-src 'self'",
"X-XSS-Protection": "1; mode=block",
"Referrer-Policy": "strict-origin-when-cross-origin"
})
return response
Secure handling of sensitive configuration:
- Encrypted secret storage
- Automatic key rotation
- Secure memory wiping
- Hardware security module (HSM) support
- Environment variable protection
Configurable CORS policy:
app.add_middleware(
CORSMiddleware,
allow_origins=["https://trusted-domain.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Additional security measures:
-
Input Validation
- Strict type checking
- Schema validation
- Content length limits
- Character encoding validation
-
Output Encoding
- HTML escaping
- JSON encoding
- CSV injection prevention
- File name sanitization
-
Error Handling
- Generic error messages
- No stack traces in production
- Structured error logging
- Security event auditing
-
Secure Development
- Regular dependency updates
- Security scanning integration
- Code review requirements
- Security testing automation
Run the test suite:
pytest
Run specific tests:
pytest tests/test_auth.py -v
pytest tests/test_users.py -v
Run the API test script:
./api-test.sh
- Clone the repository:
git clone https://github.com/yourusername/authly.git
cd authly
- Install development dependencies:
poetry install
- Run linting:
poetry run flake8
poetry run black .
poetry run isort .
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Create a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
For support, please open an issue in the GitHub repository or contact the maintainers.
- FastAPI
- PostgreSQL
- Python-Jose
- Bcrypt
- Psycopg