Patient Impersonation
Overview
Patient impersonation allows administrators to temporarily assume the identity of a patient for support purposes. This feature is critical for customer support scenarios where a patient needs assistance but cannot complete tasks themselves (e.g., technical issues, accessibility needs, language barriers).
What is Impersonation?
Impersonation generates a time-limited session token that grants an admin the ability to act as a specific patient within the system. All actions performed during impersonation are:
- Logged to the audit trail with the impersonation_id
- Tagged with the original admin's identity for accountability
- Limited in duration (max 1 hour, non-renewable)
- Rate limited to prevent abuse
How It Works
1. Admin Initiates Impersonation
POST /v1/patients/100/impersonate
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"reason": "Patient unable to complete intake form, assisting via phone"
}2. System Validates Request
- Check admin has
adminrole - Validate reason is at least 10 characters
- Check rate limit (max 3 impersonations per 5 minutes)
- Verify patient exists and is accessible
3. Generate Impersonation Token
impersonationToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": patientUserID,
"impersonated_by": adminUserID,
"impersonation_id": impersonationID,
"exp": time.Now().Add(1 * time.Hour).Unix(),
"role": "patient",
"organization_id": orgID,
})4. Log Impersonation Event
INSERT INTO audit_log (
organization_id,
user_id,
action,
entity_type,
entity_id,
changes,
action_context
) VALUES (
1, -- organization_id
1, -- admin_user_id
'IMPERSONATE',
'patient',
100, -- patient_id
'{"reason": "..."}'::jsonb,
'impersonation'
);5. Return Impersonation Token
{
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user_id": 42,
"expires_at": "2025-01-15T11:00:00Z",
"impersonation_id": 17
}
}6. Admin Uses Token
All subsequent requests use the impersonation token:
GET /v1/appointments
Authorization: Bearer <impersonation_token>
# Returns appointments for patient (user_id: 42)
PUT /v1/forms/201
Authorization: Bearer <impersonation_token>
{
"values": {
"pain_level": "Big pain"
}
}
# Saves form as if patient filled it out7. All Actions Tagged in Audit Log
Every action during impersonation is logged with the impersonation_id:
{
"action": "UPDATE",
"entity_type": "form",
"entity_id": 201,
"user_id": 42, // Patient being impersonated
"impersonated_by": 1, // Admin who initiated
"impersonation_id": 17, // Links back to original impersonation event
"changes": {
"values": {
"pain_level": "Big pain"
}
}
}API Reference
Initiate Impersonation
Endpoint: POST /v1/patients/{id}/impersonate
Authorization: Admin only
Request:
{
"reason": "Patient unable to complete intake form, assisting via phone"
}Response: 200 OK
{
"data": {
"token": "eyJhbGciOi...",
"user_id": 42,
"expires_at": "2025-01-15T11:00:00Z",
"impersonation_id": 17
}
}Errors:
400 reason_required- Reason must be at least 10 characters403 forbidden- Non-admin attempting impersonation404 not_found- Patient doesn't exist429 rate_limited- Max 3 impersonations per 5 minutes
Security Considerations
1. Admin-Only Access
Only users with role='admin' can impersonate:
if currentUserRole != "admin" {
return errors.New("forbidden: admin role required")
}2. Mandatory Reason
Every impersonation requires a reason (min 10 characters) for the audit trail:
if len(req.Reason) < 10 {
return errors.New("reason_required: minimum 10 characters")
}This ensures accountability and makes it easy to review impersonation events later.
3. Time-Limited Tokens
Tokens expire after 1 hour and cannot be renewed:
expiry := time.Now().Add(1 * time.Hour)After expiry, the admin must initiate a new impersonation session (with a new reason).
4. Rate Limiting
Maximum 3 impersonations per 5 minutes per admin:
if impersonationCount > 3 {
return errors.New("rate_limited: max 3 impersonations per 5 minutes")
}This prevents abuse and forces admins to be deliberate about impersonation.
5. Full Audit Trail
Every action during impersonation is logged with:
user_id- Patient being impersonatedimpersonated_by- Admin who initiatedimpersonation_id- Links to original impersonation event
This creates a complete audit trail for compliance reviews.
6. Token Claims
Impersonation tokens include special claims:
{
"user_id": 42, // Patient's user ID
"impersonated_by": 1, // Admin's user ID
"impersonation_id": 17, // Audit trail link
"role": "patient", // Patient role (not admin)
"organization_id": 1,
"exp": 1642525200 // 1 hour expiry
}The backend can detect impersonation by checking for the impersonated_by claim.
Audit Logging
Impersonation Start Event
{
"id": 1001,
"organization_id": 1,
"user_id": 1, // Admin
"action": "IMPERSONATE",
"entity_type": "patient",
"entity_id": 100, // Patient
"changes": {
"reason": "Patient unable to complete intake form, assisting via phone",
"impersonation_id": 17,
"expires_at": "2025-01-15T11:00:00Z"
},
"action_context": "impersonation",
"created_at": "2025-01-15T10:00:00Z"
}Actions During Impersonation
{
"id": 1002,
"organization_id": 1,
"user_id": 42, // Patient being impersonated
"action": "UPDATE",
"entity_type": "form",
"entity_id": 201,
"changes": {
"values": {
"pain_level": "Big pain"
}
},
"impersonated_by": 1, // Admin who initiated
"impersonation_id": 17, // Links to impersonation start event
"created_at": "2025-01-15T10:05:00Z"
}Querying Impersonation Events
Find all impersonation sessions:
SELECT * FROM audit_log
WHERE action = 'IMPERSONATE'
ORDER BY created_at DESC;Find all actions during a specific impersonation:
SELECT * FROM audit_log
WHERE impersonation_id = 17
ORDER BY created_at;Find all impersonations by a specific admin:
SELECT * FROM audit_log
WHERE action = 'IMPERSONATE'
AND user_id = 1 -- Admin user ID
ORDER BY created_at DESC;Use Cases
1. Technical Support
Scenario: Patient calls support saying "I can't submit the intake form, it keeps failing"
Solution:
- Admin:
POST /v1/patients/123/impersonate { "reason": "Form submission error, debugging issue" } - Admin navigates to patient's form view
- Admin reproduces the issue and identifies the problem
- Admin fixes or escalates to engineering
2. Assisted Form Completion
Scenario: Elderly patient calls support saying "I don't know how to fill out this computer form"
Solution:
- Admin:
POST /v1/patients/123/impersonate { "reason": "Patient unable to complete intake form, assisting via phone" } - Admin guides patient through questions over the phone
- Admin fills out form on patient's behalf
- Session expires after 1 hour (or admin completes task sooner)
3. Accessibility Assistance
Scenario: Patient with vision impairment needs help booking an appointment
Solution:
- Admin:
POST /v1/patients/123/impersonate { "reason": "Vision impaired patient needs assistance booking appointment" } - Admin creates appointment on patient's behalf
- Admin confirms details with patient over the phone
4. Language Barrier
Scenario: Patient speaks limited English and needs help understanding the appointment system
Solution:
- Admin (bilingual support):
POST /v1/patients/123/impersonate { "reason": "Non-English speaking patient needs assistance" } - Admin explains the process in patient's language
- Admin performs actions on patient's behalf
Best Practices
For Admins
- Always provide a clear reason - Make it specific enough that you could explain it 6 months later in a compliance review
- Minimize session duration - Don't keep the impersonation token open longer than necessary
- Verify patient identity - Confirm you're helping the right patient before impersonating
- Document the interaction - Add notes to the patient record about what was done
For Development
- Never bypass audit logging - Every action during impersonation MUST be logged
- Enforce token expiry - Never allow token renewal - force new impersonation with new reason
- Rate limit strictly - Prevent abuse by limiting frequency
- Include reason in audit - Store the reason in the audit log for compliance
- Visual indicators in UI - Show clear "IMPERSONATING: John Doe" banner
For Compliance
- Regular audit reviews - Review impersonation logs monthly
- Investigate anomalies - Flag high-frequency impersonators for review
- Training - Ensure all admins understand when impersonation is appropriate
- Policy documentation - Maintain written policy on acceptable impersonation use cases
Debugging & Support
Check if request is impersonated
func isImpersonated(claims jwt.MapClaims) bool {
_, ok := claims["impersonated_by"]
return ok
}
func getImpersonationID(claims jwt.MapClaims) *int64 {
if id, ok := claims["impersonation_id"].(float64); ok {
id64 := int64(id)
return &id64
}
return nil
}Extract impersonation context
type ImpersonationContext struct {
PatientUserID int64
AdminUserID int64
ImpersonationID int64
}
func extractImpersonation(claims jwt.MapClaims) *ImpersonationContext {
if !isImpersonated(claims) {
return nil
}
return &ImpersonationContext{
PatientUserID: int64(claims["user_id"].(float64)),
AdminUserID: int64(claims["impersonated_by"].(float64)),
ImpersonationID: int64(claims["impersonation_id"].(float64)),
}
}Audit log helper
func logWithImpersonation(ctx context.Context, action string, entityType string, entityID int64, changes map[string]interface{}) error {
impCtx := extractImpersonation(ctx.Value("claims").(jwt.MapClaims))
log := AuditLog{
OrganizationID: getCurrentOrgID(ctx),
UserID: impCtx.PatientUserID,
Action: action,
EntityType: entityType,
EntityID: entityID,
Changes: changes,
}
if impCtx != nil {
log.ImpersonatedBy = &impCtx.AdminUserID
log.ImpersonationID = &impCtx.ImpersonationID
}
return db.Create(&log).Error
}Related Documentation
- api.md - Patient API endpoints
- README.md - Patient feature overview
- ../audit/README.md - Audit logging system
- ../auth/README.md - Authentication and authorization