scott.mebberson.codes

SPA

SPA

JWT Authentication

The basic premise of this JWT authentication pattern is that the front-end authenticates via a GraphQL API with a valid JWT on each request.

Attributes of this pattern include:

  • Using a JWT.
  • Splitting the JWT across two cookies.
  • Ensuring the entire JWT is not available to JavaScript.
  • Works with and without a browser (with and without cookies).

Using a JWT.

A valid JWT will act as the currency for an authenticated user.

The first step is to authenticate the user and issue a valid JWT. This step is required in both browser and non-browser contexts.

Splitting the cookie.

Now that the client has a valid JWT, it will need to be sent with each request to the GraphQL API.

Storing JWTs in a SPA is notoriously tricky. Most of the options present a security risk as the entire JWT ends up being available to JavaScript. As such, retrieval of the JWT might be possible with common exploits such as cross-site scripting.

To avoid these scenarios, we'll split the JWT content ({header}.{payload}.{signature}) across two cookies.

The first cookie will contain the {header}.{payload} content from the JWT with the following attributes:

  • Expiry: 30 minutes
  • SameSite: Lax
  • Secure: yes

The second cookie will contain the {signature} content from the JWT with the following attributes:

  • Expiry: session
  • HTTPOnly: yes
  • SameSite: Lax
  • Secure: yes

Splitting the JWT across two cookies provides the following benefits:

  • The entire JWT never exists in JavaScript.
  • JavaScript can still extract user information from a cookie containing the payload portion of the JWT.
  • Each request made in a browser context will automatically include all authentication content.
  • The signature cookie is HTTPOnly so it is not available to JavaScript and helps to protect against XSS.
  • Using SameSite Lax cookies will provide some protection against CSRF.
  • Using Secure cookies will help to mitigate man-in-the-middle attacks.

Optionally, reconstructing the JWT

Each request to the GraphQL API will need to be authenticated with a valid JWT.

Browsers will supply the JWT via two cookies, and API clients will provide the JWT via an Authorization header.

The GraphQL API will need to respond to all requests, regardless if they're coming from a browser or not.

In the context of an API client, an Authorization header containing a JWT will be supplied.

In the context of the browser, the server will be responsible for creating an Authorization header by reconstructing a JWT from the content within the two cookies. Constructing the Authorization header will ensure that all future processing of the request is identical for browser-based and non-browser-based applications alike.

Each request should be inspected for cookies and if found, their content to be used in reconstructing the JWT and creating anAuthorization header.

Guarding against CSRF

While using cookies to store the JWT (rather than storing it in local storage or session storage) protects applications from some type of attacks, it makes them vulnerable to others, including CSRF.

Using the Same Origin Policy (SOP), we can mitigate CSRF by requiring that a custom header exists if supplying the JWT via cookies. This is described as a CSRF prevention technique on OWASP.

Processing of the cookies should only take place if a custom header with the correct information is supplied.

Validating the JWT

The GraphQL API should require a valid JWT in the Authorization header.

When validating the JWT, ensuring the following are checked:

  • Verify the issuer.
  • Verify the audience.
  • Ensure the token is not expired.

Continue processing

At this point, if the JWT validated and the user roles have access to the GraphQL API queries being requested, continue processing the request using your GraphQL framework.