feat: update configuration and README for improved authentication and database support
This commit is contained in:
148
README.md
148
README.md
@@ -5,10 +5,10 @@ A production-ready, modular Rust API template for K-Suite applications, followin
|
||||
## Features
|
||||
|
||||
- **Hexagonal Architecture**: Clear separation between Domain, Infrastructure, and API layers
|
||||
- **Multiple Auth Modes**: Session-based, JWT, or both - fully configurable
|
||||
- **OIDC Integration**: Connect to any OpenID Connect provider (Keycloak, Auth0, Zitadel, etc.)
|
||||
- **JWT-Only Authentication**: Stateless Bearer token auth — no server-side sessions
|
||||
- **OIDC Integration**: Connect to any OpenID Connect provider (Keycloak, Auth0, Zitadel, etc.) with stateless cookie-based flow state
|
||||
- **Database Flexibility**: SQLite (default) or PostgreSQL via feature flags
|
||||
- **Type-Safe Configuration**: Newtypes with built-in validation for all security-sensitive values
|
||||
- **Type-Safe Domain**: Newtypes with built-in validation for emails, passwords, secrets, and OIDC values
|
||||
- **Cargo Generate Ready**: Pre-configured for scaffolding new services
|
||||
|
||||
## Quick Start
|
||||
@@ -22,7 +22,6 @@ cargo generate --git https://github.com/GKaszewski/k-template.git
|
||||
You'll be prompted to choose:
|
||||
- **Project name**: Your new service name
|
||||
- **Database**: `sqlite` or `postgres`
|
||||
- **Session auth**: Enable cookie-based sessions
|
||||
- **JWT auth**: Enable Bearer token authentication
|
||||
- **OIDC**: Enable OpenID Connect integration
|
||||
|
||||
@@ -33,15 +32,6 @@ git clone https://github.com/GKaszewski/k-template.git my-api
|
||||
cd my-api
|
||||
cp .env.example .env
|
||||
# Edit .env with your configuration
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
# Development (with hot reload via cargo-watch)
|
||||
cargo watch -x run
|
||||
|
||||
# Or simply
|
||||
cargo run
|
||||
```
|
||||
|
||||
@@ -51,51 +41,51 @@ The API will be available at `http://localhost:3000/api/v1/`.
|
||||
|
||||
All configuration is done via environment variables. See [.env.example](.env.example) for all options.
|
||||
|
||||
### Authentication Modes
|
||||
### Key Variables
|
||||
|
||||
Set `AUTH_MODE` to one of:
|
||||
|
||||
| Mode | Description | Required Features |
|
||||
|------|-------------|-------------------|
|
||||
| `session` | Cookie-based sessions | `auth-axum-login` |
|
||||
| `jwt` | Bearer token authentication | `auth-jwt` |
|
||||
| `both` | Try JWT first, fall back to session | `auth-axum-login`, `auth-jwt` |
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `DATABASE_URL` | `sqlite:data.db?mode=rwc` | Database connection string |
|
||||
| `COOKIE_SECRET` | *(insecure dev default)* | Secret for encrypting OIDC state cookie (≥64 bytes in production) |
|
||||
| `JWT_SECRET` | *(insecure dev default)* | Secret for signing JWT tokens (≥32 bytes in production) |
|
||||
| `JWT_EXPIRY_HOURS` | `24` | Token lifetime in hours |
|
||||
| `CORS_ALLOWED_ORIGINS` | `http://localhost:5173` | Comma-separated allowed origins |
|
||||
| `SECURE_COOKIE` | `false` | Set `true` when serving over HTTPS |
|
||||
| `PRODUCTION` | `false` | Enforces minimum secret lengths |
|
||||
|
||||
### OIDC Integration
|
||||
|
||||
To enable OIDC login (e.g., "Login with Google"):
|
||||
To enable "Login with Google/Keycloak/etc.":
|
||||
|
||||
1. Enable the `auth-oidc` feature (enabled by default)
|
||||
2. Configure your OIDC provider in `.env`:
|
||||
1. Enable the `auth-oidc` feature (on by default in cargo-generate)
|
||||
2. Set these environment variables:
|
||||
```env
|
||||
OIDC_ISSUER=https://your-provider.com
|
||||
OIDC_CLIENT_ID=your-client-id
|
||||
OIDC_CLIENT_SECRET=your-secret
|
||||
OIDC_REDIRECT_URL=http://localhost:3000/api/v1/auth/callback
|
||||
```
|
||||
3. Users can login via `GET /api/v1/auth/login/oidc`
|
||||
3. Users start the flow at `GET /api/v1/auth/login/oidc`
|
||||
|
||||
OIDC state (CSRF token, PKCE verifier, nonce) is stored in a short-lived encrypted cookie — no database session table required.
|
||||
|
||||
## Feature Flags
|
||||
|
||||
Features are configured in `api/Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default = ["sqlite", "auth-axum-login", "auth-oidc", "auth-jwt"]
|
||||
default = ["sqlite", "auth-jwt"]
|
||||
```
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| `sqlite` | SQLite database support (default) |
|
||||
| `postgres` | PostgreSQL database support |
|
||||
| `auth-axum-login` | Session-based authentication |
|
||||
| `sqlite` | SQLite database (default) |
|
||||
| `postgres` | PostgreSQL database |
|
||||
| `auth-jwt` | JWT Bearer token authentication |
|
||||
| `auth-oidc` | OpenID Connect integration |
|
||||
| `auth-jwt` | JWT token authentication |
|
||||
| `auth-full` | All auth features combined |
|
||||
|
||||
### Common Configurations
|
||||
|
||||
**JWT-only API (stateless)**:
|
||||
**JWT-only (minimal, default)**:
|
||||
```toml
|
||||
default = ["sqlite", "auth-jwt"]
|
||||
```
|
||||
@@ -105,48 +95,74 @@ default = ["sqlite", "auth-jwt"]
|
||||
default = ["sqlite", "auth-oidc", "auth-jwt"]
|
||||
```
|
||||
|
||||
**Full-featured (all auth methods)**:
|
||||
**PostgreSQL + OIDC + JWT**:
|
||||
```toml
|
||||
default = ["sqlite", "auth-full"]
|
||||
default = ["postgres", "auth-oidc", "auth-jwt"]
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `POST` | `/api/v1/auth/login` | Login with email/password |
|
||||
| `POST` | `/api/v1/auth/register` | Register new user |
|
||||
| `POST` | `/api/v1/auth/logout` | Logout (session mode) |
|
||||
| `GET` | `/api/v1/auth/me` | Get current user |
|
||||
| `POST` | `/api/v1/auth/token` | Get JWT for session user |
|
||||
| `GET` | `/api/v1/auth/login/oidc` | Start OIDC login flow |
|
||||
| `GET` | `/api/v1/auth/callback` | OIDC callback |
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| `POST` | `/api/v1/auth/register` | — | Register with email + password → JWT |
|
||||
| `POST` | `/api/v1/auth/login` | — | Login with email + password → JWT |
|
||||
| `POST` | `/api/v1/auth/logout` | — | Returns 200; client drops the token |
|
||||
| `GET` | `/api/v1/auth/me` | Bearer | Current user info |
|
||||
| `POST` | `/api/v1/auth/token` | Bearer | Issue a fresh JWT (`auth-jwt`) |
|
||||
| `GET` | `/api/v1/auth/login/oidc` | — | Start OIDC flow, sets encrypted state cookie (`auth-oidc`) |
|
||||
| `GET` | `/api/v1/auth/callback` | — | Complete OIDC flow → JWT, clears cookie (`auth-oidc`) |
|
||||
|
||||
### Example: Register and use a token
|
||||
|
||||
```bash
|
||||
# Register
|
||||
curl -X POST http://localhost:3000/api/v1/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email": "user@example.com", "password": "mypassword"}'
|
||||
# → {"access_token": "eyJ...", "token_type": "Bearer", "expires_in": 86400}
|
||||
|
||||
# Use the token
|
||||
curl http://localhost:3000/api/v1/auth/me \
|
||||
-H "Authorization: Bearer eyJ..."
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
k-template/
|
||||
├── domain/ # Core business logic (no I/O dependencies)
|
||||
├── domain/ # Pure business logic — zero I/O dependencies
|
||||
│ └── src/
|
||||
│ ├── entities.rs # User entity
|
||||
│ ├── value_objects.rs # Email, Password, OIDC newtypes
|
||||
│ ├── value_objects.rs # Email, Password, JwtSecret, OIDC newtypes
|
||||
│ ├── repositories.rs # Repository interfaces (ports)
|
||||
│ └── services.rs # Domain services
|
||||
│ ├── services.rs # Domain services
|
||||
│ └── errors.rs # DomainError (Unauthenticated 401, Forbidden 403, …)
|
||||
│
|
||||
├── infra/ # Infrastructure adapters
|
||||
│ └── src/
|
||||
│ ├── auth/ # Auth backends (OIDC, JWT)
|
||||
│ └── user_repository.rs
|
||||
│ ├── auth/
|
||||
│ │ ├── jwt.rs # JwtValidator — create + verify tokens
|
||||
│ │ └── oidc.rs # OidcService + OidcState (cookie-serializable)
|
||||
│ ├── user_repository.rs # SQLite / PostgreSQL adapter
|
||||
│ ├── db.rs # DatabasePool re-export
|
||||
│ └── factory.rs # build_user_repository()
|
||||
│
|
||||
├── api/ # HTTP API layer
|
||||
├── api/ # HTTP layer
|
||||
│ └── src/
|
||||
│ ├── routes/ # API endpoints
|
||||
│ ├── config.rs # Configuration
|
||||
│ └── state.rs # Application state
|
||||
│ ├── routes/
|
||||
│ │ ├── auth.rs # Login, register, logout, me, OIDC flow
|
||||
│ │ └── config.rs # /config endpoint
|
||||
│ ├── config.rs # Config::from_env()
|
||||
│ ├── state.rs # AppState (user_service, cookie_key, jwt_validator, …)
|
||||
│ ├── extractors.rs # CurrentUser (JWT Bearer extractor)
|
||||
│ ├── error.rs # ApiError → HTTP status mapping
|
||||
│ └── dto.rs # LoginRequest, RegisterRequest, TokenResponse, …
|
||||
│
|
||||
├── .env.example # Configuration template
|
||||
├── migrations_sqlite/
|
||||
├── migrations_postgres/
|
||||
├── .env.example
|
||||
└── compose.yml # Docker Compose for local dev
|
||||
```
|
||||
|
||||
@@ -156,10 +172,13 @@ k-template/
|
||||
|
||||
```bash
|
||||
# All tests
|
||||
cargo test --all-features
|
||||
cargo test
|
||||
|
||||
# Domain tests only
|
||||
# Domain only
|
||||
cargo test -p domain
|
||||
|
||||
# Infra only (SQLite integration tests)
|
||||
cargo test -p infra
|
||||
```
|
||||
|
||||
### Database Migrations
|
||||
@@ -168,10 +187,23 @@ cargo test -p domain
|
||||
# SQLite
|
||||
sqlx migrate run --source migrations_sqlite
|
||||
|
||||
# PostgreSQL
|
||||
# PostgreSQL
|
||||
sqlx migrate run --source migrations_postgres
|
||||
```
|
||||
|
||||
### Building with specific features
|
||||
|
||||
```bash
|
||||
# Minimal: SQLite + JWT only
|
||||
cargo build -F sqlite,auth-jwt
|
||||
|
||||
# Full: SQLite + JWT + OIDC
|
||||
cargo build -F sqlite,auth-jwt,auth-oidc
|
||||
|
||||
# PostgreSQL variant
|
||||
cargo build --no-default-features -F postgres,auth-jwt,auth-oidc
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
Reference in New Issue
Block a user