learning nginx auth_request mixed with fastapi
Find a file
2025-11-21 13:21:33 -06:00
site wip 2025-11-21 13:09:03 -06:00
.gitignore README 2025-11-21 13:21:33 -06:00
justfile wip 2025-11-21 13:13:57 -06:00
main_auth.py wip 2025-11-21 13:09:03 -06:00
nginx.conf wip 2025-11-21 13:09:03 -06:00
README.md README 2025-11-21 13:21:33 -06:00

nginx auth_request Learning Demo

A hands-on demonstration of nginx's auth_request module with JWT-based authentication. This project helps you understand how to implement secure authentication and authorization using nginx as a reverse proxy with external authentication services.

What This Project Demonstrates

  • nginx auth_request module: External authentication for protected resources
  • JWT-based security: Industry-standard token authentication (not forgeable cookies)
  • Role-based access control: Different user roles with different permissions
  • Clean URL structure: Directory-based routing without .html extensions
  • Custom error pages: Professional 403/404 pages with no caching
  • FastAPI backend: Modern Python API for authentication logic

Architecture Overview

┌─────────────┐    auth_request    ┌─────────────────┐
│   nginx     │ ──────────────────▶│   FastAPI       │
│   :8000     │                    │   :5115         │
│             │◀──────────────────▶│                 │
│ - Routes    │    JWT validation  │ - Authentication│
│ - Static    │                    │ - Authorization │
│ - Errors    │                    │ - User roles    │
└─────────────┘                    └─────────────────┘

Quick Start

Prerequisites

  • Docker (for nginx)
  • Python with uv (for FastAPI)
  • just (task runner)
  • curl (for testing)

1. Start Services

Start the authentication service:

just start-auth

Start nginx in a separate terminal:

just start-nginx

2. Test Authentication

Run the full test suite to see JWT authentication in action:

just test-full-flow

3. Manual Testing

Visit the application in your browser:

User Accounts

Username Password Role Access
admin admin admin All pages
reader reader reader Home only

Available Commands

Service Management

Start the FastAPI authentication service:

just start-auth

Stop the authentication service:

just stop-auth

Start nginx in Docker:

just start-nginx

Stop nginx:

just stop-nginx

View nginx logs:

just logs-nginx

Authentication Testing

Login as admin user and save JWT cookie:

just test-login-admin

Login as reader user and save JWT cookie:

just test-login-reader

Test admin access to protected page:

just test-admin-access

Test reader blocked from admin page:

just test-reader-blocked

Check current user info (admin):

just test-whoami-admin

Check current user info (reader):

just test-whoami-reader

Test logout functionality:

just test-logout

Run complete authentication test suite:

just test-full-flow

Clean up cookie files:

just clean-cookies

How nginx auth_request Works

  1. User requests protected resource → nginx receives request
  2. nginx makes auth subrequest → Internal call to /authz endpoint
  3. FastAPI validates JWT token → Checks cookie and user permissions
  4. Authorization response → 200 (allow), 401 (login), or 403 (forbidden)
  5. nginx acts on response → Serves content or shows error page

Configuration Highlights

nginx.conf key sections:

location / {
    auth_request /authz;           # External auth check
    error_page 401 = @login;       # Redirect unauthorized users
    error_page 403 = @forbidden;   # Show 403 page for forbidden
}

location = /authz {
    internal;                      # Only nginx can call this
    proxy_pass http://127.0.0.1:5115/authz;
    proxy_set_header X-Original-URI $request_uri;
}

FastAPI auth logic:

@app.get("/authz")
def authz(request: Request):
    token = request.cookies.get("access_token")
    username = verify_jwt_token(token)
    
    # Role-based access control
    if path.startswith("/admin") and user_role != 'admin':
        return Response("Forbidden", status_code=403)
    
    return Response("OK", status_code=200)

Security Features

  • JWT tokens: Cryptographically signed, cannot be forged
  • Token expiration: 30-minute automatic expiry
  • HttpOnly cookies: Prevent XSS cookie theft
  • Role-based access: Fine-grained permission control
  • Cache prevention: No caching of sensitive pages during demos

File Structure

├── main_auth.py          # FastAPI authentication service
├── nginx.conf            # nginx configuration with auth_request
├── justfile             # Task automation recipes
├── site/                # Static web content
│   ├── index.html       # Home page
│   ├── admin/index.html # Protected admin page
│   ├── login/index.html # Login form
│   ├── 403/index.html   # Access denied page
│   └── 404/index.html   # Not found page
└── README.md           # This documentation

Learning Exercises

1. Basic Flow

# Start services
just start-auth
just start-nginx

# Test unauthenticated access
curl -I http://localhost:8000/
# Should redirect to /login/

# Login and get JWT
just test-login-admin

# Access protected resource
just test-admin-access

2. Role-Based Access

# Login as different users
just test-login-admin
just test-login-reader

# Compare access levels
just test-admin-access    # Should succeed
just test-reader-blocked  # Should fail with 403

3. JWT Security

# View JWT token structure
cat admin_cookies.txt

# Check user info from token
just test-whoami-admin

# Try accessing with expired/invalid token
# (manually edit cookie file to test)

Troubleshooting

Services not starting?

# Check if ports are available
lsof -i :5115  # FastAPI
lsof -i :8000  # nginx

# Check service status
docker ps | grep nginx

Authentication not working?

# Check FastAPI logs
just stop-auth
./main_auth.py  # Run in foreground to see errors

# Check nginx logs  
just logs-nginx
# Clean up old cookies
just clean-cookies

# Check cookie contents
cat admin_cookies.txt

Next Steps

  • Experiment with different user roles
  • Modify JWT expiration times
  • Add new protected endpoints
  • Implement password hashing
  • Add HTTPS with proper secure cookies
  • Try different nginx auth_request patterns

References