Authentication & User Management
The API Gateway implements a complete JWT-based authentication system with user management, role-based access control, and multi-tenancy support.
Overview
Authentication Method: JWT (JSON Web Tokens)
Password Hashing: bcrypt
User Roles: patient, physician, admin
Multi-tenancy: Supported via tenants table
Database Schema
Core Tables
users - Main user authentication table
uuid(UUID, PK) - Unique user identifieremail(string, unique) - User email addresspassword_hash(string) - bcrypt hashed passwordrole(string) - User role (patient,physician,admin)is_active(boolean) - Account statusis_verified(boolean) - Email verification statustenant_id(int, FK) - Associated tenantlast_login(datetime) - Last login timestamp
physicians - Physician-specific profile
uuid(UUID) - Unique physician identifieruser_id(int, FK) - Referencesusers.idemployee_id(bigint) - Legacy employee IDlanguage_preference(string) - Preferred language (en, he, etc.)private_pool(text) - Private case pool identifiervacation_mode(boolean) - Vacation statussettings(jsonb) - Physician-specific settingsadmin_settings(jsonb) - Admin-level settings
tenants - Multi-tenancy support
uuid(UUID) - Unique tenant identifiername(string) - Tenant display nameslug(string, unique) - URL-safe tenant identifieris_active(boolean) - Tenant status
refresh_tokens - JWT refresh token storage
token(string, unique) - Refresh token valueuser_id(int, FK) - Referencesusers.idexpires_at(datetime) - Token expirationrevoked(boolean) - Revocation status
password_reset_tokens - Password reset tokens
token(string, unique) - Reset token valueuser_id(int, FK) - Referencesusers.idexpires_at(datetime) - Token expirationused(boolean) - Whether token has been used
API Endpoints
All auth endpoints are under /api/auth.
POST /register
Register a new user and get JWT tokens.
Request Body:
{
"email": "user@example.com",
"password": "securepassword123",
"role": "patient" // patient | physician | admin
}Response (201 Created):
{
"access_token": "eyJ0eXAiOiJKV1QiLC...",
"refresh_token": "eyJ0eXAiOiJKV1QiLC...",
"token_type": "bearer"
}Notes:
- Password must be at least 8 characters
- Email must be unique
- If role is
physician, a physician profile is automatically created
POST /login
Login with email and password.
Request Body:
{
"email": "user@example.com",
"password": "securepassword123"
}Response (200 OK):
{
"access_token": "eyJ0eXAiOiJKV1QiLC...",
"refresh_token": "eyJ0eXAiOiJKV1QiLC...",
"token_type": "bearer"
}Errors:
401 Unauthorized- Invalid credentials403 Forbidden- Account is inactive
POST /refresh
Get new access and refresh tokens using a refresh token.
Request Body:
{
"refresh_token": "eyJ0eXAiOiJKV1QiLC..."
}Response (200 OK):
{
"access_token": "eyJ0eXAiOiJKV1QiLC...",
"refresh_token": "eyJ0eXAiOiJKV1QiLC...", // New refresh token
"token_type": "bearer"
}Notes:
- Old refresh token is automatically revoked
- Returns new access and refresh tokens
GET /me
Get current user information (requires authentication).
Headers:
Authorization: Bearer eyJ0eXAiOiJKV1QiLC...Response (200 OK):
{
"user": {
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"email": "doctor@example.com",
"role": "physician",
"is_active": true,
"is_verified": false,
"tenant_id": 1,
"created_at": "2025-10-09T12:00:00Z",
"last_login": "2025-10-09T14:30:00Z"
},
"physician": {
"uuid": "660e8400-e29b-41d4-a716-446655440000",
"user_id": 1,
"employee_id": 12345,
"language_preference": "en",
"vacation_mode": false,
"created_at": "2025-10-09T12:00:00Z"
},
"tenant": {
"uuid": "770e8400-e29b-41d4-a716-446655440000",
"name": "Maccabi",
"slug": "maccabi",
"is_active": true,
"created_at": "2025-10-01T00:00:00Z"
}
}POST /request-reset
Request a password reset token.
Request Body:
{
"email": "user@example.com"
}Response (200 OK):
{
"message": "If your email is registered, you will receive a password reset link"
}Notes:
- Always returns success (doesn’t reveal if email exists)
- TODO: Currently logs token to console, needs email service integration
POST /reset-password
Reset password using reset token.
Request Body:
{
"token": "xY9zAbC...",
"new_password": "newsecurepassword456"
}Response (200 OK):
{
"message": "Password successfully reset"
}Errors:
400 Bad Request- Invalid or expired token
POST /change-password
Change password for authenticated user.
Headers:
Authorization: Bearer eyJ0eXAiOiJKV1QiLC...Request Body:
{
"current_password": "oldpassword123",
"new_password": "newpassword456"
}Response (200 OK):
{
"message": "Password successfully changed"
}Errors:
400 Bad Request- Current password incorrect
Using Authentication in Your Code
Protecting Endpoints
from typing import Annotated
from fastapi import Depends
from core.dependencies import get_current_user, get_current_physician, get_current_admin
from database.models import User
@router.get("/protected")
def protected_route(
current_user: Annotated[User, Depends(get_current_user)]
):
# Any authenticated user can access
return {"user_id": current_user.uuid}
@router.get("/physician-only")
def physician_route(
current_user: Annotated[User, Depends(get_current_physician)]
):
# Only physicians and admins can access
return {"physician_id": current_user.physician.uuid}
@router.get("/admin-only")
def admin_route(
current_user: Annotated[User, Depends(get_current_admin)]
):
# Only admins can access
return {"admin": True}Client-Side Usage
React Native / Web:
// Register
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'password123',
role: 'patient'
})
});
const { access_token, refresh_token } = await response.json();
// Store tokens securely
// Web: httpOnly cookies or secure localStorage
// React Native: @react-native-async-storage/async-storage
// Use access token in requests
const protectedResponse = await fetch('/api/protected-endpoint', {
headers: {
'Authorization': `Bearer ${access_token}`
}
});
// Refresh token when access token expires
const refreshResponse = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token })
});Token Configuration
Tokens are configured in src/core/auth.py:
- Access Token Expiry: 30 minutes
- Refresh Token Expiry: 30 days
- Reset Token Expiry: 24 hours
- Algorithm: HS256
- Secret Key: Set via
SECRET_KEYenvironment variable (auto-generated for local dev)
Migrating Existing Physicians
A migration script is provided to import physicians from the legacy dd_api.groups table:
cd services/api_gateway
uv run python -m scripts.migrate_physiciansWhat it does:
- Creates
tenantsfrom uniquevendor_idvalues - Migrates physician emails, roles, and settings
- Creates password reset tokens for all physicians
- Prints reset URLs for distribution
Output Example:
Successfully migrated 44 physicians
doctor1@maccabi.com (physician)
http://localhost:3000/reset-password?token=xY9zAbC...
doctor2@maccabi.com (admin)
http://localhost:3000/reset-password?token=aB8cDeF...Security Considerations
- Password Storage: bcrypt with automatic salt generation
- Token Security:
- Access tokens are short-lived (30 min)
- Refresh tokens are rotated on use
- Reset tokens are single-use and expire after 24h
- Secret Key: Must be set in production via
SECRET_KEYenv var - HTTPS: Always use HTTPS in production
- CORS: Configure
CORS_ORIGINSfor your frontend domains
TODO / Future Enhancements
- Email service integration for password resets
- Email verification flow
- 2FA / MFA support
- OAuth providers (Google, Apple Sign-In)
- Session management and revocation
- Rate limiting on auth endpoints
- Audit logging for auth events
- SAML/SSO for enterprise tenants