Added authentication forms and routes
This commit is contained in:
283
src/myfasthtml/auth/README.md
Normal file
283
src/myfasthtml/auth/README.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# FastHTML + FastAPI Authentication Integration
|
||||
|
||||
This package provides a complete authentication system integrating FastHTML (frontend) with FastAPI (backend).
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
auth/
|
||||
├── __init__.py # Package initialization
|
||||
├── utils.py # JWT helpers + Beforeware
|
||||
├── pages/
|
||||
│ ├── __init__.py
|
||||
│ ├── LoginPage.py # Login form component
|
||||
│ ├── RegisterPage.py # Registration form component
|
||||
│ └── WelcomePage.py # Protected welcome page
|
||||
└── routes.py # All routes (public + protected)
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ **Login page** with email/password authentication
|
||||
- ✅ **Registration page** with password confirmation
|
||||
- ✅ **Automatic token refresh** (proactive refresh at 5 minutes before expiry)
|
||||
- ✅ **Route protection** with Beforeware (whitelist support)
|
||||
- ✅ **Session management** using FastHTML signed cookies
|
||||
- ✅ **DaisyUI 5** components for beautiful UI
|
||||
- ✅ **HTMX integration** for dynamic form submissions
|
||||
|
||||
## Dependencies
|
||||
|
||||
Install the required packages:
|
||||
|
||||
```bash
|
||||
pip install fasthtml httpx python-jose[cryptography] --break-system-packages
|
||||
```
|
||||
|
||||
Note: `python-jose` may already be installed if you have FastAPI.
|
||||
|
||||
## Configuration
|
||||
|
||||
### 1. Update API configuration in `auth/utils.py`
|
||||
|
||||
```python
|
||||
API_BASE_URL = "http://localhost:5001" # Your FastAPI backend URL
|
||||
JWT_SECRET = "jwt-secret-to-change" # Must match your FastAPI secret
|
||||
```
|
||||
|
||||
### 2. Create your `main.py`
|
||||
|
||||
```python
|
||||
from fasthtml.common import fast_app, serve, Link, Script
|
||||
from auth import create_auth_beforeware, setup_auth_routes
|
||||
|
||||
# Import your existing FastAPI auth app
|
||||
# from your_auth_module import create_auth_app_for_sqlite
|
||||
|
||||
# Create Beforeware for route protection
|
||||
beforeware = create_auth_beforeware()
|
||||
|
||||
# Create FastHTML app
|
||||
app, rt = fast_app(
|
||||
before=beforeware,
|
||||
hdrs=(
|
||||
# DaisyUI 5 CSS
|
||||
Link(rel='stylesheet', href='https://cdn.jsdelivr.net/npm/daisyui@5/daisyui.css'),
|
||||
# Tailwind CSS 4
|
||||
Script(src='https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4'),
|
||||
)
|
||||
)
|
||||
|
||||
# Mount FastAPI auth backend
|
||||
# auth_api = create_auth_app_for_sqlite("Users.db", "jwt-secret-to-change")
|
||||
# app.mount("/auth", auth_api)
|
||||
|
||||
# Setup authentication routes
|
||||
setup_auth_routes(app, rt)
|
||||
|
||||
if __name__ == "__main__":
|
||||
serve()
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting the application
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
The application will be available at `http://localhost:5001`.
|
||||
|
||||
### Routes
|
||||
|
||||
**Public routes:**
|
||||
- `GET /login` - Display login page
|
||||
- `POST /login` - Handle login submission
|
||||
- `GET /register` - Display registration page
|
||||
- `POST /register` - Handle registration submission
|
||||
|
||||
**Protected routes (require authentication):**
|
||||
- `GET /` - Welcome page (home)
|
||||
- `GET /welcome` - Alternative welcome page
|
||||
- `POST /logout` - Logout and revoke tokens
|
||||
|
||||
### Adding custom protected routes
|
||||
|
||||
```python
|
||||
@rt("/dashboard")
|
||||
def get_dashboard(session, auth):
|
||||
"""
|
||||
Custom protected route.
|
||||
|
||||
The 'auth' parameter is automatically injected by the Beforeware.
|
||||
"""
|
||||
user_info = session.get('user_info', {})
|
||||
return Div(
|
||||
H1(f"Dashboard for {user_info.get('email', 'User')}"),
|
||||
# Your dashboard content
|
||||
)
|
||||
```
|
||||
|
||||
### Extending the whitelist
|
||||
|
||||
If you need to add more public routes:
|
||||
|
||||
```python
|
||||
beforeware = create_auth_beforeware(
|
||||
additional_patterns=[
|
||||
'/about',
|
||||
'/contact',
|
||||
r'/api/.*', # Regex patterns are supported
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
### 1. Beforeware Protection
|
||||
|
||||
The `auth_before()` function runs before every route (except whitelisted ones):
|
||||
|
||||
1. Checks if `access_token` exists in session
|
||||
2. Validates token expiration
|
||||
3. If token expires in < 5 minutes, automatically refreshes it
|
||||
4. If refresh fails or no token exists, redirects to `/login`
|
||||
|
||||
### 2. Token Refresh Flow
|
||||
|
||||
```
|
||||
User Request → Beforeware checks token
|
||||
↓
|
||||
Token expires in < 5 min?
|
||||
↓ Yes
|
||||
Call /auth/refresh with refresh_token
|
||||
↓
|
||||
Update session with new tokens
|
||||
↓
|
||||
Continue to route handler
|
||||
```
|
||||
|
||||
### 3. Session Storage
|
||||
|
||||
Session contains:
|
||||
- `access_token` (JWT, 30 minutes validity)
|
||||
- `refresh_token` (Opaque, 7 days validity)
|
||||
- `user_info` (email, id, etc.)
|
||||
|
||||
## Customization
|
||||
|
||||
### Changing the refresh threshold
|
||||
|
||||
Edit `auth/utils.py`:
|
||||
|
||||
```python
|
||||
TOKEN_REFRESH_THRESHOLD_MINUTES = 5 # Change to your preferred value
|
||||
```
|
||||
|
||||
### Customizing page components
|
||||
|
||||
Each page component is a class with:
|
||||
- `__init__()` - Constructor with parameters
|
||||
- `_render()` - HTML generation logic
|
||||
- `__ft__()` - FastHTML protocol method
|
||||
|
||||
Example:
|
||||
|
||||
```python
|
||||
class LoginPage:
|
||||
def __init__(self, error_message=None, redirect_url="/"):
|
||||
self.error_message = error_message
|
||||
self.redirect_url = redirect_url
|
||||
|
||||
def _render(self):
|
||||
# Build your custom HTML here
|
||||
return Div(...)
|
||||
|
||||
def __ft__(self):
|
||||
return self._render()
|
||||
```
|
||||
|
||||
### Styling with DaisyUI 5
|
||||
|
||||
DaisyUI 5 provides semantic class names:
|
||||
|
||||
```python
|
||||
# Button
|
||||
Button("Click me", cls="btn btn-primary")
|
||||
|
||||
# Input
|
||||
Input(type="email", cls="input input-bordered")
|
||||
|
||||
# Card
|
||||
Div(
|
||||
Div(
|
||||
H2("Title", cls="card-title"),
|
||||
P("Content"),
|
||||
cls="card-body"
|
||||
),
|
||||
cls="card bg-base-100 shadow-xl"
|
||||
)
|
||||
|
||||
# Alert
|
||||
Div(
|
||||
Span("Success message"),
|
||||
cls="alert alert-success"
|
||||
)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: "Module not found: auth"
|
||||
|
||||
Make sure the `auth` directory is in your Python path or in the same directory as `main.py`.
|
||||
|
||||
### Issue: "Connection refused" when calling API
|
||||
|
||||
Check that:
|
||||
1. `API_BASE_URL` in `auth/utils.py` matches your FastAPI server
|
||||
2. Your FastAPI backend is running
|
||||
3. The `/auth` endpoints are correctly mounted
|
||||
|
||||
### Issue: Token refresh not working
|
||||
|
||||
Verify:
|
||||
1. `JWT_SECRET` in `auth/utils.py` matches your FastAPI secret
|
||||
2. Your FastAPI `/auth/refresh` endpoint is working
|
||||
3. The refresh token is being stored correctly in the session
|
||||
|
||||
### Issue: Infinite redirect loop
|
||||
|
||||
Check:
|
||||
1. The `/login` route is in the whitelist (it should be by default)
|
||||
2. Your FastAPI `/auth/login` endpoint returns `access_token` and `refresh_token`
|
||||
|
||||
## Testing
|
||||
|
||||
To test the authentication flow:
|
||||
|
||||
1. Start your application
|
||||
2. Navigate to `http://localhost:5001/`
|
||||
3. You should be redirected to `/login`
|
||||
4. Register a new account at `/register`
|
||||
5. Login with your credentials
|
||||
6. You should see the welcome page
|
||||
7. Your session will automatically refresh tokens as needed
|
||||
|
||||
## API Integration
|
||||
|
||||
This package expects your FastAPI backend to provide these endpoints:
|
||||
|
||||
- `POST /auth/login` - Returns `access_token` and `refresh_token`
|
||||
- `POST /auth/register` - Creates a new user
|
||||
- `POST /auth/refresh` - Refreshes the access token
|
||||
- `GET /auth/me` - Returns current user info (requires Bearer token)
|
||||
- `POST /auth/logout` - Revokes refresh token
|
||||
|
||||
## License
|
||||
|
||||
This code is provided as-is for educational and development purposes.
|
||||
|
||||
## Questions?
|
||||
|
||||
Feel free to modify and extend this authentication system according to your needs!
|
||||
Reference in New Issue
Block a user