Authentication
Token Configuration
| Token |
Expiry |
Storage |
| access_token |
15 minutes |
Memory + Secure storage |
| refresh_token |
7 days |
Secure storage (rotated on use) |
All authenticated endpoints require:
Authorization: Bearer <access_token>
Authentication Endpoints
Register
POST /api/v1/auth/register
Content-Type: application/json
Request:
{
"email": "user@example.com",
"password": "securePassword123",
"tenant_name": "Company Name"
}
Response (201):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"is_active": true,
"tenant_id": "660e8400-e29b-41d4-a716-446655440000"
}
}
Login
POST /api/v1/auth/login
Content-Type: application/json
Request:
{
"email": "user@example.com",
"password": "securePassword123"
}
Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"is_active": true,
"tenant_id": "660e8400-e29b-41d4-a716-446655440000"
}
}
Refresh Token
POST /api/v1/auth/refresh
Content-Type: application/json
Request:
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response (200):
{
"access_token": "new_access_token...",
"refresh_token": "new_refresh_token...",
"token_type": "bearer"
}
Logout
POST /api/v1/auth/logout
Content-Type: application/json
Request:
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response (200):
{
"message": "Logged out successfully"
}
Email Verification
Send Verification
POST /auth/email/send-verification
Content-Type: application/json
Request:
{
"email": "user@example.com"
}
Response (200):
{
"message": "Verification code sent"
}
Verify Email
POST /auth/email/verify
Content-Type: application/json
Request:
{
"email": "user@example.com",
"code": "123456"
}
Response (200):
{
"message": "Email verified successfully"
}
Password Reset
Send Reset Email
POST /auth/email/send-reset-password
Content-Type: application/json
Request:
{
"email": "user@example.com"
}
Response (200):
{
"message": "If the email exists, a reset code has been sent"
}
Reset Password
POST /auth/email/reset-password
Content-Type: application/json
Request:
{
"email": "user@example.com",
"code": "123456",
"new_password": "newSecurePassword456"
}
Response (200):
{
"message": "Password reset successfully"
}
OAuth Authentication
Initiate OAuth
GET /api/v1/auth/oauth/google
GET /api/v1/auth/oauth/github
Response (200):
{
"authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?...",
"state": "random_state_string"
}
OAuth Callback
GET /api/v1/auth/oauth/google/callback?code=AUTHORIZATION_CODE&state=STATE
Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "uuid",
"email": "user@example.com",
"is_active": true,
"tenant_id": "uuid"
}
}
Supported Providers
| Provider |
Endpoint |
| Google |
/api/v1/auth/oauth/google |
| GitHub |
/api/v1/auth/oauth/github |
| Custom (Okta) |
/api/v1/auth/oauth/custom/okta |
Test Credentials
| Role |
Email |
Password |
| Admin |
admin@courierflow.com |
admin |
| Courier |
courier@courierflow.com |
admin |
Error Responses
401 Unauthorized
{
"detail": "Invalid credentials"
}
400 Bad Request
{
"detail": "User already exists"
}
422 Validation Error
{
"detail": [
{
"loc": ["body", "email"],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}
Security Considerations
- Password Storage: bcrypt with salt (never plaintext)
- Token Rotation: Refresh tokens rotate on every use
- JWT Payload: Contains
sub (user_id), tenant_id, exp
- Rate Limits: Applied per endpoint (see Rate Limiting docs)