Delegated Access with $drop-privilege
$drop-privilege lets a broadly privileged caller obtain a short-lived token that is scoped down to a single target identity — and that can never grant more access than the caller already had. It is designed for services that hold wide access but need to hand a narrowly-scoped credential to a less-trusted component.
A typical example: a backend Device identity with access across many organizations needs to perform work on behalf of one patient. Instead of passing its own powerful token to a downstream worker, it drops privilege to Patient/123 and passes the resulting token, which can only see and touch what both the device and that patient are allowed to.
$drop-privilege requires authentication to be enabled and the API token provider to be enabled (API_TOKENS_ENABLED=true), because the minted token is a Fire Arrow fa_ token.
Requesting a Dropped-Privilege Token
The caller issues a server-level operation:
POST /fhir/$drop-privilege HTTP/1.1
Authorization: Bearer <jwt>
Content-Type: application/fhir+json
with a Parameters body:
{
"resourceType": "Parameters",
"parameter": [
{ "name": "fhir_identity_reference", "valueString": "Patient/123" },
{ "name": "expires_at", "valueInstant": "2026-07-20T10:15:00Z" }
]
}
| Parameter | Required | Description |
|---|---|---|
fhir_identity_reference | Yes | The target identity to scope the token to, e.g. Patient/123. |
expires_at | No | Requested expiry instant. Capped as described in Token lifetime. |
The response is an RFC 8693-style Parameters resource:
{
"resourceType": "Parameters",
"parameter": [
{ "name": "access_token", "valueString": "fa_abc123def456..." },
{ "name": "token_type", "valueString": "Bearer" },
{ "name": "expires_in", "valueInteger": 300 },
{ "name": "fhir_identity_reference", "valueString": "Patient/123" },
{ "name": "on_behalf_of", "valueString": "Device/456" }
]
}
| Field | Description |
|---|---|
access_token | The short-lived dropped-privilege token (fa_ prefix). |
token_type | Always Bearer. |
expires_in | Token lifetime in seconds. |
fhir_identity_reference | The target identity the token is bound to. |
on_behalf_of | The original caller's FHIR reference, recorded for audit. |
The returned token is then used like any other bearer token:
GET /fhir/Patient/123 HTTP/1.1
Authorization: Bearer fa_abc123def456...
No Privilege Widening
The central guarantee is that a dropped-privilege token can never grant more than the caller already had. At use time, every request made with the token is authorized as the intersection of two rule sets:
- the target identity's rules — for example, the patient-compartment rules for
Patient/123, and - the original caller's rules — the rules of the
on_behalf_ofidentity that minted the token.
A request is allowed only if both rule sets allow it; if either denies, the request is denied.
For example, if the target patient's rules would permit updating a resource but the calling device has read-only access, the dropped token cannot update — the caller's constraint applies. Search results are narrowed the same way: the token returns only resources that satisfy both rule sets.
Who Can Call It
$drop-privilege is a JWT-only operation. Callers presenting an API token (fa_ / fo_) receive 403 Forbidden, and the caller's JWT must carry an exp claim.
Minting a token is gated in two steps:
- Operation permission. The caller must have an authorization rule granting the
drop-privilegeoperation on the target resource type. - Resource access. The caller must additionally be authorized to
updatethe specific target resource instance under its own rules — the check that answers "can this caller reach this resource at all?"
To grant the operation, add a rule alongside your other authorization rules (see Authorization concepts):
fire-arrow:
authorization:
validation-rules:
- client-role: Device
resource: Patient
operation: drop-privilege
validator: LegitimateInterest
The resource field must name a concrete identity resource type (Patient, Practitioner, Device, or RelatedPerson).
Token Lifetime
Dropped-privilege tokens are deliberately short-lived. The issued lifetime is the minimum of:
- the requested
expires_at(or the configured default when omitted), - the caller's JWT
expclaim — the token never outlives the JWT that authorized it, and - the configured maximum.
If the caller's JWT is close to expiring, the dropped token is correspondingly short; the caller refreshes its JWT and mints a new token.
Configuration
The settings live under the API token provider and are only needed if you use the operation:
fire-arrow:
authentication:
providers:
my-tokens:
type: api-token
enabled: true
api-token:
drop-privilege-default-expiration-minutes: 5
drop-privilege-max-expiration-minutes: 15
drop-privilege-cleanup-after-days: 14
| Property | Default | Description |
|---|---|---|
drop-privilege-default-expiration-minutes | 5 | Lifetime applied when the caller does not request one. |
drop-privilege-max-expiration-minutes | 15 | Upper bound on any dropped token's lifetime. |
drop-privilege-cleanup-after-days | 14 | How long expired dropped tokens are retained for audit before deletion. |
The supporting database columns are added automatically by Flyway on the first startup after upgrade; no operator action is required.