Learn by Directing AI
Unit 3

Protected Routes and RBAC

Step 1: Build authorization middleware

You have working authentication -- users can register, log in, and get a secure session cookie. But authentication alone is not enough. Right now, any logged-in user can access any endpoint. A nurse can request clinical notes. An admin can pull up medication histories. The system knows who the user is, but it doesn't check what they're allowed to do.

Open materials/guides/auth-guide.md and read the RBAC Patterns section. The pattern you need is middleware that runs before every protected route handler and checks two things: is the user authenticated (valid session), and does their role permit this specific action?

Direct Claude to build the authorization middleware. Be explicit about what it must check:

Build authorization middleware for the patient portal API routes. The middleware must check both authentication AND authorization on every protected route. A valid session is not sufficient -- the middleware must also verify that the user's role permits the specific action. If the session is missing or invalid, return 401. If the session is valid but the role doesn't permit the action, return 403.

The distinction between 401 and 403 matters. 401 means "you haven't identified yourself." 403 means "I know who you are, and you're not allowed." A nurse who requests clinical notes should get 403, not 401 -- she's authenticated, just not authorised for that resource.

AI commonly generates middleware that checks authentication but not authorization. The middleware verifies "is someone logged in?" and passes the request through. It never checks the role. This looks correct in testing because the logged-in user happens to be a doctor. It fails silently when a nurse makes the same request.

Step 2: Implement protected API routes

With the middleware ready, apply it to the patient record routes. The access rules from your PRD define what each role can access:

  • Doctors: Full clinical records, clinical notes (read/write), care plans (read/write), vitals (read/write), patient contact info (read), scheduling (read/write)
  • Nurses: Care plans (read/write), vitals (read/write), scheduling (read), patient contact info (read). No access to clinical notes.
  • Admin: Scheduling (read/write), patient contact info (read/write), clinic statistics. No access to clinical data.

Direct Claude to implement these routes with the authorization middleware applied. Each route should specify which roles are permitted. The middleware checks the user's session role against the required role for that endpoint.

While Claude generates the routes, watch for rate limiting on sensitive endpoints. Login, password reset, and registration endpoints should limit how many requests a single IP can make in a time window. AI commonly generates auth flows without rate limiting -- which means an attacker can try thousands of passwords per second. Also check that error messages on login don't reveal whether the email exists in the system. "Invalid email or password" is safe. "No account found for that email" tells an attacker which emails are registered.

Step 3: Test adversarially

This is the most important step in this unit. Everything before this was construction. This is verification -- and not the kind where you confirm things work. This is where you confirm things fail correctly.

Log in as a nurse. Then, using curl, Postman, or the browser's DevTools console, make a direct HTTP request to a doctor-only endpoint -- something like /api/patients/:id/clinical-notes. Include the nurse's session cookie in the request.

If the response is 403 Forbidden, the authorization is working. The server checked the nurse's role, found it insufficient for clinical notes, and denied the request.

If the response is 200 OK with the clinical notes data, the authorization is broken. The nurse's button may be hidden in the UI, but the data is served to anyone who constructs the right HTTP request. This is OWASP's number one web application vulnerability -- Broken Access Control.

Test every boundary in the access control matrix. A nurse requesting doctor-only data. An admin requesting clinical data. An unauthenticated request (no session cookie at all) to any protected endpoint. Each should return the correct denial -- 403 for wrong role, 401 for no session.

This adversarial approach -- testing as the wrong role, not just the right one -- catches failures that happy-path testing never will. Testing that a doctor can see clinical notes proves nothing about whether a nurse is also seeing them.

Step 4: Document the access control model

Open materials/templates/test-strategy-template.md. You'll use the test strategy template later in Unit 5, but for now, notice the Auth-Specific Test Scenarios table. The scenarios you just tested manually -- wrong role, unauthenticated, correct role -- are exactly the scenarios that need automated tests.

Now document the access control model. This is a security obligation, not a nice-to-have. Write a clear document that specifies:

  • Which roles exist and what each can access
  • Where authorization is enforced (the API middleware, not the frontend)
  • What happens at the boundaries (401 for unauthenticated, 403 for wrong role)
  • Where the trust boundary sits -- the server is the enforcement point, the client is below the trust boundary
  • Which endpoints are protected and which roles each permits

This documentation is what lets another developer -- or your future self -- understand and maintain the security model. An undocumented auth model is an unmaintainable auth model. When someone adds a new endpoint six months from now, the documentation tells them which middleware to apply and which roles to permit.

✓ Check

✓ Check: Log in as a nurse. Use curl or Postman to request a doctor-only endpoint (e.g., /api/patients/:id/clinical-notes). The response should be 403 Forbidden, not 200 OK.