Skip to content

Patient records and who is allowed to see them

  • Home
  • Blog
  • Patient records and who is allowed to see them
Patient records and who is allowed to see them

Patient record access control is the set of rules, enforcement points and audit trails that decide which user, service or integration may read or change a specific patient's data. It is enforced at three layers at once: identity, application policy, and the database itself — usually row-level rules plus an append-only access log.

Key Takeaways

  • A login and a role dropdown is not access control. If a nurse can query the whole patients table, your policy lives in the UI and nowhere else.
  • Enforce the rule as close to the data as you can. Row-level security in Postgres survives a careless developer forgetting a WHERE patient_id = ... clause. A middleware check does not.
  • RBAC answers "what job does this person do", ABAC answers "should this person see this row right now". Most clinics need both, layered.
  • Break-glass access is mandatory in real care settings and dangerous if unlogged. Make it time-boxed, loud and reviewed.
  • The audit trail is the deliverable. If you cannot answer "who opened patient 4471's file last Tuesday", you cannot pass an audit or investigate an incident.
  • Most breaches in health systems come from stale accounts and over-broad roles, not exotic exploits.
  • Deprovisioning is part of access control, not an HR afterthought.
How one patient record read request is authorisedFive ordered stages: request, identity, policy decision, row filtering and audit write, connected by arrows.One read request, five checkpoints1Requestarrives2Identityresolved3Policydecides4Rowsfiltered5Auditentry written
A single patient record read passes five checkpoints: the request arrives, identity is resolved, policy decides, the database filters rows, and an audit entry is written.

What does patient record access control actually cover?

Access control covers three separate questions: who the user is, what that user may do with one specific record, and what proof exists afterwards that the read happened. A system that answers only the first — a login page and a role dropdown — is a guest list, not a control. The other two are where production incidents come from.

In practice you are building four things: an identity source that reflects employment reality, a policy model that maps people to records, an enforcement point the application cannot bypass, and an audit log that survives someone trying to clean up after themselves. Miss any one and the remaining three are decoration.

Why does patient record access control matter in production?

Health data breaches usually trace back to over-permissioned accounts and stale credentials, not clever attacks. A receptionist account that can still log in eight months after the person left is a breach waiting for a password reset email. Regulators treat the audit trail as evidence: if you cannot show who viewed a record, you are assumed not to know.

There is a clinical angle too. Care teams share patients across departments, shifts and sometimes across institutions. Lock access down too hard and a night-duty doctor cannot treat a patient in front of them. Open it too wide and every staff member can read the whole register. The job is finding the narrow band where care still happens and exposure stays small.

Do you need attribute-based rules, or is role-based access enough?

Role-based access control works when the answer depends only on the job: a radiographer sees imaging, a billing clerk sees invoices. Attribute-based rules are needed when the answer depends on the relationship — this doctor, this patient, this shift, this consent flag. Most clinical systems end up combining both, with roles as the coarse filter and attributes as the row-level check.

Four access control models and what each one is good atRows comparing RBAC, ABAC, row-level security and break-glass access by what they enforce.Which model enforces whatRBACJob title decides the menu; fast to build, blind to relationshipsABACDepartment, shift and consent checked on every single readRow securityDatabase filters rows per session; a forgotten filter returns nothingBreak-glassTime-boxed emergency override that pages a reviewer afterwards
How the four common access control models differ in what they actually enforce at the point of a patient record read.

How does the enforcement mechanism work end to end?

A request arrives with a session token. The application resolves that token to a user identity and a set of roles — from your identity provider, an internal table, or an SSO directory. It then asks a policy layer a narrow question: may this identity act on this resource. The policy engine returns a decision, the request carries that identity into the database session, and the database applies row filters.

The critical detail is the last hop. If the application sets a session variable such as app.user_id on the connection, a row-level policy can read it and filter rows automatically. That way, even a query written without a patient filter returns only the rows the caller is entitled to see. For external integrations, the same logic usually arrives as scopes — SMART on FHIR defines scope strings like patient/*.read or user/Observation.read — and you map those scopes to the same policy layer rather than writing a second implementation.

How do you implement patient record access control step by step?

  1. Model the relationships first. Write down who needs to see which records and why. Care team membership, department, referring clinician, consent flags and emergency override are the usual attributes. Do this on paper before touching a schema.
  2. Pick the identity source. Prefer your existing directory so that a leaver is disabled in one place. Give every service and integration its own identity; never share a human account with an API client.
  3. Add the access tables. A care_team or patient_access table with (user_id, patient_id, reason, granted_at, expires_at) covers most of the relationship cases and is easy to query and audit.
  4. Enforce at the database. Enable row-level security on the clinical tables and write a policy per operation. Test on a replica or a restored copy before you touch production.
  5. Make the application set identity per request. Use a transaction-scoped setting so pooled connections cannot leak one user's identity into the next request.
  6. Write the audit entry in the same transaction as the read. If the read succeeds and the audit insert fails, the read should fail. An audit log that drops rows silently is worse than none.
  7. Add break-glass. A separate endpoint that grants a short-lived override, records the stated reason, and notifies a reviewer. Log it as its own event class so it is easy to report on.
  8. Wire deprovisioning. When someone leaves or changes role, their grants expire automatically. Manual removal does not happen at 6pm on a Friday.

Which configuration details break in real deployments?

Connection pooling is the classic one. If you set the user identity with SET LOCAL inside a transaction, transaction-level pooling behaves correctly. If you set it with a plain SET on a session that is later reused, the next request inherits the previous user's identity. That is a silent cross-patient data leak and it will not show up in a functional test.

The second is table ownership. In Postgres, the table owner bypasses row security by default unless you force it, so a migration or admin script running as the owner sees everything. Decide deliberately who runs as owner and keep that credential out of application code. Enable the policy on the actual clinical tables, not a view that some queries bypass.

ALTER TABLE encounters ENABLE ROW LEVEL SECURITY;
ALTER TABLE encounters FORCE ROW LEVEL SECURITY;

CREATE POLICY care_team_read ON encounters
  FOR SELECT
  USING (
    patient_id IN (
      SELECT patient_id FROM care_team
      WHERE user_id = current_setting('app.user_id')::uuid
    )
  );

Enabling row security on a live table changes what every existing query returns the moment it commits. Restore a recent backup into a staging database, apply the policy there, and run your real query set against it first. The PostgreSQL row security policy documentation is the reference for the exact semantics.

How do you verify that access control actually works?

Verify with negative tests, not by clicking through the UI as an admin. Sign in as each role and try to fetch a record you should not see, directly against the API rather than the interface. Confirm you get an empty result or a 403, not a partial record. Then query the audit table and check the read was recorded with the right user, patient and timestamp.

Run a quarterly access review: export every active grant, send it to the department lead, and require an explicit confirmation. Anything unconfirmed gets revoked. Pair it with an access audit process that also covers the surrounding systems — the scheduling tool, the shared drive, the reporting database — because patient data leaks sideways as often as it leaks outward.

What are the common failure modes and how do you debug them?

Start with the symptom. "Doctor cannot see the patient" is usually a missing care team row, an expired grant, or an identity that resolved to a different user ID than the one in the access table. Check the policy decision log first, then the grant table, then the identity mapping. Nine times out of ten it is the third.

"Everyone can see everything" is almost always enforcement that never applied: row security enabled on the wrong table, the app connecting as the table owner, or a reporting user with a broad role. Run the query as the application's own database user, not as your admin account, and see what comes back.

"The audit log has gaps" usually means writes are fire-and-forget or the log table allows updates and deletes. Revoke update and delete on the audit table from the application role, and check that a failed audit write actually rolls back the read.

What does this cost to run, in money and attention?

The engineering cost sits mostly in the modelling and the migration, not the code. Row-level policies are a few dozen lines; deciding which relationships matter takes workshops with clinical staff. Expect ongoing attention rather than a one-off build: access reviews every quarter, policy changes whenever a department reorganises, and audit retention storage that grows with read volume.

Infrastructure drivers are ordinary — database size, audit table growth and retention, and any separate policy service you choose to run. If you want a figure for your own environment, work it through your cloud vendor's own calculator, or talk to our team about scoping the build.

Which security details do teams get wrong?

The first mistake is treating the UI as the enforcement point. Hiding a button does nothing when the API is one curl command away. The second is the shared service account: once three integrations use one credential, your audit log can no longer tell you which system read the record.

The third is a break-glass path with no review. Emergency access is genuinely necessary, but an override that nobody reads is a permanent backdoor with better branding. The fourth is deprovisioning. Offboarding is a security control, and removing system access when staff leave should be a checklist item on the last working day, not a task someone remembers a month later.

What does this look like in a real clinic rollout?

Picture a five-site clinic group running a custom patient management system. Every staff account sits in one role called "clinical", so anyone can open any patient. The first change is not technical: the team maps who genuinely needs to see what, and discovers that the physiotherapy department only needs patients referred to it.

Then the build. A care_team table is populated from existing appointment data, row-level policies go onto the encounters and notes tables, and the application sets identity per request. Two weeks of staging testing later, a night-duty doctor hits a patient outside their team, uses break-glass with a reason, and gets access for thirty minutes. The reviewer sees it the next morning. Nobody loses the ability to treat anyone, and for the first time the group can answer who looked at which file.

How do the access control models compare?

Choose the model that matches the question you are actually asking. RBAC is cheap to build and easy to explain, and it is the right starting point for job-shaped permissions. Row-level security is where you enforce the relationship, and it is the layer that survives developer error. Break-glass is the pressure valve that keeps clinicians from sharing passwords because the system got in their way.

ModelAnswersEnforced whereOperational overhead
RBACWhat does this job need?Application roles and menusLow — review role assignments
ABACShould this person see this row now?Policy service or query layerMedium — attributes must stay accurate
Row-level securityWhich rows may this session read?Inside the databaseMedium — migrations and pooling care
Break-glassWho overrode the rule, and why?Separate endpoint plus auditHigh — every use needs review
The life of one access grant from joining to offboardingA horizontal timeline showing grant creation, role change, emergency override, access review and revocation.The life of one access grantJoinedgrant createdRole changescope re-checkedBreak-glassoverride reviewedOffboardedaccess revokedAccess reviews run between every stage, not only at the end
A single access grant moves through joining, role change, emergency override and offboarding — with access reviews running between each stage.

In short

Patient record access control is three things working together: an identity source that reflects reality, a policy enforced at the database rather than the interface, and an audit trail you would be willing to hand to a regulator. Get the modelling right first — the code is the easy part.

People also search for

If you are building or inheriting a patient-facing system and the access rules currently live in the UI, our team can help you model the permissions, enforce them at the database, and ship the audit trail. Start with a look at our custom software development work, see how we handled a data-heavy platform in our analytics institute case study, or get in touch to talk through your setup.

Frequently asked questions

  • It is the set of rules deciding which users, systems and integrations can read or change a patient record, and under what conditions. Practical implementations combine assigned roles (clinician, front desk, billing), attribute checks such as care-team membership, and an audit trail recording who opened which record and when. Access is denied by default and granted explicitly.

  • Under HIPAA, access follows treatment, payment and health-care operations, plus any purpose the patient authorises in writing. Map each workforce role to the minimum data set it needs rather than granting full-chart view. Verify by pulling an access report per role and confirming nobody sees fields outside their function.

  • HIPAA's minimum necessary rule at 45 CFR 164.502(b) requires that uses and disclosures be limited to the least information needed for the purpose. Enforce it in the application layer with scoped queries and field-level filtering, not only in the UI. Audit logs should show the query scope, not just the page visited.

  • Prerequisites are a single identity source, agreed role definitions, and a documented approval path. Create roles with least privilege, require MFA for any account that can reach records, wire deprovisioning to HR exit events, and log every grant. Test with a low-privilege account before going live.

  • Break-glass is an emergency override letting a clinician open a record outside their normal role, usually with a typed reason. It should be time-limited, alert the privacy officer immediately, and be reviewed retrospectively. Log the override separately so routine access reports still show normal patterns.

  • Test with accounts at each role level, confirm a request outside scope returns a denial, and confirm the denial is written to the audit log. Reconcile the application's user list against the HR roster quarterly. Review logs for out-of-hours access, VIP records and same-surname lookups.

  • Role sprawl. People change teams, roles get copied from a colleague, contractors keep accounts after the contract ends, and shared logins hide who did what. Each change adds a permission nobody removes. Fix with periodic access reviews, named accounts only, and deprovisioning tied to the HR system.

  • Capture who accessed which record, when, from what IP or device, what action was taken, and the stated purpose where the workflow requires one. Logs must be tamper-evident and retained per policy; HIPAA documentation retention is six years at 45 CFR 164.316(b)(2)(i). Storage and SIEM ingestion costs scale with retention and query volume, so set both deliberately.

  • Missing object-level authorisation. A list endpoint is protected but a direct record ID is not, so changing the ID returns another patient's chart. Exported CSV and PDF reports bypass UI restrictions, and misconfigured object storage or backup buckets expose files. Test every endpoint with another patient's identifier.

  • Role-based access control is the default. Attribute-based control adds checks such as care-team membership or shift, and suits organisations with fuzzy roles, though it needs clean identity data. Delegated and consent-based models fit patient-facing portals. Most production systems run a hybrid: roles for broad scope, attributes for the last mile. Our team can help you choose at /contact.

0 comments

Be the first to share your thoughts.

Leave a comment

Chat on WhatsApp