---
title: 'The patient-held authorization server: UMA in practice - LifeCare.ID®'
description: "How a patient-held authorization server works: an authorization server separate from every data holder, tokens issued only against the person's own consent grant and bound to a purpose, validated by introspection, dead within half a second of revocation. The UMA 2.0 pattern as LifeCare.ID runs it, with sample requests."
canonical: https://lifecare.id/authorization-server
generated: 2026-10-08
---
The patient-held authorization server: UMA in practice - LifeCare.ID®

[LifeCare.ID®](https://lifecare.id/)

Reference

# The patient-held authorization server: UMA in practice

In most of healthcare the organisation that holds your data also decides who may read it. A patient-held authorization server separates the two: the holders keep the data, and the decision comes from a server that answers to you. This page describes how LifeCare.ID® runs that server today.

Published 4 September 2026. Describes the implementation serving the LifeCare network; the contract is published and drift-tested.

## What an authorization server is, and why the patient should hold it

In OAuth terms, an authorization server is the party that issues access tokens, and a resource server is the party that holds the data and honours those tokens. User-Managed Access, the Kantara Initiative's extension of OAuth 2.0, adds the idea that matters for health data: the two servers can belong to different parties, and the resource owner, the person, sets the policy the authorization server applies. A hospital, a lab or a device maker can hold the bytes and still not be the one who decides.

"Patient-held" means the policy is the person's own consent, recorded as a [consent receipt](https://lifecare.id/consent-receipt) they can read and revoke, and the identity behind every decision is their decentralized identifier, controlled by a key in their device. The server itself runs at the person's home node, the node of the trusted provider that anchors their identifier. The person holds the key and the consent; the provider runs the machine.

## The three parties

| Party | In the LifeCare network today | What it holds |
|---|---|---|
| Authorization server | LifeCare.ID, at the home node | DIDs, public keys, grants and receipts, token hashes, the revocation registry. No health data. |
| Resource server | The node that holds the person's raw data and runs their agents; later a lab, a device maker, an EHR | The data. It serves a request only after introspecting the token. |
| Requester | An app such as ProForta, or an agent on the node acting for the person | A token, minimum-scope and time-boxed, and only the interpreted outputs the resource server returns. |

The invariant the whole design rests on: for any data value held by a resource server and delivered to a requester, the transfer is direct. The authorization server is never an intermediary, cache or proxy of the value.

## Getting a token

A requester asks for a token by naming the person, one scope and a purpose. The server looks for an active grant by that person covering that scope whose declared purpose matches; purpose is compared on its Data Privacy Vocabulary term when both sides carry one. No grant, no token. A newer grant on the same scope for a different purpose cannot shadow the one asked for, because purpose is part of the lookup rather than a check after it.

```
POST /v1/token
{
  "subject_did": "did:web:lifecare.id:u:8f2c1a9e0b7d6c54",
  "scope": "cgm",
  "purpose": "weekly health synthesis",
  "purpose_dpv": "dpv:PersonalisedRecommendation",
  "requester": "node0-synthesis-agent"
}

200
{
  "access_token": "lc_…",
  "token_type": "Bearer",
  "scope": "cgm",
  "consent_id": "urn:lifecare:consent:9b1e4c0f2a7d4e8b8c11d3f5a6b7c8d9",
  "expires_at": "2026-09-04T09:32:31Z"
}

403  { "detail": "no active consent for scope 'cgm' with purpose 'weekly health synthesis'" }
```

Four properties of the token that comes back:

- **One scope.** A token names a single data category. An agent that needs two categories holds two tokens, each revocable on its own.

- **Time-boxed twice.** It expires at the earlier of twenty minutes from issue and the grant's own expiry. A long grant never produces a long-lived token.

- **Opaque, hashed at rest.** The token is a random string with a fixed prefix. The server stores its SHA-256, so a copy of the server's database issues nothing.

- **Ledgered.** Issuing it appends a line to the person's ledger: "node0-synthesis-agent obtained a cgm access token. Purpose: weekly health synthesis."

## Validating a token: introspection, three shapes

A resource server never decodes a token. It presents it to the authorization server and acts on the answer. The answer is one of four shapes, and the resource server branches on `token_type`, failing closed on any shape it does not implement.

The resource server asks as itself. Following RFC 7662, section 2.1, introspection requires the caller to authenticate with its own client credentials: a caller with none is refused with `401`, and a person's session or a holder signature in place of a client credential with `403`, both before the token is looked up. Holding a token string is not enough to learn whose data it opens.

| Shape | Meaning | Carries |
|---|---|---|
| active: false | Expired, revoked, its grant revoked, or unknown. Four causes, one answer, so a caller learns nothing from the difference. | Nothing else. |
| token_type: "user" | The session of a signed-in person. The caller is this person. | subject_did, expires_at |
| token_type: "access" | A data-access token issued to a requester against a grant. The holder is an app or an agent, not the person, so it never carries a subject. | resource_owner, scope, purpose, requester, consent_id, expires_at |
| token_type: "delegated" | Defined and not yet issued: an actor exercising a subset of a person's authority, with the person, the actor and the parent grant all named. Published now so every resource server learns to branch on it before the first one is minted. | subject_did, actor_did, parent_grant, trust tier |

```
POST /v1/token/introspect   { "token": "lc_…" }
Authorization: Basic base64(client_id:client_secret)

{
  "active": true,
  "token_type": "access",
  "resource_owner": "did:web:lifecare.id:u:8f2c1a9e0b7d6c54",
  "scope": "cgm",
  "purpose": "weekly health synthesis",
  "requester": "node0-synthesis-agent",
  "consent_id": "urn:lifecare:consent:9b1e4c0f2a7d4e8b8c11d3f5a6b7c8d9",
  "expires_at": "2026-09-04T09:32:31Z"
}
```

The distinction between a user token and an access token is what lets a resource server refuse an app that tries to act as the person. Granting or revoking a person's own consent is accepted only against a user token whose subject matches; an access token presented for the same action is refused with 403.

## Revocation

When the person revokes a grant, every token issued under it is invalidated in the same transaction, in under half a second, and the ledger records how many. The next introspection of any of them answers `active: false`. Resource servers that introspect on every request need no notification. Those that cache decisions consult the public revocation registry under the person's identifier, a plain list of revoked consent ids with a timestamp:

```
GET /v1/revocation/8f2c1a9e0b7d6c54

{
  "subject_did": "did:web:lifecare.id:u:8f2c1a9e0b7d6c54",
  "revoked_consents": ["urn:lifecare:consent:…"],
  "updated_at": "2026-09-04T10:02:11Z"
}
```

## Who may ask

The `requester` in a token request is a string the person will read in their ledger, so the server must know who actually called. Two forms of caller authentication are implemented, each verified whenever presented and each required when the deployment is configured to require it:

- **A holder signature**: a resource holder such as a node signs the request with its own DID key, ES256 over the method, the path, a timestamp and the SHA-256 of the body, in a `X-LifeCare-Signature` header naming the key. The server resolves the holder's DID document, verifies the signature, and holds the `requester` claim to the signer.

- **Confidential-client credentials**: an app registered with the server authenticates with its client secret, the same way it federates people in over OAuth 2.0 with PKCE.

The same gate protects grant creation, alongside the person's own device proof over the grant. The two gates are separate on purpose: who is calling and who approved it are different questions.

## How a node uses it: agents never hold the source

On a LifeCare node the resource server and the requesters share a machine, and the pattern still holds. Agents never receive the raw data source. The runtime hands each agent a guarded accessor restricted to the scopes its definition declares, from the same fixed vocabulary the authorization server knows. Every fetch through that accessor acquires a token for the person, the scope and the agent's purpose, and records the access in the person's ledger. An agent that asks outside its declared scope gets a permission error before any data is read.

The node validates the tokens apps present to its own API the same way: through a chain of validators that ends in an introspection call to the authorization server. A user token carries the person; an access token carries the app; an unknown shape is refused.

## What is UMA 2.0 here, and what is collapsed

Kept from User-Managed Access 2.0: the separation of authorization server and resource server; the resource owner's policy applied by the authorization server; minimum-scope, time-boxed tokens; validation by introspection; revocation at the authorization server. Collapsed: the permission-ticket round trip. In UMA the resource server first obtains a ticket from the authorization server and hands it to the requester, who exchanges it for a token. Here the person's grant already exists as a receipt, so a requester asks for the token directly by naming the person, scope and purpose, and the grant lookup plays the role of the ticket. The result is one round trip instead of three, with the same policy applied. GNAP-style delegation chains, where an agent exercises a subset of a person's authority, are the defined-and-unissued third token shape above.

## What the server never sees

Scopes are categories: `cgm`, `daily`, `labs`, `body_composition`, `recovery`, `activity`, `insights`. A token request names a category and a purpose; an introspection answers with identifiers, a category, a purpose and a time. The authorization server participates only in the set {DID, public key, token, scope, consent receipt, revocation status}. A full compromise of it yields token hashes, public keys and consent metadata, and not one measurement. The data it authorises access to never passes through it.

## Lineage and further reading

- [User-Managed Access 2.0 Grant for OAuth 2.0 Authorization](https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html) and [Federated Authorization for UMA 2.0](https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-federated-authz-2.0.html), Kantara Initiative Recommendations.

- [OpenID HEART](https://openid.net/wg/heart/), the Health Relationship Trust working group that profiled OAuth 2.0, OpenID Connect and UMA for patient-directed health data exchange; the lineage this design follows.

- [RFC 7662, OAuth 2.0 Token Introspection](https://www.rfc-editor.org/rfc/rfc7662), and [RFC 8693, Token Exchange](https://www.rfc-editor.org/rfc/rfc8693), whose actor-claim semantics the delegated shape follows.

- [HIE of One](https://en.wikipedia.org/wiki/HIE_of_One), Adrian Gropper's open-source patient-controlled UMA authorization server, the earliest working precedent for a patient holding the authorization decision over their own records.

- [The consent receipt](https://lifecare.id/consent-receipt): the policy this server applies, and [Verify a node](https://lifecare.id/verify) with [how its attestation works](https://lifecare.id/attestation): how to check what a resource server says it runs.

- [The paper: design and early operation](https://lifecare.id/paper), the preprint that puts the receipt, the tokens, the enforcement and the attestation in one document, with the design rationale and the limits.

**Deployment note.** Caller authentication and the device proof on grants are implemented and verified whenever presented; whether an unauthenticated call is refused is a deployment setting, so a network can turn it on for every holder at once. The delegated token shape is in the published contract and no code path issues it yet.
