Skip to content

Commit 465694f

Browse files
authored
Document full JWT authentication support (#1098)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 057e7b6 commit 465694f

1 file changed

Lines changed: 81 additions & 9 deletions

File tree

docs/configuration.md

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,12 @@ contains the schema collections they own.
324324
Authentication is only available in the [Enterprise](commercial.md)
325325
edition. Learn more about [commercial licensing](commercial.md).
326326

327-
Authentication currently supports a single policy type, `apiKey`, where a
328-
consumer is granted access by presenting a pre-shared key. Support for OpenID
329-
Connect and JWT is planned. Anything not covered by a policy stays public, so
330-
the configuration only ever describes what to protect, never what to expose.
331-
When a path is governed by more than one policy, the policies are unioned.
327+
Authentication supports two policy types. An `apiKey` policy grants access to a
328+
consumer that presents a pre-shared key, and a `jwt` policy grants access to a
329+
consumer that presents a signed JSON Web Token, verified against the issuer's
330+
published key set. Anything not covered by a policy stays public, so the
331+
configuration only ever describes what to protect, never what to expose. When a
332+
path is governed by more than one policy, the policies are unioned.
332333

333334
**The UNIX model**: Visibility and access are kept separate, following the UNIX
334335
filesystem model. A policy that governs a directory does not erase it from its
@@ -353,20 +354,28 @@ A policy governs a [Collection](#collections) or [Page](#pages), or a namespace
353354
above them (the instance root governs everything). It cannot gate an individual
354355
path inside a collection: a collection is either public or private as a whole.
355356

357+
Every policy declares its `type`, a `name`, and the `paths` it governs,
358+
regardless of type:
359+
356360
| Property | Type | Required | Default | Description |
357361
|-----------------|------|----------|---------|-------------|
358-
| `/type` | String | :red_circle: **Yes** | N/A | The policy type. Currently only `apiKey` is supported |
362+
| `/type` | String | :red_circle: **Yes** | N/A | The policy type, either `apiKey` or `jwt` |
359363
| `/name` | String | :red_circle: **Yes** | N/A | The policy name, surfaced in directory listings. Must consist of lowercase letters, digits, and hyphens. The name `public` is reserved |
360-
| `/algorithm` | String | :red_circle: **Yes** | N/A | How a presented key is compared against the stored keys. Either `identity` (the environment variable holds the key verbatim) or `sha256` (the environment variable holds the lowercase hexadecimal SHA-256 digest of the key) |
361364
| `/paths` | Array | :red_circle: **Yes** | N/A | The registry paths this policy governs, each rooted at `/`. Every path must be `/` itself (governing the whole instance) or name a known collection, page, or route |
362-
| `/keys` | Array | :red_circle: **Yes** | N/A | The keys this policy accepts, each read from an environment variable so that secrets never live in the configuration file |
363-
| `/keys/*/environmentVariable` | String | :red_circle: **Yes** | N/A | The name of the environment variable that holds the key, or its hash when `algorithm` is not `identity` |
364365

365366
### API Key
366367

367368
Consumers present a key through the `Authorization` header using the `Bearer`
368369
scheme ([RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750)).
369370

371+
An `apiKey` policy declares the following additional properties:
372+
373+
| Property | Type | Required | Default | Description |
374+
|-----------------|------|----------|---------|-------------|
375+
| `/algorithm` | String | :red_circle: **Yes** | N/A | How a presented key is compared against the stored keys. Either `identity` (the environment variable holds the key verbatim) or `sha256` (the environment variable holds the lowercase hexadecimal SHA-256 digest of the key) |
376+
| `/keys` | Array | :red_circle: **Yes** | N/A | The keys this policy accepts, each read from an environment variable so that secrets never live in the configuration file |
377+
| `/keys/*/environmentVariable` | String | :red_circle: **Yes** | N/A | The name of the environment variable that holds the key, or its hash when `algorithm` is not `identity` |
378+
370379
!!! tip
371380

372381
To consume schemas from a gated instance in your projects, take a look at
@@ -438,6 +447,69 @@ pre-hashed one:
438447
WAF](https://www.gravitee.io/blog/rate-limiting-throttling-with-an-api-gateway-why-it-matters)
439448
in front of the instance.
440449

450+
### JWT
451+
452+
A `jwt` policy grants access to machine consumers that present a signed JSON Web
453+
Token ([RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) through the
454+
`Authorization` header using the `Bearer` scheme ([RFC
455+
6750](https://datatracker.ietf.org/doc/html/rfc6750)), as issued by an OAuth 2.0
456+
or OpenID Connect provider. Unlike an `apiKey` policy, no shared secret lives in
457+
the configuration or the instance: the policy names a trusted issuer, and the
458+
instance verifies each token against the public key set that issuer publishes,
459+
fetched over HTTP at request time and cached.
460+
461+
A token is admitted only when its signature verifies against the issuer's key
462+
set, its `iss` claim matches the policy's `issuer`, its `aud` claim includes the
463+
policy's `audience`, its signature algorithm is one the policy allows, and it is
464+
within its validity period. A token that fails any of these is denied, with the
465+
same response as any other unauthenticated request.
466+
467+
| Property | Type | Required | Default | Description |
468+
|-----------------|------|----------|---------|-------------|
469+
| `/issuer` | String | :red_circle: **Yes** | N/A | The token issuer to trust, matched against the `iss` claim |
470+
| `/audience` | String | :red_circle: **Yes** | N/A | The audience this instance identifies as. A token is accepted when its `aud` claim includes this value, so a token minted for several audiences at once is accepted as long as this one is among them |
471+
| `/algorithms` | Array | :red_circle: **Yes** | N/A | The JSON Web Signature algorithms the policy accepts. One or more of `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`, `ES256`, `ES384`, `ES512`, and `EdDSA` |
472+
| `/jwksUri` | String | No | Discovered from the issuer | The URL of the issuer's JSON Web Key Set. When omitted, it is discovered from the issuer's OpenID Connect metadata at `{issuer}/.well-known/openid-configuration` |
473+
474+
For example, the following instance keeps `/docs` public, gates `/partners`
475+
behind an API key, and protects `/internal` with a JWT policy that trusts a
476+
single issuer and audience:
477+
478+
```json title="one.json"
479+
{
480+
"url": "https://schemas.example.com",
481+
"authentication": [
482+
{
483+
"type": "apiKey",
484+
"algorithm": "identity",
485+
"name": "partners",
486+
"paths": [ "/partners" ],
487+
"keys": [ { "environmentVariable": "ONE_PARTNERS_KEY" } ]
488+
},
489+
{
490+
"type": "jwt",
491+
"name": "internal",
492+
"paths": [ "/internal" ],
493+
"issuer": "https://accounts.example.com",
494+
"audience": "https://schemas.example.com",
495+
"algorithms": [ "RS256" ]
496+
}
497+
],
498+
"contents": {
499+
"docs": { "path": "./schemas/docs" },
500+
"partners": { "path": "./schemas/partners" },
501+
"internal": { "path": "./schemas/internal" }
502+
}
503+
}
504+
```
505+
506+
!!! note
507+
508+
The key set is fetched from the issuer over HTTP and cached as soft state,
509+
honouring the response's `Cache-Control` and refreshed when a token presents
510+
an unrecognised key identifier, so that issuer key rotation is picked up
511+
without restarting the instance.
512+
441513
## Extends
442514

443515
The `extends` property enables configuration inheritance, allowing you to build

0 commit comments

Comments
 (0)