Token Exchange

In HelseID, Token Exchange is based on the IETF specification RFC 8693: OAuth 2.0 Token Exchange, which describes a protocol for receiving Tokens from an Athorization Server for use of "impersonation" and "delegation".

This specification can be used in HelseID when APIs have the need to call other APIs, on behalf of an authenticated person and/or organization.

Principal terms

The concept of Token Exchange introduces some new terms:

subject_token: An Access Token issued by HelseID, that can contain claims that identifies an authenticated user (person) and/or the organization that has legal rights to the Client.

subject client: A Client that has received a subject_token from HelseID.

actor client: A Client that needs to exchange a subject_token by the use of the Token Exchange mechanism. The purpose of this would be to receive an new Access Token, that can be used for authorization against another server. In most cases, Actor client will be an API that wants to receive data from another API, which also is dependent upon HelseID for authorization.

The Token Exchange flow

The usage of Token Exchange in context of HelseID is arranged with a flow where a subject client requests an Access Token from HelseID. Following that, the subject client makes use of this Access Token against an API that contains an actor client. The actor client then performs the Token Exchange call itself, however, the total flow can be somewhat complex.

The complete flow (with one Token Exchange)

  1. The Subject Client requires authentication of a user, and requests access to API 1
  2. HelseID issues an Access Token (AT#1, subject_token) that will give access to API 1
  3. The Subject Client calls API 1, with AT#1 in the Authorization header
  4. API 1 needs data from API 2, and this requires an Access Token
  5. The Actor Client (API 1) performs a Token Exchange request against the Token endpoint in HelseID, and requests scope(s) for API 2
    • In this request, AT#1 is the subject_token
  6. HelseId issues a new Access Token (AT#2), and sends it in the response to the Actor client (API 1)
  7. The Actor client (API 1) calls API 2 with AT#2 in the Authorization header

Token exchange

The request to HelseId (the call to the Token endpoint)

In order to be issued an Access Token by the Token Exchange mechanism, the Actor Client must use the Token endpoint in the same way as a "normal" HelseID-Client. However, some extra parameters must be set in the call:

  • grant_type: must always be urn:ietf:params:oauth:grant-type:token-exchange
  • subject_token: The Access Token that should be exchanged with a new Access Token
  • subject_token_type: must always be urn:ietf:params:oauth:token-type:access_token

An example of a POST-request to HelseID for Token Exchange:

POST /connection/token

grant_type=urn:ietf:params:oauth:grant-type:token-exchange&
scope=api1 api2&
subject_token=eyJhbGciOiJSUzI...lZ22kWJV4pHr8t&
subject_token_type=urn:ietf:params:oauth:token-type:access_token&
client_assertion=eyJhbGciOiJSUzI...lZ22kWJV4pHr8t&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_id=3c349ff9-20ea-4026-bf8f-f2fb9ed1bb59&

Success response

If HelseID issues an Access Token, the response will be equal to the one specified in RFC8693 (Token Exchange).

Error messages from the Token endpoint

When an error occurs in the call to the Token endpoint, HelseID will issue an error message, with a more detailed error description. The table below indicates the most common error messages:

Error message Error description Interpretation of the error
invalid_request invalid subject_token - [detailed information] This error will occur when the subject Token is not issued by HelseID, or the Token lifetime has expired.
invalid_request not permitted This error will occur when the subject client is not configured for the use of Token Exchange against the actor client.
invalid_target invalid scopes requested This error will occur when the actor client requests an scopes that are spread across more than one API-resource.
invalid_request subject_token exchanged too many times (5) This error will occur when the actor client tries to exchange a Token that has already been exchanged too many times.
invalid_request The audience in the subject token and the client with client_id '[client_id]' have different configuration owners. The Actor Client is set up for another configuration owner than the one listed in the aud (audience) claim in the subject token.

See also general error messages.

The issued Access Token

With a successful Token Exchange, the actor client gets issued an Access Token.

The following claim will be changed in the new token:

  • aud: audience for API 2

The following claim identifies the actor client:

  • act: A set of claims that identifies the organization and the Client that operates on behalf of this organization, and/or an authenticated person. The act claim is structured as follows:

    • iss: The HelseID Authorization Server that has authenticated the actor client
    • client_id: An identifier for the actor client
    • helseid://claims/client/claims/orgnr_parent: The parent organization number
    • helseid://claims/client/claims/orgnr_child: The child organization number

For call chains where Token Exchange is used more than once, you will get a nested structure of act claims. The innermost Actor Client will be the oldest, and the outermost Actor Client will be the newest, and as such, the active Actor Client for the relevant Access Token.

The following claim identifies the subject client:

  • helseid://claims/client/original_client_id: The Client ID from the first subject_token in the call chain

Claims that will be copied from the subject_token:

  • All claim with the prefix helseid://
  • sub: A HelseID spesific identificator for an authenticated user
  • name: The name of the authenticated user
  • given_name: The given name of the authenticated user
  • middle_name: The middle name of the authenticated user
  • family_name: The family name of the authenticated user
  • sid: The session ID for the authenticated user
  • idp: The IDP used for authenticating the user
  • amr: The methdod for logging on the user
  • auth_time: The time when the user was authenticated

⚠️  Consequences for this (for API owners):

  • Claims that do not exist in the incoming Token (subject_token), will not be present in the new Token, even if API 2 requires this information. An example of this kind of information might be claims which indicate the identity of a logged in user.
  • An example of an Access Token from Token exchange

    {
      "alg": "RS256",
      "kid": "78667F90DC11BF04BD9467D1F9120C4A4340B4CF",
      "x5t": "eGZ_kNwRvwS9lGfR-RIMSkNAtM8",
      "typ": "JWT"
    }.{
      "iss": "https://helseid-sts.test.nhn.no",
      "nbf": 1713888289,
      "iat": 1713888289,
      "exp": 1713888889,
      "aud": "nhn:helseid-public-samplecode",
      "cnf": {
        "jkt": "aoezvIe32RdFpTP4FIxwBYb6VGX0dp3ecvoVjFkdXQk"
      },
      "scope": [
        "nhn:helseid-public-samplecode/client-credentials"
      ],
      "amr": [
        "pwd"
      ],
      "client_id": "helseid-sample-token-exchange-actor-client",
      "helseid://claims/client/original_client_id": "helseid-sample-token-exchange-subject-client",
      "client_amr": "private_key_jwt",
      "helseid://claims/identity/pid": "11857998857",
      "helseid://claims/identity/security_level": "4",
      "name": "VIRKELIG KJELTRING",
      "jti": "25488FC67BAB3419DC6E70C273FBB871",
      "sid": "0FAB2BC0164BF60B39ECED460E2A56BA",
      "act": {
        "iss": "https://helseid-sts.test.nhn.no",
        "client_id": "helseid-sample-token-exchange-actor-client",
        "helseid://claims/client/claims/orgnr_parent": "999977774"
      },
      "helseid://claims/client/claims/orgnr_parent": "999977774",
      "oldsub": "LHcSgmD3Q/SO/0dLxXvhKBnh1ebeq61C3ndgEIkptbo=",
      "sub": "vJs8Xr2F58spTNEPHM/a07KdZtSBGLQN9EmHuBGLy/c=",
      "auth_time": 1713888282,
      "idp": "testidp-oidc",
      "helseid://claims/client/amr": "rsa_private_key",
      "helseid://claims/client/client_name": "helseid-sample-token-exchange-actor-client",
      "helseid://claims/client/client_tenancy": "single-tenant"
    }.[Signature]