Skip to main content

Healthcare Demo Application

Run the Galactic Health Corporation (GHC) demo app and see how one healthcare app combines three permission models in Permit.io. This worked example is for developers who design authorization for apps with delegated access, such as patients who share records with caregivers.

GHC is a Next.js sample app set in the universe of "Rick and Morty". The app uses:

note

The code is in the Galactic-Health-Corporation repository. For a longer walkthrough, read Building healthcare authorization with Next.js on the Permit blog.

Galactic Health Corporation features

GHC app key features

Every feature below maps to a resource, action, or role that scripts/setupPermit.js creates in your Permit environment.

FeatureWhat it does in GHC
Member data ownershipEach member holds the owner resource role on their own member instance, which derives owner on their profile, health plan, and medical records.
Member groupsA group administrator holds member_group#admin and assigns other users the member_group#org_member role. Users in a group read each other's profiles.
Delegated caregiver accessA member grants another user the caregiver resource role on their own member data. The caregiver then reads that member's health plan and medical records.
Time-limited delegationA caregiver_bounds user attribute holds a start and end date per delegation. Custom Rego denies the caregiver role outside that range.
Benefits pilot groupThe Benefits Pilot Group role grants view on the benefits_pg resource. The Personal Benefits card on the plan page renders only for users who hold that role.

Run GHC locally

Prerequisites

Set up the application

  1. Clone the repository and change into it:

    git clone https://github.com/permitio/Galactic-Health-Corporation.git
    cd Galactic-Health-Corporation
  2. In the root folder, create a file named .env.local and copy the content of .env.example into it:

    NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
    NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
    NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
    NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/

    NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="<your clerk publishable key>"
    CLERK_SECRET_KEY="<your clerk secret key>"
    PERMIT_SDK_KEY="<your permit sdk key>"
    PERMIT_PDP_URL=http://localhost:7766
  3. In the Clerk dashboard, open API Keys, choose the Next.js example, and copy the NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY values into .env.local.

  4. Copy your Permit environment API key into the PERMIT_SDK_KEY value in .env.local. PERMIT_SDK_KEY is the variable name the app reads; the value is the environment API key.

  5. Run npm install to install the dependencies.

Seed the policy in Permit

From the repository root, run npm run config. The command runs scripts/setupPermit.js, which creates the resources, actions, resource roles, relations, and role derivations listed in ReBAC requirements.

The seed script deletes the environment's policy first

scripts/setupPermit.js starts by deleting every resource, resource relation, resource role, role, and user in the environment the API key points at. Point PERMIT_SDK_KEY at an empty environment, or you lose the policy and directory data already there.

Verify: In the Permit dashboard, open the Policy screen and the Resources tab. Six resources appear: member, profile, health_plan, medical_records, member_group, and benefits_pg. On the Roles tab, the Benefits Pilot Group role appears.

Start the PDP and the app

  1. Start the PDP container. Replace <YOUR_PERMIT_API_KEY> with your environment API key. The command maps the PDP's port 7000 to port 7766 on your machine, which matches PERMIT_PDP_URL.

    docker run -it \
    -e PDP_API_KEY=<YOUR_PERMIT_API_KEY> \
    -p 7766:7000 \
    -p 8081:8081 \
    permitio/pdp-v2:latest
  2. In a new terminal, from the repository root, run npm run dev.

  3. Open http://localhost:3000.

Verify: the browser redirects to the Clerk sign-in page at /sign-in, and the PDP terminal logs a policy update from Permit.

RBAC and ReBAC model

GHC builds most of its policy with ReBAC, and uses one top-level RBAC role for the benefits pilot group.

Entities

EntityDefinition
ResourceA type of object you manage access to. Each resource can have many instances, such as one member's health plan.
ActionAn operation a user can perform on a resource. The policy allows or denies each action.
RelationA named link between two resource types. GHC uses parent (from member) and belongs (from member_group).
Relationship tupleOne instance of a relation between two resource instances, such as member_group:smith_family belongs profile:profile_user_rick.
Resource roleA role that applies to instances of one resource type. Permit writes it as Resource#Role, for example health_plan#caregiver.
Role derivationA rule that grants a resource role to users who hold another resource role on a related instance. In GHC, roles on a member instance derive roles on that member's profile, health plan, and medical records.
Top-level roleA role assigned in a tenant rather than on an instance. GHC has one: Benefits Pilot Group.

ReBAC requirements

  • Users read their own profile, health plan, and medical records.
  • Users read the profiles of other users in the same member group.
  • Users delegate read access to their profile, health plan, and medical records to another user, for a specific date range.
  • Users in the benefits pilot group see the Personal Benefits card.

scripts/setupPermit.js creates this model. Resource and role keys in the table are the keys the app checks against:

Resource (key)ActionsResource rolesRelationsRole derivations
Member (member)read, writeowner (read, write), caregiver (read)Subject of parent on profile, health_plan, and medical_recordsNone. Both roles are assigned directly on a member instance.
Profile (profile)read, writeowner (read, write), caregiver (read)parent from member, belongs from member_groupowner from member#owner via parent. caregiver from member#caregiver via parent and from member_group#org_member via belongs.
Health Plan (health_plan)readowner (read), caregiver (read)parent from memberowner from member#owner and caregiver from member#caregiver, both via parent.
Medical Records (medical_records)readowner (read), caregiver (read)parent from memberowner from member#owner and caregiver from member#caregiver, both via parent.
Member Group (member_group)list, assignadmin (list, assign), org_member (list)Subject of belongs on profileNone. Both roles are assigned directly on a member_group instance.
Benefits (benefits_pg)viewNoneNoneNone. The top-level Benefits Pilot Group role (benefits_pg_role) grants benefits_pg:view.

Instance keys follow the pattern <resource>_<user id>. The health plan of user user_rick is the instance health_plan:health_plan_user_rick.

The profile caregiver role has no date bounds

profile#caregiver is derived from member_group#org_member as well as from member#caregiver, so every user in a member group can read the profiles of the other users in that group. The custom Rego rule in ABAC model for time-limited delegation skips the profile resource for that reason. Date bounds apply to health plans and medical records.

API endpoints

GHC's Next.js API routes run the following checks. An endpoint with no check filters its results with the Permit API instead of calling permit.check().

EndpointWhat it returnsCheckResource roles that allow it
GET /api/account/profile/{user}One user's profile detailsread on profile:profile_{user}profile#owner, profile#caregiver
GET /api/account/dashboard/health-plan/{user}One user's health planread on health_plan:health_plan_{user}health_plan#owner, health_plan#caregiver
GET /api/account/dashboard/medical-records/{user}One user's medical recordsread on medical_records:medical_records_{user}medical_records#owner, medical_records#caregiver
GET, POST, DELETE /api/account/caregiverThe signed-in user's caregivers, and grant or revoke a delegationwrite on member:member_{signed-in user}member#owner
POST /api/account/member-groups/assign, POST /api/account/member-groups/unassign, DELETE /api/account/member-groupsAdd or remove a group member, or delete a groupassign on member_group:{group}member_group#admin
GET /api/account/dashboard/health-benefitsOne decision for the action and resource in the query stringThe action and resource from the requestWhichever role grants that action. permit-fe-sdk calls this route.
GET /api/account/memberThe users in the groups the signed-in user administersNone. The route lists admin and org_member role assignments through the Permit API.Not applicable
GET /api/account/member-groups, POST /api/account/member-groupsList the signed-in user's groups, or create a groupNoneNot applicable
GET /api/allowed-plansThe users whose plans the signed-in user can openNone. The route lists the signed-in user's caregiver role assignments through the Permit API.Not applicable

Test the caregiver flow

Test the ReBAC model with three users: Rick, Morty, and Bird Person.

  1. Sign up for three GHC accounts: rick@sanchez.app, morty@smith.app, and bird@person.app.

    note

    To use other email addresses, edit the user keys in the Permit Directory.

  2. Sign in as rick@sanchez.app. Rick's plan page shows only Rick's own data, because each user holds member#owner on their own member instance.

  3. Create a member group and add Morty and Bird Person to it. Adding a user to a group creates a belongs relationship tuple between the group and that user's profile.

  4. Open the Delegate Permissions Wizard. The wizard lists the users in Rick's member groups: Morty and Bird Person.

  5. Share the health plan and medical records with Morty, and set a date range that includes today. The wizard assigns Morty the caregiver role on health_plan:health_plan_<rick> and medical_records:medical_records_<rick>.

  6. Sign in as Morty. In the Shared Access section, Morty sees Rick's health plan and medical records.

  7. Sign in as Bird Person and open Rick's plan.

Verify: each user sees the following.

UserWhat the user seesWhy
RickHis own profile, health plan, and medical recordsRick holds member#owner, which derives owner on his profile, health plan, and medical records.
MortyRick's health plan and medical records under Shared AccessRick assigned Morty caregiver on those instances, and the read permission comes with that resource role.
Bird PersonRick's profile, but not Rick's health plan or medical recordsBird Person's member_group#org_member role derives profile#caregiver. No derivation reaches Rick's health plan or medical records, so GET /api/account/dashboard/health-plan/<rick> returns 403 with {"error":"Not allowed"}.

To see the RBAC half of the model, assign the Benefits Pilot Group role to Rick on the Directory screen and reload the plan page. The Personal Benefits card appears for Rick and for no one else, because permitState.check('view', 'benefits_pg') returns true only for holders of that role.

The second pilot group needs its own resource

The plan page also checks view on a resource named alt_medicine_pg for the Alternative Medicine Recommendations card. scripts/setupPermit.js doesn't create that resource, so the card stays hidden until you create the resource, a view action, and a role that grants it.

ABAC model for time-limited delegation

ABAC requirements

A delegation must be limited to a date range. For example, Rick gives Morty access to his health plan and medical records for one week only.

Date boundaries on role assignments

Morty's caregiver resource role comes from the relationship between Rick's member data and Morty. A resource role assignment has no expiry of its own, so GHC bounds it with a user attribute and a custom Rego rule.

GHC stores the bounds in a caregiver_bounds user attribute on the caregiver. Each key in caregiver_bounds is the resource instance the caregiver was granted access to, and each value holds an RFC 3339 start_date and end_date. The POST /api/account/caregiver handler writes this attribute with permit.api.users.update() before it assigns the caregiver role:

{
"key": "user_morty_smith",
"attributes": {
"caregiver_bounds": {
"health_plan:health_plan_user_rick_sanchez": {
"start_date": "2030-05-01T09:00:00.000Z",
"end_date": "2030-05-08T09:00:00.000Z"
},
"medical_records:medical_records_user_rick_sanchez": {
"start_date": "2030-05-01T09:00:00.000Z",
"end_date": "2030-05-08T09:00:00.000Z"
}
}
}
}

The custom Rego rule in scripts/custom.rego allows the derived caregiver role only while the current time falls inside those bounds:

enforce_boundries(resource) {
time.now_ns() >= time.parse_rfc3339_ns(abac.attributes.user.caregiver_bounds[resource].start_date)
time.now_ns() <= time.parse_rfc3339_ns(abac.attributes.user.caregiver_bounds[resource].end_date)
}

time.parse_rfc3339_ns rejects any value that is not an RFC 3339 timestamp, so write the dates in the format shown above. The Delegate Permissions Wizard in GHC produces them with Date.prototype.toISOString().

Implementation options

You can bound a role by time in Permit in two ways:

OptionHow it worksWhen to use it
Condition set in the Permit dashboardA user set matches the user when an expiry attribute is still ahead of a current_time attribute that your app passes in each check. See Time Based Role Example.You bound a top-level role, and you can add the attributes through the dashboard.
Custom Rego policyYou write Rego that Permit combines with the generated policy, and the PDP evaluates both. See Write custom policies with GitOps.You bound a derived resource role, which condition sets don't reach.

GHC uses the custom Rego option, because the caregiver role is derived through a relationship rather than assigned directly. The generated and custom policy code is in the ghc-demo-policy repository.

Connect Permit to a policy repository with GitOps

Custom Rego lives in the policy repository, so connect Permit to a Git repository first. Permit writes the generated policy to that repository, and you change the custom policy through branches and pull requests. See Connect a GitHub repository to Permit for the setup steps.

Add the custom policy

In the branch of your policy repository that Permit syncs for your environment:

  1. Replace the content of custom/root.rego with the content of scripts/custom.rego from the GHC repository.

  2. Edit the top-level root.rego file. By default, root.rego has two allow rules, one for policies.allow (the generated policy) and one for custom.allow, so access is allowed if either rule allows it. Remove the second allow rule, and put both conditions in one allow rule so that both must pass:

    allow {
    policies.allow
    custom.allow
    }

For more on custom Rego in Permit, see Write custom policies with GitOps.

Test the time-limited delegation

Run the caregiver flow from Test the caregiver flow again, with two date ranges.

  1. In the Delegate Permissions Wizard, share Rick's health plan with Morty and set a range that includes today.
  2. Sign in as Morty and open Rick's plan.
  3. Sign back in as Rick, revoke the delegation, and share the health plan again with a range that ends before today.
  4. Sign in as Morty and open Rick's plan again.

Verify: with the range that includes today, Morty sees Rick's health plan. With the range that ends before today, GET /api/account/dashboard/health-plan/<rick> returns 403 with {"error":"Not allowed"} and the Shared Access section shows no health plan. The generated policy still grants health_plan#caregiver, and custom.allow denies the request because enforce_boundries fails.

The PDP evaluates custom Rego only after GitOps syncs it

The custom rule runs only once Permit has synced the policy repository branch for your environment and the PDP has pulled it. Until then, the PDP evaluates the generated policy alone, and a delegation whose date range has passed still grants access. Check the sync status in Settings, then GitOps, before you test.

Next steps