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
- Learn about Authorization & Access Control
- Explore Medict App
- Understand Medict Provider
- Review OAuth2 Reference