Skip to main content

Tokens in detail

As documented in the section on authentication, access to the business endpoints is negotiated with an access token, and new access (and refresh tokens) can be obtained using the refresh token. This section digs a bit deeper into the format of these tokens and explains how you can determine various properties of the access tokens directly.

Access tokens

Our access tokens are JSON Web Tokens (JWTs), self contained, verifiable representations of the permissions granted.

Some relevant external resources:

Each token is formed of three Base64 encoded parts, each separated by a period - i.e. "."

Algorithm

The first part of the token represents the signature algorithm. This decodes as a JSON value:

{"alg":"RS256"}

Data

The second part contains the data of the token. Again it decodes as JSON and it will contain, at a minimum, the following fields:

FieldNameMeaning
subSubjectRepresents the owner of the resources (data) to which this token will give access
audAudienceRepresents the holder of the token - i.e. to whom will data be exposed when this token is used
nbfNot BeforeThe earliest timestamp at which the token may be used (usually synonymous with the iat field, although it is possible for this field to be a future date)
issIssuerThe authority issuing the token (usually represents an authorization server)
expExpiryThe timestamp at which the token expires
iatIssued AtThe timestamp at which the token was issued
scopeScopeThe scopes granted to the token

For example, from our test system:

{
"sub":"18429",
"aud":"1234-5678-2",
"nbf":1651663930,
"scope":[ "account.base", "account.bank-account:read", "order", "wallet" ],
"iss":"https://api.s4f3.io",
"exp":1651664230,
"iat":1651663930
}

Here the token was issued via the Bank ID flow, so...

  • sub - a customer ID as the token gives access to this customer's data. If a client_credentials grant is used this will be the same as the aud value.
  • aud - a test client id (also known by Safello as app id) identifying the organisation to which the token has been issued
  • scope - a set of scopes that grants access to this customer's data

As expected, the timestamps for "Not Before" and "Issued At" are identical, and the "Expiry" represents (see the Validity section for more on this) a time five minutes later than the token was issued.

Validity

The third and final part of the issued token decodes to the binary signature of the token data using the algorithm specified in the first part.

Our public key is published under the /oauth2/jwks path, so for example https://api.s4f3.io/oauth2/jwks for our staging environment.

Output of the JWKS path example

Typically you will interact via pre-existing JWT/OAuth2 support libraries rather than consuming these values directly.

{ "keys" : 
[
{
"kty":"RSA",
"e":"AQAB",
"n":"yrT5ZF5NgunOgA2ftF0ayV4xHEbGdGwDbFR7dHw5LmBY-69plhYKp1_qAsipBG6cbej3WAy02OsdUoR2aRW7lrk1BdP-QMAMl-3YtnKwHV8wG9FNbco72VpfWc7by_R047K5rR79hP8h78RKHk39UPxQH-uHno0WspDIjlUZJtdgL_fGYyreJKchqvZWaXPsCX2zuR3BNj4mNFQYogTuE2i_ofcIDPJM1RWaM-hF6IAWibGsqWF3ZoanphIRfIejmp5f8_B-jMsQh63v1wp34mtF_ClW9Szla89dkLtNWQa69709tzmFcFxkJTmQYIYAsDixSzY_vbsB-lmx_VSzIQ"
}
]
}

Here some of the properties are specific to the algorithm being used, but in this example they are:

FieldMeaning
keysThe list of published keys
ktyIdentifies the Key TYpe
eThe exponent value for an RSA public key
nThe modulus value for an RSA public key

The signature in the token can be validated against the known Safello public key, and given the expiry timestamp embedded in the token its current validity and legitimacy can be confirmed.

Obtaining other OAuth2 related information

This and a number of other standard endpoints can be listed under the /.well-known/oauth-authorization-server path, so https://api.s4f3.io/.well-known/oauth-authorization-server for our staging environment.

{
"issuer": "https://api.s4f3.io",
"authorization_endpoint": "https://api.s4f3.io/oauth2/authorize",
"token_endpoint": "https://api.s4f3.io/oauth2/token",
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt"
],
"jwks_uri": "https://api.s4f3.io/oauth2/jwks",
"response_types_supported": [
"code"
],
"grant_types_supported": [
"authorization_code",
"client_credentials",
"refresh_token"
],
"revocation_endpoint": "https://api.s4f3.io/oauth2/revoke",
"revocation_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt"
],
"introspection_endpoint": "https://api.s4f3.io/oauth2/introspect",
"introspection_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"client_secret_jwt",
"private_key_jwt"
],
"code_challenge_methods_supported": [
"plain",
"S256"
]
}

Expiry

Access tokens, being JWTs can be decoded to extract an expiry timestamp. For refresh tokens you will need to query a Safello endpoint to determine their expiry timestamp.

Timestamps are generally given as a Unix Epoch timestamp, representing the number of seconds elapsed from 1970-01-01T00:00:00UTC. If your systems do not use UTC you will need to manage the appropriate timezone conversion carefully and include test cases for clock transitions to and from daylight-savings times. In general usage you will use a support library to interpret OAuth2 timestamps.

Decoding a JWT expiry timestamp example

The value of the iss and exp fields in a decoded JWT access token will be large integer values:

{ ...
"iss":1651663930,
"exp":1651664230,
...
}

Using the Linux date command to decode these as Unix Epoch timestamps:

$ date --date='@1651663930'
Wed 4 May 13:32:10 CEST 2022
$ date --date='@1651664230'
Wed 4 May 13:37:10 CEST 2022

The output indicates that this token was issued on 4th May 2022 at around 13:32 Stockholm time, and expired 5 minutes later at about 13:37 Stockholm time. This token is therefore no longer valid.

When refreshing tokens you should always allow a small margin for network latency to avoid inadvertent use of expired tokens. For example, request a replacement access token 5 seconds before it expires, rather than 5 milliseconds before it expires!

Access token expiry

The exp property of the access token JWT is a Unix Epoch timestamp representing the expiry of the token. Access tokens are generally issued with a short duration, currently (2022-05-05) 5 minutes. This should be used in preference to the expiry field (defined in seconds) included in the token providing response from the /oauth2/token path because that value cannot account for any network latency or other delays in receiving the token value. Any inaccuracy in the exp property will only be due to clock-skew between the issuing and receiving servers.

Once expired a new access token must be requested by using the longer-lived refresh token.

Refresh token expiry

The expiry timestamp of a refresh token can only be determined by querying a Safello API at path /oauth2/introspect and supplying the token itself as the value for a token parameter in the body.

Retrieving a refresh token expiry timestamp example
export CLIENT_ID=1234-5678-2
export SECRET=...
export CREDENTIALS_BASE_64=$(echo -e -n "$CLIENT_ID:$SECRET" | base64)

curl \
--silent \
--header "Authorization: Basic $CREDENTIALS_BASE_64" \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'source-ip-address: 127.0.0.1' \
--header 'source-user-agent: curl' \
--request POST \
--location 'https://api.s4f3.io/oauth2/introspect' \
--data 'token=sCSnchKE_zQFKpDWDRGyiNIwvzWRll6zPyVqdtARjp48D5X2Pqr1SGOGV55DaN7Rh8jArSM_Gp102dOjxNrlOXyvh4RJI56Luh68qy6vOaNo-t7OO6gPcJQlAEUKyDio'

If the refresh token is valid the response will be of the form:

{
"active":true,
"client_id":"1234-5678-2",
"iat":1651663931,
"exp":1683199931
}

Or if invalid the response will be just:

{ "active" : false }

An expired refresh token cannot be extended; you must request a new refresh token (including the BankID flow where necessary).