PageSourceSearch

https://openfga.dev/assets/js/76f66de8.85d0185c.js

js openfga.dev collected 2026-09-24 17:41:21 UTC 10,775 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkopenfga_dev=globalThis.webpackChunkopenfga_dev||[]).push([[8065],{95269(e,t,s){s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>d,default:()=>p,frontMatter:()=>o,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"content/industries/healthcare","title":"Fine-Grained Authorization for Healthcare \u2014 HIPAA, PHI, EHR","description":"Model patients, providers, encounters, and HIPAA-regulated PHI access in OpenFGA. Care teams, facility hierarchy, and sensitive-field permissions for EHR.","source":"@site/docs/content/industries/healthcare.mdx","sourceDirName":"content/industries","slug":"/industries/healthcare","permalink":"/docs/industries/healthcare","draft":false,"unlisted":false,"editUrl":"https://github.com/openfga/openfga.dev/edit/main/docs/content/industries/healthcare.mdx","tags":[],"version":"current","lastUpdatedAt":1789987177000,"sidebarPosition":2,"frontMatter":{"title":"Fine-Grained Authorization for Healthcare \u2014 HIPAA, PHI, EHR","description":"Model patients, providers, encounters, and HIPAA-regulated PHI access in OpenFGA. Care teams, facility hierarchy, and sensitive-field permissions for EHR.","sidebar_position":2,"slug":"/industries/healthcare"},"sidebar":"docs","previous":{"title":"Fine-Grained Authorization by Industry","permalink":"/docs/industries"},"next":{"title":"Banking","permalink":"/docs/industries/banking"}}');var n=s(74848),r=s(28453),a=s(20822);const o={title:"Fine-Grained Authorization for Healthcare \u2014 HIPAA, PHI, EHR",description:"Model patients, providers, encounters, and HIPAA-regulated PHI access in OpenFGA. Care teams, facility hierarchy, and sensitive-field permissions for EHR.",sidebar_position:2,slug:"/industries/healthcare"},d="Healthcare Authorization with ",l={},c=[{value:"Core resources and relations",id:"core-resources-and-relations",level:2},{value:"What the model gets right",id:"what-the-model-gets-right",level:2},{value:"Where this maps to <ProductName></ProductName> features",id:"where-this-maps-to--features",level:2},{value:"Compliance posture",id:"compliance-posture",level:2},{value:"Working sample",id:"working-sample",level:2}];function h(e){const t={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.header,{children:(0,n.jsxs)(t.h1,{id:"healthcare-authorization-with-",children:["Healthcare Authorization with ",(0,n.jsx)(a.bU,{format:a.Ed.ShortForm})]})}),"\n",(0,n.jsx)(t.p,{children:"Healthcare applications \u2014 EHR/EMR systems like Epic, Cerner, or Athenahealth, plus patient portals, telehealth, and clinical research tools \u2014 share a few hard authorization problems:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:["A single patient is seen by many providers across many facilities, and the ",(0,n.jsx)(t.em,{children:"care team"})," changes per encounter."]}),"\n",(0,n.jsx)(t.li,{children:"Some fields on a patient record (allergies, blood type, date of birth) are more sensitive than others and need a separate permission."}),"\n",(0,n.jsx)(t.li,{children:"Records like diagnoses and treatments don't have their own ACL \u2014 they inherit visibility from the encounter and patient they belong to."}),"\n",(0,n.jsx)(t.li,{children:"Facility directors need to manage facility metadata without inheriting access to every patient seen there."}),"\n"]}),"\n",(0,n.jsxs)(t.p,{children:["A relationship-based model handles all of this naturally. The full sample is in ",(0,n.jsx)(t.a,{href:"https://github.com/openfga/sample-stores/tree/main/stores/healthcare",children:"openfga/sample-stores/stores/healthcare"}),"."]}),"\n",(0,n.jsx)(t.h2,{id:"core-resources-and-relations",children:"Core resources and relations"}),"\n",(0,n.jsx)(t.p,{children:"The healthcare sample defines these object types:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:"organization"})," \u2014 the tenant (a hospital network, a clinic group). Roles: ",(0,n.jsx)(t.code,{children:"admin"}),", ",(0,n.jsx)(t.code,{children:"provider"}),", ",(0,n.jsx)(t.code,{children:"nurse"}),", ",(0,n.jsx)(t.code,{children:"medical_records"}),", ",(0,n.jsx)(t.code,{children:"member"}),"."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:"facility"})," \u2014 a physical site. Has a ",(0,n.jsx)(t.code,{children:"director"})," and a parent ",(0,n.jsx)(t.code,{children:"organization"}),"."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:"patient"})," \u2014 belongs to an ",(0,n.jsx)(t.code,{children:"organization"}),". The care team grants ",(0,n.jsx)(t.code,{children:"can_view_record"})," to assigned providers and nurses, and a separate ",(0,n.jsx)(t.code,{children:"can_view_sensitive"})," permission gates PHI fields."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:"encounter"})," \u2014 a visit. Belongs to a ",(0,n.jsx)(t.code,{children:"patient"})," and a ",(0,n.jsx)(t.code,{children:"facility"}),". Authorized providers can ",(0,n.jsx)(t.code,{children:"can_view"}),", ",(0,n.jsx)(t.code,{children:"can_create"}),", but typically not ",(0,n.jsx)(t.code,{children:"can_edit"})," after the encounter closes."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:"diagnosis"})," and ",(0,n.jsx)(t.strong,{children:"treatment"})," \u2014 inherit visibility from ",(0,n.jsx)(t.code,{children:"encounter"})," \u2192 ",(0,n.jsx)(t.code,{children:"patient"}),", so the care team automatically sees them without separate tuples."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.strong,{children:"medication"})," (formulary) \u2014 admin-only writes, everyone-reads."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"what-the-model-gets-right",children:"What the model gets right"}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Care-team scoping."})," Rather than granting a provider access to all patients in a facility, the model writes a tuple per ",(0,n.jsx)(t.code,{children:"(provider, patient)"})," care-team relationship. Revocation is one tuple delete."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"PHI as a distinct permission."})," ",(0,n.jsx)(t.code,{children:"can_view_record"})," and ",(0,n.jsx)(t.code,{children:"can_view_sensitive"})," are separate relations. A nurse on the care team can see the chart but not the sensitive fields without an explicit grant \u2014 useful when separating clinical-care views from billing or research access."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Record inheritance."})," Diagnoses and treatments use object-to-object relationships (",(0,n.jsx)(t.a,{href:"/docs/modeling/parent-child",children:"parent-child"}),") so the care team inherits access through the encounter and patient. No separate ACL maintenance on every clinical note."]}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(t.strong,{children:"Facility hierarchy."})," Facility directors are scoped to facility-level data (staff lists, equipment, schedules) \u2014 they do ",(0,n.jsx)(t.em,{children:"not"})," inherit ",(0,n.jsx)(t.code,{children:"can_view_sensitive"})," on patients seen at their facility. That separation is hard in role-only systems and easy in ",(0,n.jsx)(a.bU,{format:a.Ed.ShortForm}),"."]}),"\n",(0,n.jsxs)(t.h2,{id:"where-this-maps-to--features",children:["Where this maps to ",(0,n.jsx)(a.bU,{format:a.Ed.ShortForm})," features"]}),"\n",(0,n.jsxs)(t.table,{children:[(0,n.jsx)(t.thead,{children:(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.th,{children:"Healthcare requirement"}),(0,n.jsxs)(t.th,{children:[(0,n.jsx)(a.bU,{format:a.Ed.ShortForm})," feature"]})]})}),(0,n.jsxs)(t.tbody,{children:[(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Care-team membership per patient"}),(0,n.jsxs)(t.td,{children:["direct tuples on ",(0,n.jsx)(t.code,{children:"patient"})]})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"PHI vs. non-PHI fields"}),(0,n.jsxs)(t.td,{children:["two relations: ",(0,n.jsx)(t.code,{children:"can_view_record"}),", ",(0,n.jsx)(t.code,{children:"can_view_sensitive"})]})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Diagnosis/treatment inheritance"}),(0,n.jsx)(t.td,{children:(0,n.jsx)(t.a,{href:"/docs/modeling/parent-child",children:"parent-child relationships"})})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Multi-tenant org isolation"}),(0,n.jsxs)(t.td,{children:["tenant-scoped types, see ",(0,n.jsx)(t.a,{href:"/docs/use-cases/multi-tenant-saas",children:"multi-tenant SaaS"})]})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Time-bounded access (rotations, locums)"}),(0,n.jsx)(t.td,{children:(0,n.jsx)(t.a,{href:"/docs/interacting/contextual-tuples",children:"contextual tuples"})})]}),(0,n.jsxs)(t.tr,{children:[(0,n.jsx)(t.td,{children:"Break-glass access logging"}),(0,n.jsxs)(t.td,{children:["tuple writes flow through the ",(0,n.jsx)(t.a,{href:"/docs/interacting/read-tuple-changes",children:"Read Changes API"})]})]})]})]}),"\n",(0,n.jsx)(t.h2,{id:"compliance-posture",children:"Compliance posture"}),"\n",(0,n.jsxs)(t.p,{children:[(0,n.jsx)(a.bU,{format:a.Ed.ShortForm})," itself isn't a HIPAA product \u2014 compliance is a property of how you run it. What ",(0,n.jsx)(a.bU,{format:a.Ed.ShortForm})," does give you:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:["A single, queryable source of truth for ",(0,n.jsx)(t.em,{children:"who can see which patient record"})," at any moment, which audit programs typically require."]}),"\n",(0,n.jsx)(t.li,{children:"Append-only tuple change history via the Read Changes API."}),"\n",(0,n.jsx)(t.li,{children:"Separation between checks (read-only) and writes (admin-only) at the API level."}),"\n"]}),"\n",(0,n.jsxs)(t.p,{children:["For the operational side \u2014 encryption at rest, logging, deployment in a HIPAA boundary \u2014 see ",(0,n.jsxs)(t.a,{href:"/docs/best-practices/running-in-production",children:["Running ",(0,n.jsx)(a.bU,{format:a.Ed.ShortForm})," in Production"]}),"."]}),"\n",(0,n.jsx)(t.h2,{id:"working-sample",children:"Working sample"}),"\n",(0,n.jsxs)(t.p,{children:["The full schema, sample tuples, and assertion tests for the model above live in ",(0,n.jsx)(t.a,{href:"https://github.com/openfga/sample-stores/tree/main/stores/healthcare",children:"openfga/sample-stores/stores/healthcare"}),". You can load it with the ",(0,n.jsx)(t.a,{href:"/docs/getting-started/cli",children:"FGA CLI"})," and run the included assertions to see the model behave end-to-e
1nd."]})]})}function p(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.