Authentication Generator
Generate a complete, production-ready authentication system with support for JWT, sessions, OAuth, and advanced security features including RBAC (Role-Based Access Control).
Synopsis
azu generate auth [options]Description
The auth generator creates a comprehensive authentication system for your Azu application, including user models, authentication endpoints, security middleware, and database migrations. It supports multiple authentication strategies and can be configured with advanced features like role-based access control, CSRF protection, and OAuth integration.
Features
Core Authentication
✅ User Registration: Secure user signup with email verification
✅ Login/Logout: Session or token-based authentication
✅ Password Management: Secure password hashing with BCrypt (cost: 14)
✅ Password Reset: Email-based password recovery
✅ Email Confirmation: Account verification workflow
✅ Account Locking: Automatic lockout after failed attempts
Security Features
🔐 BCrypt Password Hashing: Industry-standard with high cost factor
🎫 JWT Support: Secure token generation with refresh tokens
🛡️ CSRF Protection: Cross-site request forgery prevention
🔒 Security Headers: Comprehensive HTTP security headers
🚫 Rate Limiting: Protection against brute force attacks
🔑 Two-Factor Authentication: Optional 2FA support (infrastructure ready)
Advanced Features
👥 RBAC: Complete role-based access control system
🌐 OAuth Integration: Google and GitHub authentication
⏰ Token Expiration: Configurable access and refresh tokens
📊 Audit Trail: Track login attempts and security events
🎯 Permission System: Granular permission management
Usage
Basic Usage
Generate authentication with default settings (Authly strategy with RBAC):
azu generate authThis creates a complete authentication system with:
User model with security features
Authentication endpoints (register, login, logout)
JWT token management
RBAC tables and structure
Security middleware
Strategy-Specific Generation
Authly (Recommended)
Full-featured OAuth2 server with JWT and RBAC:
azu generate auth --strategy authlyFeatures:
OAuth2 server capabilities
JWT access and refresh tokens
RBAC and permissions
OAuth provider integration
Comprehensive security
JWT Only
Simple JWT-based authentication:
azu generate auth --strategy jwtFeatures:
JWT access tokens
Refresh token support
User model with basic auth
Login/register endpoints
Session-Based
Traditional session authentication:
azu generate auth --strategy sessionFeatures:
Server-side sessions
Cookie-based auth
Session store integration
OAuth
External OAuth provider authentication:
azu generate auth --strategy oauthFeatures:
Google OAuth
GitHub OAuth
Extensible provider support
Advanced Options
# Full-featured auth with all options
azu generate auth \
--strategy authly \
--user-model CustomUser \
--enable-rbac \
--enable-csrf \
--oauth-providers google,github,facebook
# Simple JWT auth without RBAC
azu generate auth \
--strategy jwt \
--no-rbac \
--no-csrf
# Custom user model
azu generate auth \
--user-model Account \
--strategy sessionOptions
--strategy <type>
string
authly
Authentication strategy: authly, jwt, session, oauth
--user-model <name>
string
User
Name of the user model class
--enable-rbac
flag
true
Enable role-based access control
--no-rbac
flag
Disable RBAC features
--enable-csrf
flag
true
Enable CSRF protection
--no-csrf
flag
Disable CSRF protection
--oauth-providers <list>
comma-separated
google,github
OAuth providers to enable
--force
flag
false
Overwrite existing files
Generated Files
Directory Structure
project/
├── src/
│ ├── models/
│ │ └── user.cr # User model with auth
│ ├── endpoints/
│ │ └── auth_endpoint.cr # Authentication endpoints
│ ├── requests/
│ │ └── auth/
│ │ ├── register_request.cr # Registration validation
│ │ ├── login_request.cr # Login validation
│ │ ├── refresh_token_request.cr # Token refresh
│ │ └── change_password_request.cr # Password change
│ ├── middleware/
│ │ ├── csrf_protection.cr # CSRF middleware
│ │ └── security_headers.cr # Security headers
│ └── config/
│ └── authly.cr # Authly configuration
├── db/
│ ├── migrations/
│ │ └── TIMESTAMP_create_auth_tables.cr # Auth migrations
│ └── seed_rbac.cr # RBAC seed data
└── .env.example # Environment variables templateFile Descriptions
User Model (src/models/user.cr)
src/models/user.cr)Complete user model with:
class User < CQL::Model
db_table "users"
getter id : Int64
getter email : String
getter password_hash : String
getter name : String?
getter role : String
getter confirmed_at : Time?
getter locked_at : Time?
getter failed_login_attempts : Int32
getter last_login_at : Time?
getter password_changed_at : Time?
getter two_factor_enabled : Bool
getter two_factor_secret : String?
getter created_at : Time
getter updated_at : Time
# Associations
has_many :user_roles
has_many :roles, through: :user_roles
# Authentication methods
def hash_password(password : String) : String
def verify_password(password : String) : Bool
def generate_token : String
def locked? : Bool
def confirmed? : Bool
def has_role?(role : String) : Bool
def can?(permission : String) : Bool
endAuthentication Endpoints (src/endpoints/auth_endpoint.cr)
src/endpoints/auth_endpoint.cr)RESTful authentication API:
POST /auth/register- User registrationPOST /auth/login- User loginPOST /auth/logout- User logoutPOST /auth/refresh- Refresh access tokenPOST /auth/forgot-password- Request password resetPOST /auth/reset-password- Reset password with tokenPOST /auth/change-password- Change authenticated user passwordGET /auth/confirm/:token- Confirm email addressPOST /auth/resend-confirmation- Resend confirmation email
CSRF Protection Middleware (src/middleware/csrf_protection.cr)
src/middleware/csrf_protection.cr)Protects against cross-site request forgery:
class CSRFProtection
include Azu::Middleware
def call(context : HTTP::Server::Context)
return call_next(context) if safe_method?(context.request.method)
token = get_token(context)
stored_token = context.session["csrf_token"]
if token.nil? || token != stored_token
context.response.status = HTTP::Status::FORBIDDEN
return context.response.print("CSRF token validation failed")
end
call_next(context)
end
endSecurity Headers Middleware (src/middleware/security_headers.cr)
src/middleware/security_headers.cr)Comprehensive HTTP security headers:
class SecurityHeaders
include Azu::Middleware
def call(context : HTTP::Server::Context)
context.response.headers["X-Frame-Options"] = "DENY"
context.response.headers["X-Content-Type-Options"] = "nosniff"
context.response.headers["X-XSS-Protection"] = "1; mode=block"
context.response.headers["Strict-Transport-Security"] = "max-age=31536000"
context.response.headers["Content-Security-Policy"] = csp_policy
call_next(context)
end
endDatabase Schema
Users Table
Core user authentication table:
id
Int64
Primary key
email
String
Unique email address
password_hash
String
BCrypt password hash
name
String
User's full name
role
String
User role (default: "user")
confirmed_at
Time?
Email confirmation timestamp
locked_at
Time?
Account lock timestamp
failed_login_attempts
Int32
Failed login counter
last_login_at
Time?
Last successful login
password_changed_at
Time?
Password change timestamp
two_factor_enabled
Bool
2FA enabled flag
two_factor_secret
String?
2FA secret key
recovery_codes
String?
Backup recovery codes
created_at
Time
Creation timestamp
updated_at
Time
Last update timestamp
RBAC Tables (when --enable-rbac)
--enable-rbac)Roles Table
id
Int64
Primary key
name
String
Role name (unique)
description
String
Role description
permissions
String
JSON permissions array
created_at
Time
Creation timestamp
updated_at
Time
Last update timestamp
Permissions Table
id
Int64
Primary key
name
String
Permission name (unique)
description
String
Permission description
resource
String
Resource type
action
String
Action type
created_at
Time
Creation timestamp
User_Roles Junction Table
id
Int64
Primary key
user_id
Int64
Foreign key to users
role_id
Int64
Foreign key to roles
assigned_at
Time
Assignment timestamp
assigned_by
Int64?
Assigning user ID
Role_Permissions Junction Table
id
Int64
Primary key
role_id
Int64
Foreign key to roles
permission_id
Int64
Foreign key to permissions
created_at
Time
Creation timestamp
OAuth Tables (when --strategy authly)
--strategy authly)OAuth_Applications Table
id
Int64
Primary key
name
String
Application name
client_id
String
Unique client ID
client_secret
String
Client secret
redirect_uri
String
OAuth redirect URI
scopes
String
Allowed scopes
confidential
Bool
Confidential flag
created_at
Time
Creation timestamp
updated_at
Time
Last update timestamp
OAuth_Access_Tokens Table
id
Int64
Primary key
application_id
Int64
Foreign key to applications
resource_owner_id
Int64
User ID
token
String
Access token (unique)
refresh_token
String?
Refresh token (unique)
expires_in
Int32
Token lifetime (seconds)
scopes
String
Granted scopes
created_at
Time
Creation timestamp
revoked_at
Time?
Revocation timestamp
Configuration
Environment Variables
Add to .env:
# JWT Configuration
JWT_SECRET=your-secret-key-here
JWT_REFRESH_SECRET=your-refresh-secret-here
JWT_ISSUER=your-app-name-api
JWT_AUDIENCE=your-app-name-client
# OAuth Configuration (if using Authly)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# Email Configuration (for confirmations)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=your-username
SMTP_PASSWORD=your-password
FROM_EMAIL=noreply@yourapp.com
# Security
CSRF_SECRET=your-csrf-secret
SESSION_SECRET=your-session-secret
# Account Security
MAX_LOGIN_ATTEMPTS=5
LOCKOUT_DURATION=30m
PASSWORD_MIN_LENGTH=8
REQUIRE_EMAIL_CONFIRMATION=trueAuthly Configuration (src/config/authly.cr)
src/config/authly.cr)Authly.configure do |config|
# Token lifetimes
config.access_token_lifetime = 15.minutes
config.refresh_token_lifetime = 7.days
# OAuth providers
config.oauth_providers = {
"google" => {
"client_id" => ENV["GOOGLE_CLIENT_ID"],
"client_secret" => ENV["GOOGLE_CLIENT_SECRET"],
"redirect_uri" => "#{ENV["APP_URL"]}/auth/google/callback"
},
"github" => {
"client_id" => ENV["GITHUB_CLIENT_ID"],
"client_secret" => ENV["GITHUB_CLIENT_SECRET"],
"redirect_uri" => "#{ENV["APP_URL"]}/auth/github/callback"
}
}
# Security settings
config.bcrypt_cost = 14
config.max_login_attempts = 5
config.lockout_duration = 30.minutes
config.require_email_confirmation = true
endUsage Examples
User Registration
# Register new user
POST /auth/register
{
"email": "user@example.com",
"password": "SecurePassword123!",
"name": "John Doe"
}
# Response
{
"user": {
"id": 1,
"email": "user@example.com",
"name": "John Doe",
"role": "user",
"created_at": "2024-01-01T00:00:00Z"
},
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc...",
"expires_in": 900
}User Login
POST /auth/login
{
"email": "user@example.com",
"password": "SecurePassword123!"
}
# Response
{
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc...",
"expires_in": 900,
"user": {
"id": 1,
"email": "user@example.com",
"name": "John Doe",
"role": "user"
}
}Token Refresh
POST /auth/refresh
{
"refresh_token": "eyJhbGc..."
}
# Response
{
"access_token": "eyJhbGc...",
"expires_in": 900
}Password Reset
# Request reset
POST /auth/forgot-password
{
"email": "user@example.com"
}
# Reset with token
POST /auth/reset-password
{
"token": "reset-token-from-email",
"password": "NewSecurePassword123!"
}Using Authentication in Endpoints
class ProtectedEndpoint
include Azu::Endpoint(EmptyContract, JsonResponse)
def call
# Check if user is authenticated
unless current_user
return unauthorized("Authentication required")
end
# Check role
unless current_user.has_role?("admin")
return forbidden("Admin access required")
end
# Check specific permission
unless current_user.can?("posts:delete")
return forbidden("Insufficient permissions")
end
# Proceed with protected logic
ok({ message: "Access granted" })
end
private def current_user
@current_user ||= authenticate_from_token(request)
end
endRBAC Usage
Seeding Roles and Permissions
# db/seed_rbac.cr
require "../src/models/**"
# Create permissions
read_posts = Permission.create!(
name: "posts:read",
description: "Read posts",
resource: "posts",
action: "read"
)
create_posts = Permission.create!(
name: "posts:create",
description: "Create posts",
resource: "posts",
action: "create"
)
delete_posts = Permission.create!(
name: "posts:delete",
description: "Delete posts",
resource: "posts",
action: "delete"
)
# Create roles
user_role = Role.create!(
name: "user",
description: "Regular user",
permissions: [read_posts.id]
)
editor_role = Role.create!(
name: "editor",
description: "Content editor",
permissions: [read_posts.id, create_posts.id]
)
admin_role = Role.create!(
name: "admin",
description: "Administrator",
permissions: [read_posts.id, create_posts.id, delete_posts.id]
)
# Assign role to user
user = User.find!(1)
user.add_role(admin_role)Checking Permissions
# Check if user has specific role
if user.has_role?("admin")
# Allow admin actions
end
# Check if user has specific permission
if user.can?("posts:delete")
# Allow post deletion
end
# Get all user permissions
permissions = user.permissions # => ["posts:read", "posts:create", "posts:delete"]
# Get all user roles
roles = user.roles # => [Role<admin>]Security Best Practices
1. Strong Secrets
Generate cryptographically secure secrets:
# JWT secrets
openssl rand -hex 64
# Session secrets
openssl rand -base64 322. Password Requirements
Enforce strong passwords in validation:
class RegisterRequest
include Azu::Contract
field email : String
field password : String
validates :password, length: {minimum: 8, maximum: 72}
validates :password, format: {
with: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
message: "must include uppercase, lowercase, number, and special character"
}
end3. Rate Limiting
Implement rate limiting on auth endpoints:
class RateLimitMiddleware
def call(context : HTTP::Server::Context)
if auth_endpoint?(context.request.path)
check_rate_limit(context)
end
call_next(context)
end
end4. Secure Token Storage
Client-side token storage recommendations:
Web: Use httpOnly, secure cookies for refresh tokens
SPA: Store access tokens in memory, refresh tokens in httpOnly cookies
Mobile: Use secure keychain/keystore
5. Token Rotation
Rotate refresh tokens on use:
def refresh_token(old_token : String)
# Verify old token
payload = verify_refresh_token(old_token)
return nil unless payload
# Revoke old token
revoke_token(old_token)
# Generate new tokens
user_id = payload["sub"].as_i64
{
access_token: generate_token(user_id),
refresh_token: generate_refresh_token(user_id)
}
endDependencies
Add to shard.yml:
dependencies:
# Core auth
crypto:
github: crystal-lang/crypto
jwt:
github: crystal-community/jwt
bcrypt:
github: crystal-community/bcrypt
# Authly (if using authly strategy)
authly:
github: azutoolkit/authly
# Email (for confirmations)
carbon:
github: luckyframework/carbonMigration and Setup
After generation:
# 1. Install dependencies
shards install
# 2. Run migrations
azu db:migrate
# 3. Seed RBAC data (if enabled)
crystal run db/seed_rbac.cr
# 4. Set environment variables
cp .env.example .env
# Edit .env with your values
# 5. Start application
azu serveTesting
Example authentication tests:
require "../spec_helper"
describe "Authentication" do
describe "POST /auth/register" do
it "registers new user" do
response = post("/auth/register", {
email: "test@example.com",
password: "SecurePassword123!",
name: "Test User"
})
response.status.should eq(201)
json = JSON.parse(response.body)
json["user"]["email"].should eq("test@example.com")
json["access_token"].should_not be_nil
end
it "rejects weak password" do
response = post("/auth/register", {
email: "test@example.com",
password: "weak"
})
response.status.should eq(422)
end
end
describe "POST /auth/login" do
it "authenticates valid credentials" do
user = create_user(email: "test@example.com", password: "SecurePass123!")
response = post("/auth/login", {
email: "test@example.com",
password: "SecurePass123!"
})
response.status.should eq(200)
json = JSON.parse(response.body)
json["access_token"].should_not be_nil
end
it "locks account after failed attempts" do
user = create_user(email: "test@example.com", password: "correct")
# Make 5 failed attempts
5.times do
post("/auth/login", {email: "test@example.com", password: "wrong"})
end
# Account should be locked
response = post("/auth/login", {
email: "test@example.com",
password: "correct"
})
response.status.should eq(423) # Locked
end
end
endTroubleshooting
JWT Token Issues
Problem: "JWT_SECRET environment variable not set"
Solution:
export JWT_SECRET=$(openssl rand -hex 64)
export JWT_REFRESH_SECRET=$(openssl rand -hex 64)BCrypt Cost Too High
Problem: Login is slow
Solution: Adjust BCrypt cost (default: 14):
# In user model
Crypto::Bcrypt::Password.create(password, cost: 12)CSRF Token Mismatch
Problem: "CSRF token validation failed"
Solution:
Ensure CSRF token is included in requests
Check token matches session value
Verify middleware order
Account Lockout
Problem: Can't login after failed attempts
Solution:
# Manually unlock user
user = User.find_by_email("locked@example.com")
user.update(locked_at: nil, failed_login_attempts: 0)Related Documentation
See Also
azu session:setup- Configure session storageazu generate model- Generate modelsazu db:migrate- Run migrations
Last updated