Skip to content

Last updated:

Security Overview

CPR handles sensitive medical data -- patient records, billing information, prescription details, and surgical schedules. Security is a fundamental requirement, not an optional enhancement.

Medical Data Sensitivity

CPR stores and displays Protected Health Information (PHI) including:

  • Patient personal details (name, birthdate, contact information, address)
  • Medical records (visual acuity, refraction, tonometry, funduscopy, surgical history)
  • Prescription data (medicines, dosages, frequencies)
  • Billing and insurance information
  • Visit history and doctor notes

All of this data must be protected from unauthorized access, both in transit (HTTPS) and at rest (server-side encryption).

Authentication Architecture

CPR uses a Bearer token authentication system that supports both JWT and Laravel Sanctum tokens:

  1. User submits username and password to the API (POST /auth/login).
  2. The API returns a user object and a token.
  3. The token and user data are stored in sessionStorage.
  4. Every subsequent API request includes the token in the Authorization: Bearer <token> header.
  5. The auth middleware checks token validity on every route navigation.

See Authentication for detailed flow documentation.

Permission System

CPR implements a role-based and permission-based access control system. The AuthUser type includes both roles and granular permissions:

ts
export interface AuthUser {
  id: number;
  name: string;
  username: string;
  email: string;
  phone?: string;
  role?: string[];           // e.g., ['doctor', 'admin']
  permissions?: string[];    // e.g., ['view_patients', 'create_billing']
  default_branch?: Branch;
}

Permission checking utilities are defined in app/types/permission.type.ts:

ts
export interface PermissionUtils {
  can: PermissionChecker;        // can('view_patients')
  cannot: PermissionChecker;     // cannot('delete_billing')
  canAny: MultiPermissionChecker; // canAny(['edit_patients', 'admin'])
  canAll: MultiPermissionChecker; // canAll(['view_patients', 'create_billing'])
  hasRole: RoleChecker;          // hasRole('doctor')
  hasAnyRole: MultiRoleChecker;  // hasAnyRole(['doctor', 'admin'])
}

Permission Enforcement

Permissions should be checked at two levels:

  1. Frontend -- Hide or disable UI elements the user cannot access. This is a UX improvement, not a security boundary.
  2. Backend -- The API must validate permissions on every request. The frontend check can be bypassed, so the server is the source of truth.
vue
<template>
  <!-- Only show delete button if user has permission -->
  <UiButton
    v-if="can('delete_patient')"
    variant="danger"
    @click="confirmDelete"
  >
    Delete Patient
  </UiButton>
</template>

XSS Prevention

Vue provides built-in protection against Cross-Site Scripting (XSS) by automatically escaping template interpolation. See XSS Prevention for details.

Secure Token Storage

Why sessionStorage

CPR uses sessionStorage instead of localStorage for token storage:

sessionStoragelocalStorage
PersistenceTab onlyAcross tabs and sessions
Cleared on tab closeYesNo
Shared workstation riskLower (session ends)Higher (persists)
XSS exposure windowCurrent sessionUntil manually cleared

In clinical settings, workstations are often shared among staff. sessionStorage ensures that closing the browser tab ends the session, reducing the risk of unauthorized access.

Token Security Best Practices

  1. Never store tokens in cookies accessible to JavaScript (unless HttpOnly).
  2. Check token expiry before every route navigation (CPR's auth middleware does this for JWTs).
  3. Clear all auth data on logout -- both Pinia store state and sessionStorage.
  4. Do not log tokens -- Production builds strip console.* calls, but avoid logging tokens even in development.

Production Security Configuration

CPR's nuxt.config.ts includes security-relevant production settings:

ts
// Drop console and debugger in production
vite: {
  esbuild: {
    drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
  },
},

// Source maps exist for Sentry but are not served to users
sourcemap: {
  client: 'hidden',
},

Sentry for Security Monitoring

CPR integrates Sentry for error monitoring. Authentication errors are captured with context:

ts
// From auth.sevice.ts
catch (err: unknown) {
  const fetchError = err as FetchError;
  const message = fetchError.data?.message ?? fetchError.message ?? 'Something went wrong';
  Sentry.captureException(err);
  throw new Error(message);
}

This allows the team to detect:

  • Unusual authentication failure patterns (brute force attempts)
  • Unexpected API errors that may indicate security issues
  • Client-side errors that could expose sensitive data

Security Checklist

ConcernCPR Implementation
Token storagesessionStorage (tab-scoped)
Token transmissionAuthorization: Bearer header
Token expiry checkAuth middleware checks JWT exp claim
Console output in prodStripped via esbuild.drop
Source maps in prodHidden (Sentry only)
XSS protectionVue template escaping
Error monitoringSentry integration
Permission systemRole + permission-based checks
HTTPSEnforced at infrastructure level

CPR - Clinical Patient Records