If you do not want to build and pay for infrastructure to cache Session State so it can be shared across multiple Web servers, then security between the browser and Web Application back-end can be implemented in a stateless manner with JSON Web Tokens (JWT).
However for a Web Application with a back-end, where the front-end only communicates to its back-end and does not perform API calls to other services, if the JWT is stored in memory, a simple browser page refresh will wipe the JWT from memory and the user will be forced to sign-in to the front-end again.
In order to store the JWT Access Token so that it persists between page refreshes, good security practices need to be followed in order to avoid and mitigate the possible attack vectors.
The safest methodology is suggested by OWASP - Token Storage on Client Side - see my explanation below.
1. Back-end: Sign-in
On the back-end, after a user’s credentials have been validated successfully:
- Generate a cryptographically strong random string - we will name this the fingerprint
- Perform a SHA256 hash of the fingerprint
- Create the JWT Access Token with the desired expiration time, and a Claim for the fingerprint, with the value of the previously calculated hash.
- Add a hardened cookie to the HTTP Response for the fingerprint, with a value of the full random string.
- Return the JWT in the HTTP Response Header or Body, depending on how you prefer. DO NOT return the JWT in a Cookie!
NOTE: a hardened cookie specifies:
- Secure - so that the cookie is only ever transmitted via HTTPS - to help prevent man-in-the-middle attacks;
- SameSite=strict - so that the cookie is sent only ever to the same site as the one that originated it; and
- '__Host-’ as the prefix to the Cookie name without a
Domainattribute and specifying a
/- so that the back-end server can confirm that the cookie was originally set on a secure origin (not from a browser) and help mitigate a session fixation attack.
2. Browser: Token Storage
The Web browser will receive both the JWT Access Token and fingerprint hardened cookie.
Store the JWT Access Token in the browser Session Storage. This will be cleared when the browser Tab or Window is closed.
Since the JWT is stored in Session Storage, it possibly could be stolen by an XSS attack - however we mitigate this risk since the fingerprint Claim in the JWT allows our back-end to prevent reuse of a stolen JWT by an attacker on their machine.
3. Browser: Perform a Back-end Call
When a call is to made to the back-end:
- the browser will automatically send the fingerprint cookie as part of the HTTP Request; and
- you need to retrieve the JWT from Session Storage and add it to the HTTP Request header or body, depending on your implementation.
4. Back-end: Authorise Web Request
Now completing the picture, when the back-end receives a HTTP Request over HTTPS:
- Validate that the JWT was signed by our system;
- Validate that the JWT has not expired;
- Perform a SHA256 hash on the value of the fingerprint hardened cookie; and
- Assert that the hash we just computed is the same as the value in the fingerprint Claim in the JWT.
With all these measures in place, we can be confident that the HTTP Request legitimately came from our front-end.