-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
387 additions
and
345 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
title: TokenX | ||
nav: | ||
- README.md | ||
- 🎯 How-To: how-to | ||
- 📚 Reference: reference | ||
- ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
--- | ||
tags: [auth, services] | ||
--- | ||
|
||
# TokenX | ||
|
||
TokenX is NAIS' own implementation of OAuth 2.0 Token Exchange to be able to propagate citizen identities originating from ID-porten. | ||
|
||
This allows applications to act on behalf of a citizen, while maintaining the [zero-trust](../../workloads/explanations/zero-trust.md) security model between applications throughout the request chain. | ||
|
||
NAIS provides support for declarative registration and configuration of TokenX resources. | ||
These cover two distinct use cases: | ||
|
||
## Consume an API | ||
|
||
To consume an API secured with TokenX on behalf of a citizen, you'll need to exchange the inbound [token](../explanations/README.md#tokens) for a new token only valid for the API you want to access. This new token will then contain the citizens identity context. | ||
|
||
```mermaid | ||
graph LR | ||
Consumer -->|1. citizen token| A | ||
A["your app"] -->|2. exchange token| TokenX | ||
TokenX -->|3. new token for other app| A | ||
A -->|4. use token| O[other app] | ||
``` | ||
|
||
By exchanging the tokens between each application, each token is restricted to a given target application while propagating the citizen identity. | ||
|
||
:dart: Learn how to [consume a API using TokenX](how-to/consume.md) | ||
|
||
## Secure your API | ||
|
||
To secure your API with TokenX, you'll first need to grant access to your consumers. | ||
|
||
```mermaid | ||
graph LR | ||
Provider["Application"] -->|grant access| TokenX | ||
``` | ||
|
||
Once configured, your consumers can [exchange a token from TokenX](#consume-an-api) that targets your application. | ||
Your application must then validate inbound requests from the consumer. | ||
|
||
:dart: Learn how to [secure your API using TokenX](how-to/secure.md) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
--- | ||
tags: [tokenx, how-to] | ||
--- | ||
|
||
# Consume API using TokenX | ||
|
||
|
||
This how-to guides you through the steps required to consume an API secured with [TokenX](../README.md): | ||
|
||
1. [Authenticate with TokenX](#authenticate-with-tokenx) | ||
1. [Exchange token](#exchange-token) | ||
1. [Consume the API using the token](#consume-api) | ||
|
||
## Prerequisites | ||
|
||
- outbound access policy mot api | ||
- app du konsumerer må ha ap for deg | ||
- du må ha konsumenter som bruker tokenx eller idporten | ||
|
||
## Authenticate with TokenX | ||
|
||
To perform a token exchange, your application must authenticate itself. | ||
To do so, create a [client assertion](../../auth/explanations/README.md#client-assertion). | ||
|
||
The client assertion is signed with your applications [private key](../../auth/explanations/README.md#private-keys) contained within [`TOKEN_X_PRIVATE_JWK`](tokenx.md#variables-for-exchanging-tokens). | ||
|
||
The assertion must contain the following claims: | ||
|
||
| Claim | Example Value | Description | | ||
|:----------|:-------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| **`sub`** | `dev-gcp:my-team:app-a` | The _subject_ of the token. Set to [`TOKEN_X_CLIENT_ID`](tokenx.md#variables-for-exchanging-tokens). | | ||
| **`iss`** | `dev-gcp:my-team:app-a` | The _issuer_ of the token. Set to [`TOKEN_X_CLIENT_ID`](tokenx.md#variables-for-exchanging-tokens). | | ||
| **`aud`** | `https://tokenx.dev-gcp.nav.cloud.nais.io/token` | The _audience_ of the token. Set to [`TOKEN_X_TOKEN_ENDPOINT`](tokenx.md#variables-for-exchanging-tokens). | | ||
| **`jti`** | `83c580a6-b479-426d-876b-267aa9848e2f` | The _JWT ID_ of the token. Used to uniquely identify a token. Set this to a UUID or similar. | | ||
| **`nbf`** | `1597783152` | `nbf` stands for _not before_. Set to now. | ||
| **`iat`** | `1597783152` | `iat` stands for _issued at_. Set to now. | ||
| **`exp`** | `1597783182` | `exp` is the _expiration time_ of the token. Between 1 and 120 seconds after now. Typically 30 seconds is fine. | | ||
|
||
Additionally, the headers of the assertion must contain the following parameters: | ||
|
||
| Parameter | Value | Description | | ||
|:----------|:---------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| **`kid`** | `93ad09a5-70bc-4858-bd26-5ff4a0c5f73f` | The key identifier of the key used to sign the assertion. This identifier is available in the JWK found in [`TOKEN_X_PRIVATE_JWK`](tokenx.md#variables-for-exchanging-tokens). | | ||
| **`typ`** | `JWT` | Represents the type of this JWT. Set this to `JWT`. | | ||
| **`alg`** | `RS256` | Represents the cryptographic algorithm used to secure the JWT. Set this to `RS256`. | | ||
|
||
??? example "Example Client Assertion Values" | ||
|
||
**Header** | ||
|
||
```json | ||
{ | ||
"kid": "93ad09a5-70bc-4858-bd26-5ff4a0c5f73f", | ||
"typ": "JWT", | ||
"alg": "RS256" | ||
} | ||
``` | ||
|
||
**Payload** | ||
|
||
```json | ||
{ | ||
"sub": "prod-gcp:namespace-gcp:gcp-app", | ||
"aud": "https://tokenx.dev-gcp.nav.cloud.nais.io/token", | ||
"nbf": 1592508050, | ||
"iss": "prod-gcp:namespace-gcp:gcp-app", | ||
"exp": 1592508171, | ||
"iat": 1592508050, | ||
"jti": "fd9717d3-6889-4b22-89b8-2626332abf14" | ||
} | ||
``` | ||
|
||
## Exchanging a token | ||
|
||
Now that you have a client assertion, we can use this to exchange the inbound token you received from your consumer. | ||
|
||
### Create exchange request | ||
|
||
Create a POST request with the following required parameters: | ||
|
||
| Parameter | Value | Comment | | ||
|:------------------------|:---------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `grant_type` | `urn:ietf:params:oauth:grant-type:token-exchange` | | | ||
| `client_assertion_type` | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | | | ||
| `client_assertion` | A serialized JWT identifying the calling app | The [client assertion](#authenticate-with-tokenx) | | | ||
| `subject_token_type` | `urn:ietf:params:oauth:token-type:jwt` | | | ||
| `subject_token` | A serialized JWT, the token that should be exchanged | Inbound citizen token, either from ID-porten or TokenX | | ||
| `audience` | The identifier of the app you wish to use the token for | The target application using the naming scheme `<cluster>:<namespace>:<appname>` e.g. `prod-gcp:namespace1:app1` | | ||
|
||
Send the request to the `token_endpoint`, i.e. [`TOKEN_X_TOKEN_ENDPOINT`](tokenx.md#variables-for-exchanging-tokens). | ||
|
||
???+ example | ||
```http | ||
POST /token HTTP/1.1 | ||
Host: tokenx.prod-gcp.nav.cloud.nais.io | ||
Content-Type: application/x-www-form-urlencoded | ||
|
||
grant_type=urn:ietf:params:oauth:grant-type:token-exchange& | ||
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer& | ||
client_assertion=eY...............& | ||
subject_token_type=urn:ietf:params:oauth:token-type:jwt& | ||
subject_token=eY...............& | ||
audience=prod-gcp:namespace1:app1 | ||
``` | ||
|
||
### Exchange response | ||
|
||
The response is a JSON object | ||
|
||
```json title="Exchange Response" | ||
{ | ||
"access_token" : "eyJraWQiOi..............", | ||
"issued_token_type" : "urn:ietf:params:oauth:token-type:access_token", | ||
"token_type" : "Bearer", | ||
"expires_in" : 899 | ||
} | ||
``` | ||
|
||
???+ note "Cache your tokens" | ||
|
||
The `expires_in` field in the response indicates the lifetime of the token in seconds. | ||
|
||
Use this field to cache and reuse the token to minimize network latency impact. | ||
|
||
A safe cache key is `key = sha256($subject_token + $audience)`. | ||
|
||
### Exchange error response | ||
|
||
If the exchange request is invalid, you will receive a structured error, as specified in | ||
[RFC 8693, Section 2.2.2](https://www.rfc-editor.org/rfc/rfc8693.html#name-error-response): | ||
|
||
```json title="Error response" | ||
{ | ||
"error_description" : "token exchange audience <some-audience> is invalid", | ||
"error" : "invalid_request" | ||
} | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
--- | ||
tags: [tokenx, how-to] | ||
--- | ||
|
||
# Secure your application with TokenX | ||
|
||
### Getting Started | ||
|
||
=== "nais.yaml" | ||
```yaml | ||
spec: | ||
tokenx: | ||
enabled: true | ||
accessPolicy: | ||
inbound: | ||
rules: | ||
- application: app-2 | ||
- application: app-3 | ||
namespace: team-a | ||
- application: app-4 | ||
namespace: team-b | ||
cluster: prod-gcp | ||
``` | ||
|
||
### Access Policies | ||
|
||
In order for other applications to acquire a token targeting your application, you **must explicitly** specify inbound access policies that authorizes these other applications. | ||
|
||
Thus, the access policies defines _authorization_ on the application layer, and is enforced by Tokendings on token exchange operations. | ||
|
||
For example: | ||
|
||
```yaml | ||
spec: | ||
tokenx: | ||
enabled: true | ||
accessPolicy: | ||
inbound: | ||
rules: | ||
- application: app-1 | ||
- application: app-2 | ||
namespace: team-a | ||
- application: app-3 | ||
namespace: team-b | ||
cluster: prod-gcp | ||
``` | ||
The above configuration authorizes the following applications: | ||
* application `app-1` running in the **same namespace** and **same cluster** as your application | ||
* application `app-2` running in the namespace `team-a` in the **same cluster** | ||
* application `app-3` running in the namespace `team-b` in the cluster `prod-gcp` | ||
|
||
### Token Validation | ||
|
||
If your app is a [resource server / API](../../auth/explanations/README.md#resource-server) and receives a token from another application, it is **your responsibility** to [validate the token](../../auth/explanations/README.md#token-validation) intended for your application. | ||
|
||
Configure your app with the OAuth 2.0 Authorization Server Metadata found at the well-known endpoint, [`TOKEN_X_WELL_KNOWN_URL`](tokenx.md#variables-for-validating-tokens). | ||
Alternatively, use the resolved values from said endpoint for convenience: | ||
|
||
- [`TOKEN_X_ISSUER`](tokenx.md#variables-for-validating-tokens) | ||
- [`TOKEN_X_JWKS_URI`](tokenx.md#variables-for-validating-tokens) | ||
|
||
#### Signature Verification | ||
|
||
* The token should be signed with the `RS256` algorithm (defined in JWT header). Tokens not matching this algorithm should be rejected. | ||
* Verify that the signature is correct. | ||
* The issuer's signing keys can be retrieved from the JWK Set (JWKS) at the `jwks_uri`, i.e. [`TOKEN_X_JWKS_URI`](tokenx.md#variables-for-validating-tokens). | ||
* The `kid` attribute in the token header is thus a reference to a key contained within the JWK Set. | ||
* The token signature should be verified against the public key in the matching JWK. | ||
|
||
#### Claims | ||
|
||
The following claims are by default provided in the issued token and should explicitly be validated: | ||
|
||
* `iss` \(**issuer**\): The issuer of the token **must match exactly** with the Tokendings issuer URI ([`TOKEN_X_ISSUER`](tokenx.md#variables-for-validating-tokens)). | ||
* `aud` \(**audience**\): The intended audience for the token, **must match** your application's `client_id` ([`TOKEN_X_CLIENT_ID`](tokenx.md#variables-for-validating-tokens)). | ||
* `exp` \(**expiration time**\): Expiration time, i.e. tokens received after this date **must be rejected**. | ||
* `nbf` \(**not before time**\): The token cannot be used before this time, i.e. if the token is issued in the "future" (outside "reasonable" clock skew) it **must be rejected**. | ||
* `iat` \(**issued at time**\): The time at which the token has been issued. **Must be before `exp`**. | ||
* `sub` \(**subject**\): If applicable, used in user centric access control. This represents a unique identifier for the user. | ||
|
||
Other non-standard claims (with some exceptions, see the [claim mappings](#claim-mappings) section) in the token are copied verbatim from the original token issued by `idp` (the original issuer of the subject token). | ||
For example, the claim used for the personal identifier (_personidentifikator_) for tokens issued by ID-porten is `pid`. | ||
|
||
#### Claim Mappings | ||
|
||
Some claims are mapped to a different value for legacy/compatibility reasons, depending on the original issuer (`idp`). | ||
|
||
The table below shows the claim mappings: | ||
|
||
| Claim | Original Value | Mapped Value | | ||
|:------|:---------------------------|:--------------| | ||
| `acr` | `idporten-loa-substantial` | `Level3` | | ||
| `acr` | `idporten-loa-high` | `Level4` | | ||
|
||
This currently only affects tokens from ID-porten, i.e. `idp=https://test.idporten.no` or `idp=https://idporten.no`. | ||
|
||
The mappings will be removed at some point in the future. | ||
If you're using the `acr` claim in any way, check for both the original and mapped values. | ||
|
||
#### Example Token (exchanged from ID-porten) | ||
|
||
The following example shows the claims of a token issued by Tokendings, where the exchanged subject token is issued by [ID-porten](idporten.md): | ||
|
||
???+ example | ||
```json | ||
{ | ||
"at_hash": "x6lQGCdbMX62p1VHeDsFBA", | ||
"sub": "HmjqfL7....", | ||
"amr": [ | ||
"BankID" | ||
], | ||
"iss": "https://tokenx.prod-gcp.nav.cloud.nais.io", | ||
"pid": "12345678910", | ||
"locale": "nb", | ||
"client_id": "prod-gcp:team-a:app-a", | ||
"sid": "DASgLATSjYTp__ylaVbskHy66zWiplQrGDAYahvwk1k", | ||
"aud": "prod-fss:team-b:app-b", | ||
"acr": "Level4", | ||
"nbf": 1597783152, | ||
"idp": "https://idporten.no", | ||
"auth_time": 1611926877, | ||
"exp": 1597783452, | ||
"iat": 1597783152, | ||
"jti": "97f580a6-b479-426d-876b-267aa9848e2e" | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
--- | ||
tags: [tokenx, reference] | ||
--- | ||
|
||
# TokenX reference | ||
|
||
## Spec | ||
|
||
See the [NAIS manifest](../../workloads/application/reference/application-spec.md#tokenxenabled). | ||
|
||
|
||
## Runtime Variables & Credentials | ||
|
||
Your application will automatically be injected with environment variable. | ||
|
||
### Variables used when acquiring tokens | ||
|
||
These variables are used for [client authentication](tokenx.md#client-authentication) and [exchanging tokens](tokenx.md#exchanging-a-token): | ||
|
||
| Name | Description | | ||
|:-------------------------|:---------------------------------------------------------------------------------------------------------| | ||
| `TOKEN_X_CLIENT_ID` | [Client ID](../../auth/explanations/README.md#client-id) that uniquely identifies the application in TokenX. | | ||
| `TOKEN_X_PRIVATE_JWK` | [Private JWK](../../auth/explanations/README.md#private-keys) containing an RSA key belonging to client. | | ||
| `TOKEN_X_TOKEN_ENDPOINT` | `token_endpoint` from the [metadata discovery document](../../auth/explanations/README.md#token-endpoint). | | ||
|
||
### Variables for when validating tokens | ||
|
||
These variables are used for [token validation](tokenx.md#token-validation): | ||
|
||
| Name | Description | | ||
|:-------------------------|:-----------------------------------------------------------------------------------------------------| | ||
| `TOKEN_X_CLIENT_ID` | [Client ID](../../auth/explanations/README.md#client-id) that uniquely identifies the application in TokenX. | | ||
| `TOKEN_X_WELL_KNOWN_URL` | The URL for Tokendings' [metadata discovery document](../../auth/explanations/README.md#well-known-url-metadata-document). | | ||
| `TOKEN_X_ISSUER` | `issuer` from the [metadata discovery document](../../auth/explanations/README.md#issuer). | | ||
| `TOKEN_X_JWKS_URI` | `jwks_uri` from the [metadata discovery document](../../auth/explanations/README.md#jwks-endpoint-public-keys). | | ||
|
||
## Local Development | ||
|
||
See also the [development overview](development.md) page. | ||
|
||
### Token Generator | ||
|
||
In many cases, you want to locally develop and test against a secured API in the development environments. | ||
To do so, you need a [token](../../auth/explanations/README.md#bearer-token) to access said API. | ||
|
||
Use <https://tokenx-token-generator.intern.dev.nav.no> to generate tokens in the development environments. | ||
|
||
#### Prerequisites | ||
|
||
1. The API application must be configured with [TokenX enabled](#configuration). | ||
2. Pre-authorize the token generator service by adding it to the API application's [access policy](#access-policies): | ||
```yaml | ||
spec: | ||
accessPolicy: | ||
inbound: | ||
rules: | ||
- application: tokenx-token-generator | ||
namespace: aura | ||
cluster: dev-gcp | ||
``` | ||
#### Getting a token | ||
1. Visit <https://tokenx-token-generator.intern.dev.nav.no/api/obo?aud=<audience>> in your browser. | ||
- Replace `<audience>` with the intended _audience_ of the token, in this case the API application. | ||
- The audience value must be on the form of `<cluster>:<namespace>:<application>` | ||
- For example: `dev-gcp:aura:my-app` | ||
2. You will be redirected to log in at ID-porten (if not already logged in). | ||
3. After logging in, you should be redirected back to the token generator and presented with a JSON response containing an `access_token`. | ||
4. Use the `access_token` as a [Bearer token](../../auth/explanations/README.md#bearer-token) for calls to your API application. | ||
5. Success! |
Oops, something went wrong.