README
This commit is contained in:
parent
298d3223f0
commit
bd77731487
3 changed files with 297 additions and 5 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
reader_cookies.txt
|
reader_cookies.txt
|
||||||
admin_cookies.txt
|
admin_cookies.txt
|
||||||
|
reader_cookies.txt
|
||||||
|
|
|
||||||
296
README.md
Normal file
296
README.md
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just start-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
Start nginx in a separate terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just start-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Test Authentication
|
||||||
|
|
||||||
|
Run the full test suite to see JWT authentication in action:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just test-full-flow
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Manual Testing
|
||||||
|
|
||||||
|
Visit the application in your browser:
|
||||||
|
- http://localhost:8000/ - Home page (redirects to login if not authenticated)
|
||||||
|
- http://localhost:8000/login/ - Login page
|
||||||
|
- http://localhost:8000/admin/ - Admin-only page
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
```bash
|
||||||
|
just start-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
Stop the authentication service:
|
||||||
|
```bash
|
||||||
|
just stop-auth
|
||||||
|
```
|
||||||
|
|
||||||
|
Start nginx in Docker:
|
||||||
|
```bash
|
||||||
|
just start-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
Stop nginx:
|
||||||
|
```bash
|
||||||
|
just stop-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
View nginx logs:
|
||||||
|
```bash
|
||||||
|
just logs-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication Testing
|
||||||
|
|
||||||
|
Login as admin user and save JWT cookie:
|
||||||
|
```bash
|
||||||
|
just test-login-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Login as reader user and save JWT cookie:
|
||||||
|
```bash
|
||||||
|
just test-login-reader
|
||||||
|
```
|
||||||
|
|
||||||
|
Test admin access to protected page:
|
||||||
|
```bash
|
||||||
|
just test-admin-access
|
||||||
|
```
|
||||||
|
|
||||||
|
Test reader blocked from admin page:
|
||||||
|
```bash
|
||||||
|
just test-reader-blocked
|
||||||
|
```
|
||||||
|
|
||||||
|
Check current user info (admin):
|
||||||
|
```bash
|
||||||
|
just test-whoami-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Check current user info (reader):
|
||||||
|
```bash
|
||||||
|
just test-whoami-reader
|
||||||
|
```
|
||||||
|
|
||||||
|
Test logout functionality:
|
||||||
|
```bash
|
||||||
|
just test-logout
|
||||||
|
```
|
||||||
|
|
||||||
|
Run complete authentication test suite:
|
||||||
|
```bash
|
||||||
|
just test-full-flow
|
||||||
|
```
|
||||||
|
|
||||||
|
Clean up cookie files:
|
||||||
|
```bash
|
||||||
|
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:**
|
||||||
|
```nginx
|
||||||
|
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:**
|
||||||
|
```python
|
||||||
|
@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
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
```bash
|
||||||
|
# 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?
|
||||||
|
```bash
|
||||||
|
# Check if ports are available
|
||||||
|
lsof -i :5115 # FastAPI
|
||||||
|
lsof -i :8000 # nginx
|
||||||
|
|
||||||
|
# Check service status
|
||||||
|
docker ps | grep nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Authentication not working?
|
||||||
|
```bash
|
||||||
|
# Check FastAPI logs
|
||||||
|
just stop-auth
|
||||||
|
./main_auth.py # Run in foreground to see errors
|
||||||
|
|
||||||
|
# Check nginx logs
|
||||||
|
just logs-nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cookie issues?
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
|
||||||
|
- [nginx auth_request module](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html)
|
||||||
|
- [FastAPI Security](https://fastapi.tiangolo.com/tutorial/security/)
|
||||||
|
- [JWT.io](https://jwt.io/) - JWT debugger
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
# Netscape HTTP Cookie File
|
|
||||||
# https://curl.se/docs/http-cookies.html
|
|
||||||
# This file was generated by libcurl! Edit at your own risk.
|
|
||||||
|
|
||||||
#HttpOnly_localhost FALSE / FALSE 1763754196 access_token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWFkZXIiLCJyb2xlIjoicmVhZGVyIiwiZXhwIjoxNzYzNzU0MTk2fQ.G4lmnqksaHqX-36N_HFpn_NtrTh7H6Fq2zXrCDktMgg
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue