Lion Cipher Lab
Design least-privilege OAuth scopes for an API -- and enforce them per route with tests
- Identity & Access Management
- Intermediate
- ~60 min
- Free · no account required
Why it matters
Most APIs that use OAuth start life with one scope, often literally "api", handed to every app. It works on day one, and it means a token leaked from the most harmless app (a read-only reporting dashboard, a partner integration) can approve invoices, export the database and add admin users. The OAuth security best practice (RFC 9700) says a token's privileges should be the minimum its use case needs, precisely so a leak hurts less. Doing that well is a design job: choosing scope names, deciding which client gets which, remembering that the person behind the token has limits of their own, and…
What you'll learn
- designing resource:action OAuth scopes for a real API surface
- deciding which scopes each kind of client should be allowed to request
- explaining why effective access is the client's scopes AND the user's own permissions
- writing a route-to-required-scope map with a default-deny fallback
- returning RFC 6750 401 invalid_token and 403 insufficient_scope correctly
- writing an authorization test suite that checks denies as well as allows
- measuring the blast radius of a stolen access token per client
- spotting prefix-matching and missing-route bugs in a scope check
- judging coarse vs fine scopes, and when to move to RAR or resource indicators
Steps in this lab
- The words you need, in plain language
- The anti-pattern you are replacing: one scope called "api"
- Design the scope model on paper first
- Set up a folder and check Python
- Build the API, the tiny authorization server and the guard (api.py)
- Run it and ask it questions (call.py and mint.py)
- Pin the design down with tests (test_api.py)
- Measure the blast radius of a stolen token, per client (measure.py, section 1)
- Bug 1: a route missing from the map -- default-allow vs default-deny
- Bug 2: prefix matching lets invoices:read open invoices:read_all
- Bug 3: checking the app's scope but forgetting the person
- Judgment: how fine is fine enough, and when scopes are the wrong tool
- Honest negatives: what scopes do NOT do