# Authentication and OAuth

> API keys, OAuth 2.1 with PKCE (SMART on FHIR standalone launch), OpenID Connect id_token, SMART v2 patient scopes, token lifetimes and the consent lifecycle.

Source: https://developers.anpheros.com/guides/authentication

Anpheros Platform accepts three kinds of credentials. Every request carries exactly one.

| Credential | Looks like | Who | Sees |
|---|---|---|---|
| API key | `sk_test_…` (sandbox) / `sk_live_…` (production) | a project's server | the patients the project created (in sandbox, including its own copy of 30 synthetic patients) |
| OAuth access token | `at_…` | an application acting for one patient under a consent | that patient only, within the granted SMART scopes |
| Firebase ID token | JWT | a developer in the dashboard, or a patient in "Connections" | their own organisations / their own records |

Send it as `Authorization: Bearer <credential>` (API keys also work as `X-Api-Key`).

## API keys
- Created in the dashboard or via `POST /admin/projects/{id}/keys`; shown once; stored as a SHA-256 hash.
- Scopes: `read`, `write`. A key never crosses its project's environment (`sk_test_` cannot reach production data and vice versa).
- Per-key rate limit (default 600/min) is enforced platform-wide (shared store), with `Retry-After` on 429. There is also a per-IP limit of 600/min.
- Optional headers on writes: `X-Anpheros-Author-Type`, `X-Anpheros-Author-Ref`, `X-Anpheros-Source-System`, `X-Anpheros-Origin-Id`, `X-Anpheros-Acting-As` (a Practitioner/PractitionerRole **of your own directory**), `X-Anpheros-On-Behalf-Of` (an Organization of your own directory). They populate Provenance.

## OAuth 2.1 (SMART on FHIR, standalone launch)
Discovery: `GET /.well-known/smart-configuration`.

1. `GET /oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=…&state=…&code_challenge=…&code_challenge_method=S256[&purpose=treatment][&patient=<pairwise id>][&lang=ro]`
   Shows the hosted consent page. PKCE S256 is mandatory; the `redirect_uri` must be registered exactly.
2. The patient signs in (Google or email via Firebase), chooses the record (own or a dependent's), the duration (30/90/180/365 days) and allows. The platform issues a one-time code (10 minutes).
3. `POST /oauth/token` with `grant_type=authorization_code&code=…&code_verifier=…&redirect_uri=…&client_id=…` (confidential clients add `client_secret`).
   Response: `access_token` (30 min), `refresh_token` (30 days, rotating), `patient` (your pairwise id for the record), `grant_id`, `scope`.
4. Refresh: `grant_type=refresh_token`. Each refresh token is single-use. **Presenting a used refresh token revokes the whole chain** (theft detection). A parallel duplicate of a refresh request is refused and may also revoke the chain, so never rotate concurrently.
5. Revoke: `POST /oauth/revoke` (token + client_id). Revoking a refresh token also revokes its access tokens.

Token responses carry `Cache-Control: no-store` and `Pragma: no-cache`, as SMART requires.

### OpenID Connect (`openid fhirUser`)
Ask for `openid fhirUser` alongside the patient scopes and the token response also contains an `id_token`: a JWT signed with RS256, with `iss` (the platform's base URL), `sub` (your pairwise id for the person), `aud` (your `client_id`), `exp`, `iat` and `fhirUser` — the absolute URL of the person's Patient resource, readable with the access token you just received. Discovery lives at `GET /.well-known/openid-configuration`; the public keys at `GET /oauth/jwks` (`kid` in the token header). The `nonce` parameter is not supported yet (it is optional in the authorization-code flow). `launch/patient` and `offline_access` are accepted; the `patient` field of the token response is the launch context.

Other grant types: `client_credentials` (confidential clients; behaves like an API key), and the first-party identity grant (trusted clients only).

### Scopes
SMART v2 patient scopes: `patient/<Type>.<cruds>` with an optional `?category=<code>` filter (the only filter the server enforces; any other filter is refused at authorize time). `patient/*.rs` reads everything; `offline_access` adds a refresh token. Patient demographics and the directory are always readable under a grant. A resource type outside the grant returns 403; a patient outside the grant returns 404.

### Consent lifecycle
Consents expire automatically, can be revoked by the patient at any time (Connections screen / `/me`), and every read under a consent is logged with the application id. Webhooks `consent.granted` / `consent.revoked` notify the project.

## Errors
401 unknown/expired credential · 403 the credential exists but may not do this · 404 the resource does not exist *for you* · 429 rate limited (`Retry-After`). See [`errors.md`](https://developers.anpheros.com/guides/errors).
