Skip to main content

Overview

OmniEHR uses JWT (JSON Web Token) based authentication. After successful login, clients receive a token that must be included in the Authorization header for all protected endpoints.

Authentication Flow

  1. Login: Send credentials to /api/auth/login
  2. Receive Token: Get JWT token and user profile
  3. Use Token: Include token in Authorization header for subsequent requests
  4. Token Expiry: Tokens expire based on JWT_EXPIRES_IN configuration

Base URL

/api/auth

Rate Limiting

Authentication endpoints are rate-limited to 100 requests per 15 minutes per IP address.

Login

Authenticate with email and password to receive a JWT token.
Password validation is performed server-side. Only active users can log in.

Endpoint

POST /api/auth/login

Request Body

email
string
required
User email address (case-insensitive, trimmed)
password
string
required
User password (minimum 1 character for login validation)

Example Request

curl -X POST https://your-domain.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "SecurePassword123!"
  }'

Success Response (200)

token
string
JWT access token for authenticated requests
user
object
id
string
User ID (MongoDB ObjectId as string)
email
string
User email address
fullName
string
User’s full name
organization
string
User’s organization
role
string
User role: admin, practitioner, or auditor
active
boolean
Account status
lastLoginAt
string
Timestamp of last login (ISO 8601 format)
createdAt
string
Account creation timestamp
Example Response:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "507f1f77bcf86cd799439011",
    "email": "[email protected]",
    "fullName": "Dr. Jane Smith",
    "organization": "General Hospital",
    "role": "practitioner",
    "active": true,
    "lastLoginAt": "2026-03-04T10:30:00.000Z",
    "createdAt": "2025-01-15T08:00:00.000Z"
  }
}

Error Responses

statusCode
number
HTTP status code
message
string
Error description

401 Unauthorized

Invalid credentials or inactive account:
{
  "statusCode": 401,
  "message": "Invalid credentials"
}

400 Bad Request

Validation error:
{
  "statusCode": 400,
  "message": "Validation error",
  "errors": [
    {
      "field": "email",
      "message": "Invalid email"
    }
  ]
}

429 Too Many Requests

Rate limit exceeded:
{
  "statusCode": 429,
  "message": "Too many requests, please try again later."
}

Implementation Details

From /server/src/routes/authRoutes.js:23-51:
  1. Email Validation: Email is trimmed and converted to lowercase
  2. User Lookup: Searches for user by email in database
  3. Account Status Check: Verifies user account is active
  4. Password Verification: Uses bcrypt to compare passwords
  5. Login Timestamp Update: Updates lastLoginAt field
  6. Token Generation: Creates JWT with user claims

JWT Token Payload

The token contains the following claims (from /server/src/routes/authRoutes.js:42-47):
{
  "sub": "507f1f77bcf86cd799439011",
  "email": "[email protected]",
  "role": "practitioner",
  "name": "Dr. Jane Smith",
  "iat": 1709550000,
  "exp": 1709636400
}
sub
string
Subject - User ID
email
string
User email address
role
string
User role for authorization
name
string
User’s full name
iat
number
Issued at timestamp (Unix epoch)
exp
number
Expiration timestamp (Unix epoch)

Get Current User Profile

Retrieve the authenticated user’s profile information.

Endpoint

GET /api/auth/me

Authentication

Required: JWT Bearer token

Example Request

curl -X GET https://your-domain.com/api/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Success Response (200)

user
object
id
string
User ID
email
string
Email address
fullName
string
Full name
organization
string
Organization
role
string
User role
active
boolean
Account status
lastLoginAt
string
Last login timestamp
createdAt
string
Account creation timestamp
Example Response:
{
  "user": {
    "id": "507f1f77bcf86cd799439011",
    "email": "[email protected]",
    "fullName": "Dr. Jane Smith",
    "organization": "General Hospital",
    "role": "practitioner",
    "active": true,
    "lastLoginAt": "2026-03-04T10:30:00.000Z",
    "createdAt": "2025-01-15T08:00:00.000Z"
  }
}

Error Responses

401 Unauthorized

Missing token:
{
  "statusCode": 401,
  "message": "Missing bearer token"
}
Invalid or expired token:
{
  "statusCode": 401,
  "message": "Invalid or expired token"
}

404 Not Found

User not found in database:
{
  "statusCode": 404,
  "message": "User not found"
}

Using Authentication Tokens

Authorization Header Format

All protected endpoints require the JWT token in the Authorization header:
Authorization: Bearer YOUR_JWT_TOKEN

Implementation

The authenticate middleware (from /server/src/middleware/auth.js:4-20):
  1. Extracts the Authorization header
  2. Verifies it starts with “Bearer ”
  3. Extracts the token (everything after “Bearer ”)
  4. Verifies the token signature using JWT_SECRET
  5. Decodes the token payload
  6. Attaches payload to req.user for downstream handlers

Token Verification

Token verification uses jsonwebtoken library (from /server/src/utils/jwt.js:8-10):
jwt.verify(token, env.jwtSecret)
This validates:
  • Token signature
  • Token expiration
  • Token structure

Example: Making Authenticated Requests

# Store token in variable
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Use token in requests
curl -X GET https://your-domain.com/api/fhir/Patient \
  -H "Authorization: Bearer $TOKEN"

Role-Based Authorization

Beyond authentication, many endpoints require specific roles.

Available Roles

admin
string
Full system access - user management, all FHIR operations
practitioner
string
Healthcare provider - FHIR read/write operations, limited to own data
auditor
string
Read-only access - FHIR resources and audit logs

Authorization Middleware

The authorize middleware (from /server/src/middleware/auth.js:22-32) checks if the authenticated user has one of the required roles:
authorize("admin", "practitioner")

Role Requirements by Endpoint Group

FHIR Resources

From /server/src/routes/fhirRoutes.js:53-55:
  • Read Operations (GET): admin, practitioner, auditor
  • Write Operations (POST, PUT): admin, practitioner
  • Patient Write Operations: admin only

Admin Endpoints

  • User Management: admin only
  • Practitioner Directory: admin, practitioner (self-view only)
  • Audit Logs: admin, auditor

Authorization Error

If the user lacks required permissions:
{
  "statusCode": 403,
  "message": "Insufficient permissions"
}

Token Expiration

Tokens expire based on the JWT_EXPIRES_IN environment variable.

Expiration Format Examples

  • "24h" - 24 hours
  • "7d" - 7 days
  • "60m" - 60 minutes
  • "3600" - 3600 seconds

Handling Expired Tokens

When a token expires, the API returns:
{
  "statusCode": 401,
  "message": "Invalid or expired token"
}
Clients should:
  1. Detect 401 responses
  2. Prompt user to log in again
  3. Obtain a new token
  4. Retry the original request

Token Refresh Strategy

OmniEHR does not currently implement refresh tokens. Users must log in again after token expiration.
Best practices:
  • Set JWT_EXPIRES_IN to a reasonable duration (e.g., “24h” for web apps)
  • Implement client-side token expiration checks
  • Prompt for re-authentication before token expires
  • Clear stored tokens on logout

Security Best Practices

Token Storage

Never store JWT tokens in localStorage or sessionStorage if your app is vulnerable to XSS attacks.
Recommended approaches:
  1. HttpOnly Cookies: Most secure for browser apps
  2. Memory Only: Store in JavaScript variable (lost on refresh)
  3. Secure Storage: Mobile apps should use platform-specific secure storage

Password Requirements

When registering users via /api/admin/users, passwords must meet these requirements (from /server/src/services/validation.js:9-16):
  • Minimum 12 characters
  • Maximum 128 characters
  • At least one uppercase letter (A-Z)
  • At least one lowercase letter (a-z)
  • At least one digit (0-9)
  • At least one special character

HTTPS Required

Always use HTTPS in production to prevent token interception.

Token Signing

Tokens are signed using the JWT_SECRET environment variable with HMAC-SHA256 algorithm (from /server/src/utils/jwt.js:4-6). Important:
  • Use a strong, random secret (minimum 32 characters)
  • Never commit secrets to version control
  • Rotate secrets periodically
  • Use different secrets for different environments

Testing Authentication

Health Check (No Auth Required)

curl https://your-domain.com/api/health

Complete Authentication Flow

# 1. Login
RESPONSE=$(curl -s -X POST https://your-domain.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"SecurePassword123!"}')

# 2. Extract token
TOKEN=$(echo $RESPONSE | jq -r '.token')

# 3. Verify token works
curl -X GET https://your-domain.com/api/auth/me \
  -H "Authorization: Bearer $TOKEN"

# 4. Use token for FHIR operations
curl -X GET https://your-domain.com/api/fhir/Patient \
  -H "Authorization: Bearer $TOKEN"

Next Steps

FHIR Resources

Learn about FHIR resource endpoints

Admin API

User management and registration

Error Handling

Handle API errors gracefully

Rate Limits

Understand rate limiting policies

Build docs developers (and LLMs) love