JWT Token Exchange

With the API gatekeeper, each API call has to pass through the gatekeeper, which checks if the call is allowed. To avoid this bottleneck, Feide has implemented proof of concept support for RFC 8693 - Oauth2 token exchange. Here, the client receives a short lived, digitally signed token which it can pass directly to the API. The token is a JSON Web Token (JWT). The API can decode and parse it, verify the signature and validity times, and decide whether or not to allow the operation without involving a gatekeeper.

Warning

This is a proof of concept implementation. It may be changed or discontinued at short notice.

To obtain a JWT, a client makes a token exchange request to the token endpoint using the grant type urn:ietf:params:oauth:grant-type:token-exchange. It receives a response, which if succssful includes a JWT access token.

API configuration

APIs which are to consume JWT access tokens are configured in the API gatekeeper tab of the Dataporten Dashboard, just like APIs which use the API gatekeeper. A token exchange request includes audience and scope parameters. The audience must be the ID of an API registered with the gatekeeper. Scopes must be registered subscopes of the API. The dashboard is used to configure policies for access to scopes and APIs.

Scope values are given from different perspectives in the dashboard and in a token exchange request. If the API coolapi has the subscopes foo and bar, they are called gk_coolapi_foo and gk_coolapi_bar in the dashboard. In the token exchange request, they are named from the point of view of the API, and are simply called foo and bar.

Token exchange request

A token exchange request is made to the token endpoint using the HTTP “POST” method. Parameters are included in the HTTP request entity-body using the application/x-www-form-urlencoded format. The parameters are:

grant_type

The value urn:ietf:params:oauth:grant-type:token-exchange selects Oauth2 token exchange.

client_id

The ID of the client making the exchange request.

client_secret

The client secret of the client.

audience

The API the JWT is intended for.

scope

A space separated list of desired scopes. It is up to the API how to interpret these scopes.

subject_token

An access token obtained from Feide. It should give the client access to the scopes given as scope in the API given as audience.

subject_token_type

Must be urn:ietf:params:oauth:token-type:access_token.

Here is an example:

POST https://auth.dataporten.no/oauth/token
content-type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id=208335d4-e8c1-4910-8928-05b2e5b14127
&client_secret=5df85658-d0c1-4348-890a-a204b24eca2e
&audience=coolapi
&scope=foo bar
&subject_token=96c5a3aa-1af8-45bd-a4f8-4b7fb07d393f
&subject_token_type=urn:ietf:params:oauth:token-type:access_token

Successful response

A successful response has status 200 and an application/json body with the following attributes:

token_type

The type is Bearer.

issued_token_type

The type is urn:ietf:params:oauth:token-type:jwt.

access_token

The JWT that was issued.

expires_in

Number of seconds until JWT expires.

scope

The scopes that were granted.

Here is an example:

{
    "token_type": "Bearer",
    "issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI...",
    "expires_in": 299,
    "scope": "foo bar"
}

JWT access token

The token is an ASCII string. It is intended for a third party API. It consists of a header, a payload and a signature, with a . between them. Each are base64url encoded. See RFC 7519 - JSON Web Token (JWT). The payload is a json object with the following attributes:

aud

Audience. The API should only accept the token if it is the intended audience.

iss

Issuer. Value is https://auth.dataporten.no if token was issued by Feide.

exp

Expiration time (in seconds since 1970-01-01T0:0:0 UTC).

nbf

Not valid before time. Protects against clock skew.

client_id

ID of the client that requested the token.

sub

Subject - the identity which the token authenticates. Can be a dataporten user ID or a client ID.

scope

The scopes that were granted.

act

Actor. It represents a chain of delegation. E.g., a client could authorize an API to access another on its behalf. We do not currently support delegation in JWT tokens, so the chain is only one level deep. It is a json object with a single attribute: sub, with the same value as sub in the token.

Here is an example of what the payload may look like after decoding:

{
    "aud": "coolapi",
    "iss": "https://auth.dataporten.no",
    "exp": 1610448035,
    "nbf": 1610447735,
    "client_id": "208335d4-e8c1-4910-8928-05b2e5b14127",
    "sub": "208335d4-e8c1-4910-8928-05b2e5b14127",
    "scope": "foo bar",
    "act": {
        "sub": "208335d4-e8c1-4910-8928-05b2e5b14127",
    }
}

Validating a JWT access token

The consumer must validate the access token. In particular, iss has to be auth.dataporten.no and aud has to match the ID of the consumer. See the JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens for full details about JWT access token validation.

Token lifetimes

A JWT cannot be reliably revoked. Therefore, the lifetime is short, typically on the order of minutes. If the client needs access after the token has expired, it must obtain a new one.