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:
- JSON Web Token (JWT) specification - note that per section 1 of this spec "JWT" is pronounced "jot!" Opinions differ.
- JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants specification
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:
Field | Name | Meaning |
---|---|---|
sub | Subject | Represents the owner of the resources (data) to which this token will give access |
aud | Audience | Represents the holder of the token - i.e. to whom will data be exposed when this token is used |
nbf | Not Before | The 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) |
iss | Issuer | The authority issuing the token (usually represents an authorization server) |
exp | Expiry | The timestamp at which the token expires |
iat | Issued At | The timestamp at which the token was issued |
scope | Scope | The 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 aclient_credentials
grant is used this will be the same as theaud
value.aud
- a test client id (also known by Safello asapp id
) identifying the organisation to which the token has been issuedscope
- 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:
Field | Meaning |
---|---|
keys | The list of published keys |
kty | Identifies the Key TYpe |
e | The exponent value for an RSA public key |
n | The 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).