The API uses refresh token rotation (RTR) to grant self-contained access tokens. Access tokens can't be invalidated for performance reasons, therefore their lifespan is limited. By the nature of an API, CSRF tokens aren't used. CORS headers are intentionally completely relaxed to faccilitate automation and third party clients.
This is the authentication flow as seen from the client, taking into account the possibility that there may be multiple clients (tabs) sharing a session.
- #assess: see what's in persistent storage
- if it's an old pair of tokens but the refresh token is still valid
- write READLOCK into persistent storage
- use refresh token with API
- if request fails:
- write NETWORK_ERROR to persistent storage
- go to beginning
- if request fails:
- if persistent storage still contains READLOCK
- write results into persistent storage
- if it's READLOCK
- wait until the value in persistent storage changes
- go to beginning
- if it's empty or a pair of tokens with the refresh token expired
- ask the user for credentials (also wait for storage to change and react when it does)
- Obtain tokens from the API
- write the results into persistent storage
- if it's a new pair of tokens
- nothing to do
- if it's NETWORK_ERROR
- wait a short while (configuration parameter: retry timeout)
- go to beginning
- if it's an old pair of tokens but the refresh token is still valid
- read new tokens from persistent storage
- wait until the access token is about to expire or persistent storage changes (configuration parameter: time to expiration)
- go to beginning
- Because the above flow will always check storage after waiting, logging out can be accomplished by clearing persistent storage.
- The waiting periods should be offset by a (low security) random amount to avoid a thundering herd scenario
Each accept a particular set of credentials, the most basic being an username and a password. Generates a pair of refresh and acces tokens to return and adds the refresh token in the database.
Accepts a refresh token. If it is not found in the database, deletes all tokens of the user and returns with a compromised token error. If the token exists, generates a new pair of refresh and access tokens to return and overwrites the token in the database with the new refresh token.
Accepts a valid, signed access token, serves the user without having to consult the database for authentication purposes.
The refresh token includes its own expiry and the username, but it is encrypted with the server's private key such that it's opaque to the client.
The access token contains, besides the regular JWT fields like expiry, the fields necessary to programmatically handle permission management.
The access token does NOT contain the user profile, which can be requested from the API. This is both to simplify the token renewal system and to make the token as short as possible since it's included in every request. The fields in the token may be read by clients and used to preemptively reject actions for which the user does not have permission.