S
SovereignShield
Back to Blog & Guides/HIPAA Access Control & RBAC: Technical Implementation Guide
#HIPAA#RBAC#Authentication#MFA#Security

HIPAA Access Control & RBAC: Technical Implementation Guide

Design Role-Based Access Control (RBAC), Minimum Necessary rules (§164.312(a)(1)), and mandatory Multi-Factor Authentication (MFA) for healthcare SaaS.

SovereignShield Security Architecture

HIPAA Access Control Mandate (§164.312(a)(1))

HIPAA Technical Safeguards require covered systems to:

“Implement technical policies and procedures for electronic information systems that maintain electronic protected health information to allow access only to those persons or software programs that have been granted access rights.”

Furthermore, the Minimum Necessary Rule (§164.502(b)) specifies that access to PHI must be strictly limited to the minimum amount required to fulfill a given clinical or administrative function.


1. Role-Based Access Control (RBAC) Matrix

To implement Minimum Necessary access, define granular permissions tied to explicit roles rather than granting blanket system-wide access:

Role Read PHI Edit PHI Export PHI Manage Billing Audit Logs
Attending Physician Yes (Assigned Patients) Yes No No No
Medical Billing Clerk Financial Only No Yes (Claims Only) Yes No
System Administrator No (Access Blocked) No No System Config View Only
Compliance Auditor Read-Only No Yes (Anonymized) No Full Read

2. Express/Node.js RBAC Guard Middleware

import { Request, Response, NextFunction } from 'express';

export type Permission = 
  | 'phi:read'
  | 'phi:write'
  | 'phi:export'
  | 'billing:manage'
  | 'audit:read';

const ROLE_PERMISSIONS: Record<string, Permission[]> = {
  PHYSICIAN: ['phi:read', 'phi:write'],
  BILLING: ['phi:read', 'billing:manage', 'phi:export'],
  ADMIN: ['audit:read'],
  AUDITOR: ['phi:read', 'audit:read', 'phi:export'],
};

export function authorize(requiredPermission: Permission) {
  return (req: Request, res: Response, next: NextFunction) => {
    const user = req.user; // Injected via JWT auth middleware

    if (!user || !user.role) {
      return res.status(401).json({ error: 'UNAUTHORIZED', message: 'Authentication required' });
    }

    // Verify MFA step-up verification for PHI actions
    if (!user.isMfaVerified) {
      return res.status(403).json({ 
        error: 'MFA_REQUIRED', 
        message: 'MFA verification required for ePHI access per §164.312(d)' 
      });
    }

    const permissions = ROLE_PERMISSIONS[user.role] || [];
    const hasPermission = permissions.includes(requiredPermission);

    if (!hasPermission) {
      return res.status(403).json({ 
        error: 'FORBIDDEN', 
        message: `Role ${user.role} lacks permission ${requiredPermission}` 
      });
    }

    next();
  };
}

3. Mandatory Multi-Factor Authentication (MFA) Setup

SMS-based 2FA is not recommended by NIST 800-63B due to SIM-swapping vulnerabilities. For HIPAA compliance, mandate Time-based One-Time Password (TOTP) or WebAuthn (FIDO2 / Hardware Security Keys).

TOTP Secret Generation & Verification

import { generateSecret, verifyToken } from 'node-2fa';

export function setupUserMfa(userEmail: string) {
  const secret = generateSecret({
    name: 'Healthcare Portal',
    account: userEmail,
  });

  // secret.secret: Store securely in database encrypted at rest
  // secret.qr: Base64 QR code image string for scanning with Google Auth/1Password
  return {
    secret: secret.secret,
    qrCodeUrl: secret.qr,
  };
}

export function validateMfaToken(userSecret: string, token: string): boolean {
  const result = verifyToken(userSecret, token);
  // Returns true if token is valid within the 30-second window
  return result !== null && result.delta === 0;
}

4. Session Timeout Enforcement (§164.312(a)(2)(iii))

HIPAA requires automatic logoff after a period of inactivity to prevent unauthorized access to unattended workstations.

// Front-end Inactivity Monitor (Auto Logout after 15 Minutes)
const INACTIVITY_TIMEOUT_MS = 15 * 60 * 1000;
let idleTimer;

function resetIdleTimer() {
  clearTimeout(idleTimer);
  idleTimer = setTimeout(forceLogout, INACTIVITY_TIMEOUT_MS);
}

function forceLogout() {
  sessionStorage.clear();
  window.location.href = '/login?reason=session_timeout';
}

['mousemove', 'keydown', 'click', 'scroll'].forEach((event) => {
  window.addEventListener(event, resetIdleTimer, false);
});

resetIdleTimer();