This is a submission for the Permit.io Authorization Challenge: Permissions Redefined

What We Built

MedRecord is a modern, privacy-first medical records management system that empowers doctors and patients with secure access to health data while giving administrators full oversight. We tackled the problem of data access control in healthcare - ensuring that only authorised users can read, update, or manage sensitive medical records.

With Permit.io, we implemented Attribute-Based Access Control (ABAC) and Role-Based Access Control (RBAC), allowing

  • Admins to manage the entire system
  • Doctors can view all records but update only their assigned patients
  • Patients to view only their personal records

By decoupling permissions logic from our codebase, we built a scalable and maintainable system that’s easy to audit and extend.

Demo

🔗 Live Demo: Live Demo https://medrecord.vercel.app

Project Repo

🔗 GitHub – Walidadebayo/medrecord

Medical Records Management Application

A full-stack medical records management application built with Next.js, MongoDB, and Permit.io for role-based access control.

Features

  • Role-Based Access Control: Admin, doctor, and patient roles with different permissions
  • MongoDB Integration: Persistent data storage with MongoDB
  • File Uploads: Upload and manage medical documents using Vercel Blob
  • Advanced Search: Filter records by date, doctor, patient, and more
  • Responsive Design: Works on desktop and mobile devices
  • Modern UI: Clean interface with gradient styling

Setup Instructions

Prerequisites

  • Node.js 18+ and npm
  • MongoDB database (local or Atlas)
  • Vercel account (for Blob storage)
  • Permit.io account

Environment Variables

Create a .env.local file in the root directory with the following variables:

# MongoDB
MONGODB_URI=your_mongodb_connection_string

# Authentication
JWT_SECRET=your_jwt_secret_key

# Permit.io
PERMIT_API_KEY=your_permit_api_key

# Vercel Blob
BLOB_READ_WRITE_TOKEN=your_vercel_blob_token

Installation

  1. Clone the repository
  2. Install dependencies:
npm install
Enter fullscreen mode Exit fullscreen mode
  1. Run the development server:
npm run dev
Enter fullscreen mode Exit fullscreen mode
  1. Open http://localhost:2500 in your browser

Testing Different Roles

The…

Tech Stack: Next.js, TailwindCSS, MongoDB (Mongoose), Permit.io, Docker, Render, Vercel Blob (for files storage)

Login

Login

Create Record
Create Record

Dashbaord
Dashboard

Record Details
Record Details

Viewing Record Attachments

Viewing Record Attachments

Edit Record
Edit Record

Our Journey

This project was built by a small but dedicated team of developers: @walidadebayo (frontend/backend), @volde (frontend/backend). Our main challenge was integrating Permit.io’s PDP (Policy Decision Point) in a production-like environment.

Challenges We Faced

  • PDP Hosting: Initially, we ran Permit’s PDP locally, but this didn’t scale. We learned how to containerise it using Docker and deploy it to Render.
  • Error 422 from Permit: We encountered recurring authorization errors from Permit (especially code 422) due to missing resources and users. We resolved this by programmatically registering all users, resources, and attributes during app setup.
  • Fine-Grained ABAC Logic: We built a fallback RBAC+ABAC function in Node.js to complement Permit’s decisions when no matching usersets were found.

Key Learnings

  • How to containerise a Node-less PDP using only Permit’s Docker image
  • How to define and manage resource types, actions, roles, and relations in Permit
  • How to write and test policies for both RBAC and ABAC scenarios
  • How to log and debug authorization failures using Permit’s SDK and API responses

Using Permit.io for Authorization

Here’s how we integrated Permit.io into MedRecord:

  1. User Provisioning: Upon signup, users are registered in Permit with attributes like role, name, and email. Roles like doctor, patient, and admin are assigned using Permit’s SDK.
await permit.api.createUser({
      key: user.id.toString(),
      email: user.email,
      first_name: user.name.split(" ")[0],
      last_name: user.name.split(" ").slice(1).join(" "),
      attributes: {
        role,
      },
      role_assignments: [
        {
          role: user.role,
          resource_instance: "medical_record",
          tenant: "default",
        },
      ],
    });
  1. Resource Registration: When a new medical record is created, it is registered as a Permit resource instance with attributes like doctor_name and patient_name, so ABAC policies can reference them.

Resources

const resourceDefinition = {
  key: "medical_record",
  name: "Medical Record",
  description: "A patient’s medical record",
  actions: {
    read: { name: "Read", description: "Read the record" },
    update: { name: "Update", description: "Update the record" },
    delete: { name: "Delete", description: "Delete the record" },
    create: { name: "Create", description: "Create a new record" },
  },
  attributes: {
    doctor_id: { type: "string", description: "ID of the assigned doctor" },
    patient_id: { type: "string", description: "ID of the patient" },
  },
  roles: {
    admin: {
      permissions: ["read", "update", "delete", "create"],
    },
    doctor: {
      permissions: ["read", "update"],
    },
    patient: {
      permissions: ["read"],
    },
  },
};

const response = await permit.api.createResource(resourceDefinition);

 await permit.api.resourceInstances.create({
          key: record._id.toString(),
          resource: "medical_record",
          attributes: {
            patient_name: record.patient_name,
            doctor_name: record.doctor_name,
          },
        });
  1. Authorization Check:

    • Before showing or updating a record, we make a call to permit.check(...)
    • If Permit allows, access proceeds.
    • If no userset matches (e.g., newly created users), we fall back to a local RBAC+ABAC check to prevent access while still showing meaningful logs.
  2. Policy Hosting: The PDP is containerised via Docker and deployed to Render, exposing it via a secure endpoint that our app uses.

// Example Permit Check
const allowed = await permit.check({
  user: currentUserId,
  action: "update",
  resource: {
    type: "medical_record",
    key: recordId,
    attributes: {
      doctor_name: "Dr. Sarah Johnson",
      patient_name: "Emily Davis"
    }
  },
})

Permit’s CLI and dashboard helped us manage roles, write policies, and test everything without needing a custom permission system.

Team Members

This project was built by a team of two:

We truly enjoyed building this project and learned a lot about modern access control systems. Thanks to the Permit.io team for providing powerful tools and excellent documentation!