>Welcome to MedictManageAuthentication & Identity

Authentication & Identity

Learn how to implement secure authentication and identity management in your Medict applications.

Overview

Medict provides comprehensive authentication and identity management using industry-standard OAuth 2.0 and OpenID Connect protocols. This ensures secure access to healthcare data while maintaining compliance with healthcare regulations.

Authentication Methods

OAuth 2.0 Authorization Code Flow

The recommended authentication method for web applications:

import { MedictClient } from '@medplum/core';

const client = new MedictClient({
  baseUrl: 'https://api.medplum.com',
  clientId: 'your-client-id',
  redirectUri: 'https://your-app.com/callback'
});

// Initiate OAuth flow
function login() {
  client.signInWithRedirect({
    clientId: 'your-client-id',
    redirectUri: 'https://your-app.com/callback',
    scope: 'openid profile email'
  });
}

// Handle OAuth callback
async function handleCallback() {
  try {
    const profile = await client.handleCodeExchange();
    console.log('User authenticated:', profile);
  } catch (error) {
    console.error('Authentication failed:', error);
  }
}

PKCE (Proof Key for Code Exchange)

Enhanced security for public clients:

// Generate PKCE parameters
const codeVerifier = client.generateCodeVerifier();
const codeChallenge = await client.generateCodeChallenge(codeVerifier);

// Sign in with PKCE
client.signInWithRedirect({
  clientId: 'your-client-id',
  redirectUri: 'https://your-app.com/callback',
  codeChallenge: codeChallenge,
  codeChallengeMethod: 'S256'
});

Client Credentials Flow

For server-to-server authentication:

const client = new MedictClient({
  baseUrl: 'https://api.medplum.com',
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret'
});

// Authenticate with client credentials
await client.authenticateWithClientCredentials();

User Management

Creating Users

// Create a new user account
const user = await client.createResource({
  resourceType: 'User',
  email: 'user@example.com',
  password: 'secure-password',
  profile: {
    reference: 'Practitioner/practitioner-id'
  }
});

User Profiles

// Get current user profile
const profile = await client.getProfile();

// Update user profile
const updatedProfile = await client.updateResource({
  ...profile,
  name: [{
    use: 'official',
    family: 'Smith',
    given: ['John']
  }]
});

Password Management

// Change password
await client.changePassword({
  currentPassword: 'old-password',
  newPassword: 'new-secure-password'
});

// Reset password
await client.resetPassword('user@example.com');

Role-Based Access Control

Defining Roles

// Create a role
const role = await client.createResource({
  resourceType: 'Role',
  name: 'Physician',
  description: 'Full access to patient data',
  permissions: [
    {
      resource: 'Patient',
      actions: ['read', 'write', 'delete']
    },
    {
      resource: 'Encounter',
      actions: ['read', 'write']
    }
  ]
});

Assigning Roles

// Assign role to user
const userRole = await client.createResource({
  resourceType: 'UserRole',
  user: {
    reference: 'User/user-id'
  },
  role: {
    reference: 'Role/role-id'
  },
  organization: {
    reference: 'Organization/org-id'
  }
});

Checking Permissions

// Check if user can access resource
const canAccess = await client.checkPermission('Patient', 'read', 'patient-id');

if (canAccess) {
  const patient = await client.readResource('Patient', 'patient-id');
}

Multi-Factor Authentication

Enabling MFA

// Enable MFA for user
await client.enableMFA({
  userId: 'user-id',
  method: 'totp' // or 'sms', 'email'
});

MFA Verification

// Verify MFA token
const verified = await client.verifyMFA({
  userId: 'user-id',
  token: '123456'
});

if (verified) {
  console.log('MFA verification successful');
}

Session Management

Token Management

// Get current access token
const token = client.getAccessToken();

// Refresh token
const newToken = await client.refreshToken();

// Check token validity
const isValid = client.isTokenValid();

Session Storage

// Custom session storage
const client = new MedictClient({
  baseUrl: 'https://api.medplum.com',
  clientId: 'your-client-id',
  storage: {
    getItem: (key) => localStorage.getItem(key),
    setItem: (key, value) => localStorage.setItem(key, value),
    removeItem: (key) => localStorage.removeItem(key)
  }
});

Identity Providers

SAML Integration

// Configure SAML identity provider
const samlProvider = await client.createResource({
  resourceType: 'IdentityProvider',
  type: 'saml',
  name: 'Corporate SAML',
  configuration: {
    entityId: 'https://your-saml-provider.com',
    ssoUrl: 'https://your-saml-provider.com/sso',
    x509Certificate: '-----BEGIN CERTIFICATE-----...'
  }
});

OIDC Integration

// Configure OIDC identity provider
const oidcProvider = await client.createResource({
  resourceType: 'IdentityProvider',
  type: 'oidc',
  name: 'Google OIDC',
  configuration: {
    issuer: 'https://accounts.google.com',
    clientId: 'google-client-id',
    clientSecret: 'google-client-secret'
  }
});

Security Best Practices

1. Use HTTPS

Always use HTTPS in production:

const client = new MedictClient({
  baseUrl: 'https://api.medplum.com', // Always use HTTPS
  clientId: 'your-client-id'
});

2. Implement Token Refresh

// Automatic token refresh
client.on('tokenRefresh', (newToken) => {
  console.log('Token refreshed:', newToken);
});

// Manual token refresh
setInterval(async () => {
  if (client.isTokenExpiring()) {
    await client.refreshToken();
  }
}, 300000); // Check every 5 minutes

3. Secure Storage

// Use secure storage for sensitive data
const secureStorage = {
  getItem: (key) => {
    // Use encrypted storage
    return decrypt(localStorage.getItem(key));
  },
  setItem: (key, value) => {
    // Encrypt before storing
    localStorage.setItem(key, encrypt(value));
  },
  removeItem: (key) => {
    localStorage.removeItem(key);
  }
};

4. Input Validation

// Validate user input
function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

function validatePassword(password) {
  return password.length >= 8 && 
         /[A-Z]/.test(password) && 
         /[a-z]/.test(password) && 
         /[0-9]/.test(password);
}

Error Handling

Authentication Errors

try {
  await client.signInWithRedirect();
} catch (error) {
  switch (error.code) {
    case 'invalid_client':
      console.error('Invalid client configuration');
      break;
    case 'invalid_grant':
      console.error('Invalid credentials');
      break;
    case 'access_denied':
      console.error('User denied access');
      break;
    default:
      console.error('Authentication error:', error.message);
  }
}

Token Errors

try {
  const resource = await client.readResource('Patient', 'patient-id');
} catch (error) {
  if (error.code === 'unauthorized') {
    // Redirect to login
    client.signInWithRedirect();
  } else if (error.code === 'forbidden') {
    // Show access denied message
    console.error('Access denied');
  }
}

Testing Authentication

Unit Tests

// Mock authentication for testing
const mockClient = new MedictClient({
  baseUrl: 'https://api.medplum.com',
  clientId: 'test-client-id'
});

// Mock successful authentication
mockClient.authenticate = jest.fn().mockResolvedValue({
  access_token: 'mock-token',
  token_type: 'Bearer'
});

// Test authentication flow
test('should authenticate user', async () => {
  const result = await mockClient.authenticate();
  expect(result.access_token).toBe('mock-token');
});

Integration Tests

// Test with real authentication
test('should handle OAuth callback', async () => {
  const client = new MedictClient({
    baseUrl: 'https://api.medplum.com',
    clientId: 'test-client-id'
  });
  
  // Simulate OAuth callback
  const profile = await client.handleCodeExchange('mock-code');
  expect(profile).toBeDefined();
});

Compliance Considerations

HIPAA Compliance

  • Use encrypted connections (HTTPS)
  • Implement proper access controls
  • Log all authentication events
  • Use strong password policies

Audit Logging

// Log authentication events
client.on('authenticate', (user) => {
  console.log('User authenticated:', user.id, new Date());
});

client.on('logout', (user) => {
  console.log('User logged out:', user.id, new Date());
});

Next Steps