re-uploading work
This commit is contained in:
381
docs/authentication/authentication.md
Normal file
381
docs/authentication/authentication.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# Authentication & Authorization
|
||||
|
||||
## Overview
|
||||
|
||||
Attune uses JWT (JSON Web Token) based authentication for securing API endpoints. The authentication system supports user registration, login, token refresh, and password management.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Components
|
||||
|
||||
1. **JWT Tokens**
|
||||
- **Access Tokens**: Short-lived tokens (default: 1 hour) used for API authentication
|
||||
- **Refresh Tokens**: Long-lived tokens (default: 7 days) used to obtain new access tokens
|
||||
|
||||
2. **Password Security**
|
||||
- Passwords are hashed using **Argon2id** (industry-standard, memory-hard algorithm)
|
||||
- Password hashes are stored in the `attributes` JSONB field of the `identity` table
|
||||
- Minimum password length: 8 characters
|
||||
|
||||
3. **Middleware**
|
||||
- `require_auth`: Middleware function that validates JWT tokens on protected routes
|
||||
- `RequireAuth`: Extractor for accessing authenticated user information in handlers
|
||||
|
||||
## Configuration
|
||||
|
||||
Authentication is configured via environment variables:
|
||||
|
||||
```bash
|
||||
# JWT Secret Key (REQUIRED in production)
|
||||
JWT_SECRET=your-secret-key-here
|
||||
|
||||
# Token Expiration (in seconds)
|
||||
JWT_ACCESS_EXPIRATION=3600 # 1 hour (default)
|
||||
JWT_REFRESH_EXPIRATION=604800 # 7 days (default)
|
||||
```
|
||||
|
||||
**Security Warning**: Always set a strong, random `JWT_SECRET` in production. The default value is insecure and should only be used for development.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Public Endpoints (No Authentication Required)
|
||||
|
||||
#### Register a New User
|
||||
|
||||
```http
|
||||
POST /auth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"login": "username",
|
||||
"password": "securepassword123",
|
||||
"display_name": "John Doe" // optional
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"access_token": "eyJhbGc...",
|
||||
"refresh_token": "eyJhbGc...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Login
|
||||
|
||||
```http
|
||||
POST /auth/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"login": "username",
|
||||
"password": "securepassword123"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"access_token": "eyJhbGc...",
|
||||
"refresh_token": "eyJhbGc...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Refresh Access Token
|
||||
|
||||
```http
|
||||
POST /auth/refresh
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"refresh_token": "eyJhbGc..."
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"access_token": "eyJhbGc...",
|
||||
"refresh_token": "eyJhbGc...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Protected Endpoints (Authentication Required)
|
||||
|
||||
All protected endpoints require an `Authorization` header with a valid access token:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
#### Get Current User
|
||||
|
||||
```http
|
||||
GET /auth/me
|
||||
Authorization: Bearer eyJhbGc...
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"login": "username",
|
||||
"display_name": "John Doe"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Change Password
|
||||
|
||||
```http
|
||||
POST /auth/change-password
|
||||
Authorization: Bearer eyJhbGc...
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"current_password": "oldpassword123",
|
||||
"new_password": "newpassword456"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"success": true,
|
||||
"message": "Password changed successfully"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
Authentication errors return appropriate HTTP status codes:
|
||||
|
||||
- **400 Bad Request**: Invalid request format or validation errors
|
||||
- **401 Unauthorized**: Missing, invalid, or expired token; invalid credentials
|
||||
- **403 Forbidden**: Insufficient permissions (future RBAC implementation)
|
||||
- **409 Conflict**: Username already exists during registration
|
||||
|
||||
Example error response:
|
||||
```json
|
||||
{
|
||||
"error": "Invalid authentication token",
|
||||
"code": "UNAUTHORIZED"
|
||||
}
|
||||
```
|
||||
|
||||
## Usage in Route Handlers
|
||||
|
||||
### Protecting Routes
|
||||
|
||||
Add the authentication middleware to routes that require authentication:
|
||||
|
||||
```rust
|
||||
use crate::auth::middleware::RequireAuth;
|
||||
|
||||
async fn protected_handler(
|
||||
RequireAuth(user): RequireAuth,
|
||||
) -> Result<Json<ApiResponse<MyData>>, ApiError> {
|
||||
let identity_id = user.identity_id()?;
|
||||
let login = user.login();
|
||||
|
||||
// Your handler logic here
|
||||
Ok(Json(ApiResponse::new(data)))
|
||||
}
|
||||
```
|
||||
|
||||
### Accessing User Information
|
||||
|
||||
The `RequireAuth` extractor provides access to the authenticated user's claims:
|
||||
|
||||
```rust
|
||||
pub struct AuthenticatedUser {
|
||||
pub claims: Claims,
|
||||
}
|
||||
|
||||
impl AuthenticatedUser {
|
||||
pub fn identity_id(&self) -> Result<i64, ParseIntError>
|
||||
pub fn login(&self) -> &str
|
||||
}
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Identity Table
|
||||
|
||||
The `identity` table stores user authentication information:
|
||||
|
||||
```sql
|
||||
CREATE TABLE attune.identity (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
login TEXT NOT NULL UNIQUE,
|
||||
display_name TEXT,
|
||||
attributes JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
password_hash TEXT, -- Added in migration 20240102000001
|
||||
created TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
**Note**: The `password_hash` column is optional to support:
|
||||
- External authentication providers (OAuth, SAML, etc.)
|
||||
- Service accounts that don't use password authentication
|
||||
- API key-based authentication (future implementation)
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **JWT Secret**
|
||||
- Use a strong, random secret (minimum 256 bits)
|
||||
- Never commit secrets to version control
|
||||
- Rotate secrets periodically in production
|
||||
|
||||
2. **Token Storage (Client-Side)**
|
||||
- Store tokens securely (e.g., httpOnly cookies or secure storage)
|
||||
- Never expose tokens in URLs or localStorage (if using web clients)
|
||||
- Clear tokens on logout
|
||||
|
||||
3. **Password Requirements**
|
||||
- Minimum 8 characters (enforced by validation)
|
||||
- Consider implementing additional requirements (uppercase, numbers, symbols)
|
||||
- Implement rate limiting on login attempts (future enhancement)
|
||||
|
||||
4. **HTTPS**
|
||||
- Always use HTTPS in production to protect tokens in transit
|
||||
- Configure proper TLS/SSL certificates
|
||||
|
||||
5. **Token Expiration**
|
||||
- Keep access tokens short-lived (1 hour recommended)
|
||||
- Use refresh tokens for long-lived sessions
|
||||
- Implement token revocation for logout (future enhancement)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
|
||||
1. **Role-Based Access Control (RBAC)**
|
||||
- Permission set assignments
|
||||
- Fine-grained authorization middleware
|
||||
- Resource-level permissions
|
||||
|
||||
2. **Multi-Factor Authentication (MFA)**
|
||||
- TOTP support
|
||||
- SMS/Email verification codes
|
||||
|
||||
3. **OAuth/OIDC Integration**
|
||||
- Support for external identity providers
|
||||
- Single Sign-On (SSO)
|
||||
|
||||
4. **Token Revocation**
|
||||
- Blacklist/whitelist mechanisms
|
||||
- Force logout functionality
|
||||
|
||||
5. **Account Security**
|
||||
- Password reset via email
|
||||
- Account lockout after failed attempts
|
||||
- Security audit logs
|
||||
|
||||
6. **API Keys**
|
||||
- Service-to-service authentication
|
||||
- Scoped API keys for automation
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Testing with cURL
|
||||
|
||||
```bash
|
||||
# Register a new user
|
||||
curl -X POST http://localhost:8080/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"login": "testuser",
|
||||
"password": "testpass123",
|
||||
"display_name": "Test User"
|
||||
}'
|
||||
|
||||
# Login
|
||||
curl -X POST http://localhost:8080/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"login": "testuser",
|
||||
"password": "testpass123"
|
||||
}'
|
||||
|
||||
# Get current user (replace TOKEN with actual access token)
|
||||
curl http://localhost:8080/auth/me \
|
||||
-H "Authorization: Bearer TOKEN"
|
||||
|
||||
# Change password
|
||||
curl -X POST http://localhost:8080/auth/change-password \
|
||||
-H "Authorization: Bearer TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"current_password": "testpass123",
|
||||
"new_password": "newpass456"
|
||||
}'
|
||||
|
||||
# Refresh token
|
||||
curl -X POST http://localhost:8080/auth/refresh \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"refresh_token": "REFRESH_TOKEN"
|
||||
}'
|
||||
```
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Password hashing and JWT utilities include comprehensive unit tests:
|
||||
|
||||
```bash
|
||||
# Run auth-related tests
|
||||
cargo test --package attune-api password
|
||||
cargo test --package attune-api jwt
|
||||
cargo test --package attune-api middleware
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"Missing authentication token"**
|
||||
- Ensure you're including the `Authorization` header
|
||||
- Verify the header format: `Bearer <token>`
|
||||
|
||||
2. **"Authentication token expired"**
|
||||
- Use the refresh token endpoint to get a new access token
|
||||
- Check token expiration configuration
|
||||
|
||||
3. **"Invalid login or password"**
|
||||
- Verify credentials are correct
|
||||
- Check if the identity has a password set (some accounts may use external auth)
|
||||
|
||||
4. **"JWT_SECRET not set" warning**
|
||||
- Set the `JWT_SECRET` environment variable before starting the server
|
||||
- Use a strong, random value in production
|
||||
|
||||
### Debug Logging
|
||||
|
||||
Enable debug logging to troubleshoot authentication issues:
|
||||
|
||||
```bash
|
||||
RUST_LOG=attune_api=debug cargo run --bin attune-api
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [RFC 7519: JSON Web Token (JWT)](https://datatracker.ietf.org/doc/html/rfc7519)
|
||||
- [Argon2 Password Hashing](https://en.wikipedia.org/wiki/Argon2)
|
||||
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
|
||||
Reference in New Issue
Block a user