Skip to Content
ServicesAPI GatewayAuthentication

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 identifier
  • email (string, unique) - User email address
  • password_hash (string) - bcrypt hashed password
  • role (string) - User role (patient, physician, admin)
  • is_active (boolean) - Account status
  • is_verified (boolean) - Email verification status
  • tenant_id (int, FK) - Associated tenant
  • last_login (datetime) - Last login timestamp

physicians - Physician-specific profile

  • uuid (UUID) - Unique physician identifier
  • user_id (int, FK) - References users.id
  • employee_id (bigint) - Legacy employee ID
  • language_preference (string) - Preferred language (en, he, etc.)
  • private_pool (text) - Private case pool identifier
  • vacation_mode (boolean) - Vacation status
  • settings (jsonb) - Physician-specific settings
  • admin_settings (jsonb) - Admin-level settings

tenants - Multi-tenancy support

  • uuid (UUID) - Unique tenant identifier
  • name (string) - Tenant display name
  • slug (string, unique) - URL-safe tenant identifier
  • is_active (boolean) - Tenant status

refresh_tokens - JWT refresh token storage

  • token (string, unique) - Refresh token value
  • user_id (int, FK) - References users.id
  • expires_at (datetime) - Token expiration
  • revoked (boolean) - Revocation status

password_reset_tokens - Password reset tokens

  • token (string, unique) - Reset token value
  • user_id (int, FK) - References users.id
  • expires_at (datetime) - Token expiration
  • used (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 credentials
  • 403 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_KEY environment 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_physicians

What it does:

  1. Creates tenants from unique vendor_id values
  2. Migrates physician emails, roles, and settings
  3. Creates password reset tokens for all physicians
  4. 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

  1. Password Storage: bcrypt with automatic salt generation
  2. Token Security:
    • Access tokens are short-lived (30 min)
    • Refresh tokens are rotated on use
    • Reset tokens are single-use and expire after 24h
  3. Secret Key: Must be set in production via SECRET_KEY env var
  4. HTTPS: Always use HTTPS in production
  5. CORS: Configure CORS_ORIGINS for 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
Last updated on