Snowboy Fail to Read Token in Expecttoken()

banner image

The Ultimate Guide to handling JWTs on frontend clients (GraphQL)

JWTs (JSON Spider web Token, pronounced 'jot') are becoming a popular way of handling auth.
This post aims to demystify what a JWT is, discuss its pros/cons and cover all-time practices in implementing JWT on the client-side, keeping security in mind.
Although, nosotros've worked on the examples with a GraphQL clients, but the concepts apply to any frontend client.

Note: This guide was originally published on September ninth, 2019. Concluding updated on January quaternary, 2022.

Table of Contents

  • Table of Contents
  • Introduction: What is a JWT?
    • Security Considerations
    • JWT Structure
  • Basics: Login
  • Basics: Client setup
  • Nuts: Logout
  • Silent refresh
  • Persisting sessions
  • Force logout, aka Logout of all sessions/devices
  • Server side rendering (SSR)
    • How does the SSR server know if the user is logged in?
  • Lawmaking from this blogpost (finished application)
  • Try it out!
  • References
  • Summary
  • Changelog

Introduction: What is a JWT?

For a detailed, technical description of JWTs refer to this article.

For the purposes of auth, a JWT is a token that is issued by the server. The token has a JSON payload that contains information specific to the user. This token can be used by clients when talking to APIs (by sending it along as an HTTP header) and so that the APIs can identify the user represented by the token, and take user specific action.

Security Considerations

But can't a client simply create a random JSON payload an impersonate a user?

Adept question! That'southward why a JWT also contains a signature. This signature is created past the server that issued the token (let's say your login endpoint) and any other server that receives this token can independently verify the signature to ensure that the JSON payload was not tampered with, and has data that was issued by a legitimate source.

Merely if I take a valid and signed JWT and someone steals it from the customer, tin can't they apply my JWT forever?

Yes! If a JWT is stolen, then the thief can tin can go along using the JWT. An API that accepts JWTs does an independent verification without depending on the JWT source so the API server has no way of knowing if this was a stolen token! This is why JWTs have an decease value. And these values are kept brusk. Mutual practice is to keep it effectually 15 minutes, so that any leaked JWTs will cease to exist valid fairly quickly. Only as well, brand certain that JWTs don't get leaked.

These 2 facts effect in nearly all the peculiarities about handling JWTs! The fact that JWTs shouldn't get stolen and that they need to take short expiry times in instance they do get stolen.

That's why it's also really important not to store the JWT on the client persistently. Doing and so y'all brand your app vulnerable to CSRF & XSS attacks, past malicious forms or scripts to apply or steal your token lying effectually in cookies or localStorage.

JWT Construction

Then does a JWT take a specific kind of structure? What does it look like?

A JWT looks something like this, when information technology's serialized:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o

If y'all decode that base64, you'll get JSON in iii of import parts: header, payload and signature.

JWT Claims Example from JWT.io

The three parts of a JWT (based on image taken from jwt.io)
The serialized form is in the post-obit format:

[ base64UrlEncode(header) ] . [ base64UrlEncode(payload) ] . [ signature ]

A JWT is not encrypted. It is based64 encoded and signed. And then anyone can decode the token and use its data. A JWT'south signature is used to verify that information technology is in fact from a legitimate source.

Hither is the diagram of how a JWT is issued (/login) and then used to brand an API call to another service (/api) in a nutshell:

JWT End-to-End Workflow
A workflow of how a JWT is issued and so used

Ugh! This seems complicated. Why shouldn't I stick to adept old session tokens?

This is a painful give-and-take on the Internet. Our short (and opinionated answer) is that backend developers like using JWTs considering a) microservices b) not needing a centralized token database.

In a microservices setup, each microservice tin independently verify that a token received from a client is valid. The microservice tin can farther decode the token and excerpt relevant information without needing to have access to a centralized token database.

This is why API developers like JWTs, and we (on the client-side) need to effigy out how to use information technology. However, if you tin get away with a session token issued by your favourite monolithic framework, you're totally good to get and probably don't need JWTs!


Nuts: Login

Now that nosotros have a basic understanding what a JWT is, let'due south create a simple login flow and extract the JWT. This is what nosotros desire to achieve:

A login flow for getting a JWT

Then how do nosotros start?

The login procedure doesn't really change from what yous'd ordinarily practise. For example, here's a login form that submits a username/password to an auth endpoint and grabs the JWT token from the response. This could exist login with an external provider, an OAuth or OAuth2 step. Information technology really doesn't thing, as long as the client finally gets a JWT token in the response of the final login success step.

Outset, we'll build a simple login form to send the username and password to our login server. The server will issue JWT token and we will store it in retentivity. In this tutorial we won't focus on auth server backend, simply you're welcome to check it out in example repo for this blogpost.

This is what the handleSubmit handler for a login button might look like:

          async role handleSubmit () {   //...   // Make the login API call   const response = wait fetch(`/auth/login`, {     method: 'Mail',     body: JSON.stringify({ username, countersign })   })   //...   // Extract the JWT from the response   const { jwt_token } = await response.json()   //...   // Do something the token in the login method   look login({ jwt_token }) }                  

The login API returns a token and and then we pass this token to a login function from /utils/auth where we can decide what to exercise with the token one time we have it.

          import { login } from '../utils/auth' await login({ jwt_token })                  

So we've got the token, now where do we store this token?

Nosotros demand to save our JWT token somewhere, then that we can forward it to our API equally a header. You might be tempted to persist it in localstorage; don't practise it! This is prone to XSS attacks.

What about saving information technology in a cookie?

Creating cookies on the client to save the JWT will likewise be decumbent to XSS. If it tin be read on the client from Javascript exterior of your app - information technology can be stolen. Yous might think an HttpOnly cookie (created by the server instead of the client) will assistance, but cookies are vulnerable to CSRF attacks. It is of import to note that HttpOnly and sensible CORS policies cannot forbid CSRF form-submit attacks and using cookies require a proper CSRF mitigation strategy.

Annotation that a SameSite cookie will brand Cookie based approaches safe from CSRF attacks. It might not be a solution if your Auth and API servers are hosted on different domains, but it should work really well otherwise!

Where do we relieve it then?

The OWASP JWT Cheatsheet and OWASP ASVS (Application Security Verification Standard) prescribe guidelines for handling and storing tokens.

The sections that are relevant to this are the Token Storage on Customer Side and Token Sidejacking bug in the JWT Cheatsheet, and chapters 3 (Session Direction) and eight (Data Protection) of ASVS.

From the Cheatsheet, Consequence: Token Storage on the Client Side:

"This occurs when an application stores the token in a manner exhibiting the following behavior:"

  • Automatically sent past the browser (Cookie storage).
  • Retrieved fifty-fifty if the browser is restarted (Utilise of browser localStorage container).
  • Retrieved in example of XSS effect (Cookie attainable to JavaScript code or Token stored in browser local/session storage).

"How to Prevent:"

  • Shop the token using the browser sessionStorage container.
  • Add it equally a Bearer HTTP Authentication header with JavaScript when calling services.
  • Add fingerprint information to the token.

By storing the token in browser sessionStorage container information technology exposes the token to beingness stolen through a XSS attack. Nevertheless, fingerprints added to the token prevent reuse of the stolen token by the assaulter on their machine. To close a maximum of exploitation surfaces for an attacker, add a browser Content Security Policy to harden the execution context.

Where a fingerprint is the implementation of the following guidelines from the Token Sidejacking issue:

"Symptom:"

This set on occurs when a token has been intercepted/stolen by an attacker and they use it to gain admission to the arrangement using targeted user identity.

How to Foreclose:

A way to prevent it is to add a "user context" in the token. A user context will be composed of the following information:

  • A random cord that will exist generated during the authentication phase. It will be sent to the customer as an hardened cookie (flags: HttpOnly + Secure + SameSite + cookie prefixes).
  • A SHA256 hash of the random string will exist stored in the token (instead of the raw value) in order to prevent any XSS bug allowing the attacker to read the random string value and setting the expected cookie.

IP addresses should not be used considering there are some legitimate situations in which the IP address tin modify during the same session. For instance, when an user accesses an application through their mobile device and the mobile operator changes during the exchange, and then the IP accost may (often) change. Moreover, using the IP address can potentially crusade problems with European GDPR compliance.

During token validation, if the received token does not contain the right context (for example, if information technology has been replayed), then it must be rejected.


An implementation of this on the client side may look like:

          // Curt duration JWT token (5-10 min) export function getJwtToken() {     return sessionStorage.getItem("jwt") }  consign function setJwtToken(token) {     sessionStorage.setItem("jwt", token) }  // Longer duration refresh token (30-60 min) export function getRefreshToken() {     return sessionStorage.getItem("refreshToken") }  export function setRefreshToken(token) {     sessionStorage.setItem("refreshToken", token) }  office handleLogin({ e-mail, countersign }) {   // Phone call login method in API   // The server handler is responsible for setting user fingerprint cookie during this as well   const { jwtToken, refreshToken } = await login({ email, countersign })   setJwtToken(jwtToken)   setRefreshToken(refreshToken)    // If yous like, you may redirect the user now   Router.push("/some-url") }                  

Yes, the token volition be nullified when the user switches between tabs, but we will deal with that afterwards.

Ok! Now that we have the token what tin can nosotros practise with it?

  • Using in our API client to pass it every bit a header to every API call
  • Check if a user is logged in by seeing if the JWT variable is set.
  • Optionally, we can even decode the JWT on the client to access data in the payload. Let's say we demand the user-id or the username on the customer, which we can excerpt from the JWT.

How exercise we bank check if our user is logged in?

We bank check in our if the token variable is set and if it isn't - redirect to login page.

          const jwtToken = getJwtToken(); if (!jwtToken) {   Router.push('/login') }                  

Basics: Client setup

Now information technology'south time to set up our GraphQL client. The idea is to get the token from the variable we set, and if it'southward there, nosotros pass it to our GraphQL customer.


Using the JWT in a GraphQL client

Assuming your GraphQL API accepts a JWT auth token equally an Dominance header, all you need to practice is setup your customer to set an HTTP header by using the JWT token from the variable.

Here'south what a setup with the Apollo GraphQL client using an ApolloLink middleware.

          import { useMemo } from "react" import { ApolloClient, InMemoryCache, HttpLink, ApolloLink, Operation } from "@apollo/client" import { getMainDefinition } from "@apollo/client/utilities" import { WebSocketLink } from "@apollo/client/link/ws" import merge from "deepmerge"  let apolloClient  function getHeaders() {     const headers = {} as HeadersInit     const token = getJwtToken()     if (token) headers["Say-so"] = `Bearer ${token}`     return headers }  role operationIsSubscription(operation: Operation): boolean {     const definition = getMainDefinition(functioning.query)     const isSubscription = definition.kind === "OperationDefinition" && definition.operation === "subscription"     return isSubscription }  let wsLink function getOrCreateWebsocketLink() {     wsLink ??= new WebSocketLink({         uri: procedure.env["NEXT_PUBLIC_HASURA_ENDPOINT"].replace("http", "ws").replace("https", "wss"),         options: {             reconnect: true,             timeout: 30000,             connectionParams: () => {                 render { headers: getHeaders() }             },         },     })     render wsLink }  function createLink() {     const httpLink = new HttpLink({         uri: process.env["NEXT_PUBLIC_HASURA_ENDPOINT"],         credentials: "include",     })      const authLink = new ApolloLink((operation, frontwards) => {         performance.setContext(({ headers = {} }) => ({             headers: {                 ...headers,                 ...getHeaders(),             },         }))         return forward(operation)     })      if (typeof window !== "undefined") {         return ApolloLink.from([             authLink,             // Use "getOrCreateWebsocketLink" to init WS lazily             // otherwise WS connection will be created + used fifty-fifty if using "query"             ApolloLink.split(operationIsSubscription, getOrCreateWebsocketLink, httpLink),         ])     } else {         return ApolloLink.from([authLink, httpLink])     } }  function createApolloClient() {     return new ApolloClient({         ssrMode: typeof window === "undefined",         link: createLink(),         cache: new InMemoryCache(),     }) }  export office initializeApollo(initialState = cypher) {     const _apolloClient = apolloClient ?? createApolloClient()      // If your page has Side by side.js information fetching methods that use Apollo Customer, the initial country     // become hydrated here     if (initialState) {         // Become existing cache, loaded during client side data fetching         const existingCache = _apolloClient.excerpt()          // Merge the existing cache into data passed from getStaticProps/getServerSideProps         const data = merge(initialState, existingCache)          // Restore the cache with the merged information         _apolloClient.enshroud.restore(data)     }      // For SSG and SSR always create a new Apollo Client     if (typeof window === "undefined") render _apolloClient     // Create the Apollo Client one time in the client     if (!apolloClient) apolloClient = _apolloClient      render _apolloClient }  export function useApollo(initialState) {     const shop = useMemo(() => initializeApollo(initialState), [initialState])     render store }                  

As you tin run into from the lawmaking, whenever there is a token, it's passed every bit a header to every request.

Merely what will happen if at that place is no token?

It depends on the menstruation in your application. Permit's say you redirect the user back to the login page:

          else {  Router.push button('/login') }                  

What happens if a token expires as we're using it?

Permit'south say our token is only valid for fifteen minutes. In this case we'll probably get an mistake from our API denying our request (let's say a 401: Unauthorized mistake). Remember that every service that knows how to use a JWT tin can independently verify it and check whether it has expired or non.

Let's add together fault treatment to our app to handle this case. We'll write lawmaking that volition run for every API response and cheque the error. When we receive the token expired/invalid error from our API, we trigger the logout or the redirect to login workflow.

Here's what the lawmaking looks like if nosotros're using the Apollo client:

          import { onError } from 'apollo-link-mistake';  const logoutLink = onError(({ networkError }) => {     if (networkError.statusCode === 401) logout(); })  if (typeof window !== "undefined") {     render ApolloLink.from([         logoutLink,         authLink,         ApolloLink.split(operationIsSubscription, getOrCreateWebsocketLink, httpLink),     ]) } else {     render ApolloLink.from([       logoutLink,       authLink,       httpLink     ]) }                  

Y'all may notice that this will outcome in a fairly sucky user experience. The user will go on getting asked to re-authenticate every fourth dimension the token expires. This is why apps implement a silent refresh workflow that keeps refreshing the JWT token in the groundwork. More than on this in the side by side sections below!

Basics: Logout

With JWTs, a "logout" is simply deleting the token on the client side then that it tin't be used for subsequent API calls.

check-token-flow

So...is there no /logout API call at all?

A logout endpoint is non really required, because any microservice that accepts your JWTs volition keep accepting it. If your auth server deletes the JWT, it won't thing considering the other services will go on accepting it anyway (since the whole point of JWTs was to not crave centralised coordination).

The token is nonetheless valid and can be used. What if I need to ensure that the token cannot be used ever once again?

This is why keeping JWT expiry values to a small value is important. And this is why ensuring that your JWTs don't go stolen is even more important. The token is valid (even afterward you delete it on the client), but only for short flow to reduce the probability of it being used maliciously.

In addition, yous can add a deny-listing workflow to your JWTs. In this case, yous can take a /logout API telephone call and your auth server puts the tokens in a "invalid listing". Still, all the API services that swallow the JWT now need to add an additional step to their JWT verification to bank check with the centralized "deny-list". This introduces central land over again, and brings the states back to what we had before using JWTs at all.

jwt-blogpost-3-check-token-flow-serverside

Doesn't deny-list negate the do good of JWT not needing whatsoever cardinal storage?

In a way it does. It's an optional precaution that you tin can take if you are worried that your token can get stolen and misused, but it likewise increases the corporeality of verification that has to exist done. Every bit yous tin can imagine, this had led to much gnashing of teeth on the internet.

What volition happen if I am logged in on different tabs?

One way of solving this is by introducing a global outcome listener on localstorage. Whenever we update this logout key in localstorage on one tab, the listener volition fire on the other tabs and trigger a "logout" also and redirect users to the login screen.

          window.addEventListener('storage', this.syncLogout)   //....  syncLogout (effect) {   if (consequence.key === 'logout') {     console.log('logged out from storage!')     Router.push('/login')   } }                  

These are the ii things we now demand to do on logout:

  1. Nullify the token
  2. Prepare logout item in local storage
          import { useEffect } from "react" import { useRouter } from "next/router" import { gql, useMutation, useApolloClient } from "@apollo/customer" import { setJwtToken, setRefreshToken } from "../lib/auth"  const SignOutMutation = gql`     mutation SignOutMutation {         signout {             ok         }     } `  function SignOut() {     const client = useApolloClient()     const router = useRouter()     const [signOut] = useMutation(SignOutMutation)      useEffect(() => {         // Articulate the JWT and refresh token so that Apollo doesn't try to employ them         setJwtToken("")         setRefreshToken("")         // Tell Apollo to reset the store         // Finally, redirect the user to the abode folio         signOut().then(() => {             // to back up logging out from all windows             window.localStorage.setItem('logout', Date.at present())             customer.resetStore().then(() => {                 router.push("/signin")             })         })     }, [signOut, router, client])      render <p>Signing out...</p> }                  

In that case whenever y'all log out from one tab, event listener will fire in all other tabs and redirect them to login screen.

This works across tabs. But how exercise I "force logout" of all sessions on different devices?!

We cover this topic in a footling more than detail in a section later on on: Strength logout.

Silent refresh

In that location are 2 major bug that users of our JWT based app will all the same face:

  1. Given our short decease times on the JWTs, the user volition be logged out every 15 minutes. This would be a adequately terrible experience. Ideally, we'd probably want our user to be logged in for a long time.
  2. If a user closes their app and opens it again, they'll need to login once again. Their session is not persisted because nosotros're not saving the JWT token on the client anywhere.

To solve this trouble, most JWT providers, provide a refresh token. A refresh token has two backdrop:

  1. Information technology tin can be used to make an API telephone call (say, /refresh_token) to fetch a new JWT token before the previous JWT expires.
  2. It tin can be safely persisted across sessions on the client!

How does a refresh token piece of work?

This token is issued equally part of authentication process along with the JWT. The auth server should saves this refresh token and associates it to a particular user in its own database, so that information technology can handle the renewing JWT logic.

On the customer, before the previous JWT token expires, we wire up our app to brand a /refresh_token endpoint and take hold of a new JWT.

How is a refresh token safely persisted on the client?

We follow the guidelines in the OWASP JWT Guide to prevent issues with client-side storage of a token.

Improper client-side storage occurs when "an awarding stores the token in a manner exhibiting the post-obit behavior":

  • Automatically sent by the browser (Cookie storage).
  • Retrieved even if the browser is restarted (Employ of browser localStorage container).
  • Retrieved in case of XSS issue (Cookie accessible to JavaScript code or Token stored in browser local/session storage).

To foreclose this, the following steps are taken:

  • Store the token using the browser sessionStorage container.
  • Add it as a Bearer HTTP Hallmark header with JavaScript when calling services.
  • Add fingerprint information to the token.

By storing the token in browser sessionStorage container it exposes the token to being stolen through a XSS attack. All the same, fingerprints added to the token forestall reuse of the stolen token by the attacker on their machine. To close a maximum of exploitation surfaces for an attacker, add together a browser Content Security Policy to harden the execution context.

Where the implementation of a fingerprint also serves to prevent Token Sidejacking from occuring, and is washed according to the guidelines here

So what does the new "login" process await like?

Zilch much changes, except that a refresh token gets sent along with the JWT. Let'southward take a look a diagram of login process once more, simply now with refresh_token functionality:

Login with refresh token

  1. The user logs in with a login API call.
  2. Server generates JWT token and refresh_token, and a fingerprint
  3. The server returns the JWT token, refresh token, and a SHA256-hashed version of the fingerprint in the token claims
  4. The united nations-hashed version of the generated fingerprint is stored as a hardened, HttpOnly cookie on the customer
  5. When the JWT token expires, a silent refresh will happen. This is where the client calls the /refresh token endpoint

And now, what does the silent refresh expect similar?

Silent refresh workflow

Here'south what happens:

  1. The refresh endpoint must bank check for the beingness of the fingerprint cookie, and validate that the comparison of the hashed value in the token claims is identical to the unhashed value in the cookie
  2. If either of these weather are not met, the refresh request is rejected
  3. Otherwise the refresh token is accepted, and a fresh JWT access token is granted, resetting the silent refresh process

An implementation of this workflow using the apollo-link-token-refresh parcel, is something similar the beneath.
Using this as a non-terminating link will automatically check the validity of our JWT, and endeavour a silent refresh if needed when whatever operation is run.

          import { TokenRefreshLink } from "apollo-link-token-refresh" import { JwtPayload } from "jwt-decode" import { getJwtToken, getRefreshToken, setJwtToken } from "./auth" import decodeJWT from "jwt-decode"  export function makeTokenRefreshLink() {     return new TokenRefreshLink({         // Indicates the current land of access token expiration         // If token not yet expired or user doesn't have a token (guest) truthful should exist returned         isTokenValidOrUndefined: () => {             const token = getJwtToken()              // If in that location is no token, the user is not logged in             // We return true here, because there is no need to refresh the token             if (!token) return true              // Otherwise, we check if the token is expired             const claims: JwtPayload = decodeJWT(token)             const expirationTimeInSeconds = claims.exp * yard             const now = new Date()             const isValid = expirationTimeInSeconds >= now.getTime()              // Render truthful if the token is notwithstanding valid, otherwise false and trigger a token refresh             return isValid         },         // Responsible for fetching refresh token         fetchAccessToken: async () => {             const jwt = decodeJWT(getJwtToken())             const refreshToken = getRefreshToken()             const fingerprintHash = jwt?.["https://hasura.io/jwt/claims"]?.["10-User-Fingerprint"]              const request = await fetch(process.env["NEXT_PUBLIC_HASURA_ENDPOINT"], {                 method: "Mail",                 headers: {                     "Content-Type": "application/json",                 },                 body: JSON.stringify({                     query: `                   query RefreshJwtToken($refreshToken: String!, $fingerprintHash: String!) {                     refreshJwtToken(refreshToken: $refreshToken, fingerprintHash: $fingerprintHash) {                       jwt                     }                   }                 `,                     variables: {                         refreshToken,                         fingerprintHash,                     },                 }),             })              return request.json()         },         // Callback which receives a fresh token from Response.         // From here we tin can relieve token to the storage         handleFetch: (accessToken) => {             setJwtToken(accessToken)         },         handleResponse: (operation, accessTokenField) => (response) => {             // here y'all can parse response, handle errors, prepare returned token to             // further operations             // returned object should exist like this:             // {             //    access_token: 'token cord here'             // }             return { access_token: response.refreshToken.jwt }         },         handleError: (err) => {             panel.warn("Your refresh token is invalid. Try to reauthenticate.")             console.error(err)             // Remove invalid tokens             localStorage.removeItem("jwt")             localStorage.removeItem("refreshToken")         },     }) }                  

Referring dorsum to the section addressing: "What will happen if I'g logged in on multiple tabs?", using sessionStorage for this means nosotros won't be authenticated in new tabs (if they weren't created using "Duplicate tab") or windows.

A potential solution to this, while still remaining secure, is to utilize localStorage equally an event-emitter again and sync sessionStorage between tabs of the same base URL on load.

This can exist accomplished by using a script such as this on your pages:

          if (!sessionStorage.length) {     // Enquire other tabs for session storage     localStorage.setItem("getSessionStorage", String(Date.at present())) }  window.addEventListener("storage", (event) => {     if (event.key == "getSessionStorage") {         // Some tab asked for the sessionStorage -> send information technology         localStorage.setItem("sessionStorage", JSON.stringify(sessionStorage))         localStorage.removeItem("sessionStorage")     } else if (issue.key == "sessionStorage" && !sessionStorage.length) {         // sessionStorage is empty -> fill up it         const data = JSON.parse(effect.newValue)         for (permit primal in data) {             sessionStorage.setItem(key, data[key])         }     } })                  

Persisting sessions

Persisting sessions is confronting the OWASP security guidelines for clients and token hallmark:

"... Retrieved even if the browser is restarted (Use of browser localStorage container)."

In that location is (at the time of writing) no way deemed acceptable that allows for a persistent user session after a browser has been fully closed and re-opened, unless the browser implementation retains tab session country (sessionStorage).

Y'all may choose to shop your token in localStorage or a Cookie instead, in lodge to have persistent sessions across browser restarts, but doing so is at your discretion.

Notation: For an ongoing discussion of this topic, encounter https://github.com/OWASP/ASVS/problems/1141


Forcefulness logout, aka Logout of all sessions/devices

Now that are users are logged in forever and stay logged in beyond sessions, at that place'southward a new problem that nosotros need to worry well-nigh: Forcefulness logout or, logging out of all sessions and devices.

The refresh token implementations from the sections to a higher place, testify us that we can persist sessions and stay logged in.

In this case, a simple implementation of "forcefulness logout" is request the auth server to invalidate all refresh tokens associated for a particular user.

This is primarily an implementation on the auth server backend, and doesn't need whatever special treatment on the client. Apart from a "Force Logout" button on your app peradventure :)


Server side rendering (SSR)

In server side rendering at that place are boosted complexities involved when dealing with JWT tokens.

This is what we desire:

  1. The browser makes a request to a app URL
  2. The SSR server renders the page based on the user's identity
  3. The user gets the rendered page and and so continues using the app equally an SPA (single page app)

How does the SSR server know if the user is logged in?

The browser needs to ship some data most the electric current user's identity to the SSR server. The only way to do this is via a cookie.

Since nosotros've already implemented refresh token workflows via cookies, when nosotros brand a request to the SSR server, we demand to make sure that the refresh-token is also sent along.

Annotation: For SSR on authenticated pages, it is vital that that the domain of the auth API (and hence the domain of the refresh_token cookie) is the same as the domain of the SSR server. Otherwise, our cookies won't be sent to the SSR server!

jwt-blogpost-6-ssr1

This is what the SSR server does:

  1. Upon receiving a request to return a item folio, the SSR server captures the refresh_token cookie.
  2. The SSR server uses the refresh_token cookie to go a new JWT for the user
  3. The SSR server uses the new JWT token and makes all the authenticated GraphQL requests to fetch the right data

Can the user continue making authenticated API requests once the SSR page has loaded?

Nope, not without some additional fiddling around unfortunately!

Once the SSR server returns the rendered HTML, the only identification left on the browser virtually the user's identity is the old refresh token cookie that has already been used by the SSR server!

If our app code tries to use this refresh token cookie to fetch a new JWT, this request volition fail and the user will get logged out.

To solve this, the SSR server after rendering the folio needs to ship the latest refresh token cookie, so that the browser can utilise information technology!

The entire SSR menses, end to end:

jwt-blogpost-7-ssr2


Lawmaking from this blogpost (finished application)

Sample code for this blogpost with an end to finish working app, with SSR capabilities is available hither.

https://github.com/hasura/jwt-guide

The repository also contains the sample auth backend code.


Attempt it out!

Gear up up a gratis GraphQL backend with Hasura Cloud to try it out for yourself!

Make sure you're on version ane.iii or above and you're skilful to go.


References

  • JWT.io
  • OWASP notes on XSS, CSRF and like things
    • OWASP JWT Cheatsheet
    • OWASP Application Security Verification Standard, v5
  • The Parts of JWT Security Nobody Talks About | Philippe De Ryck
    Lots of other awesome stuff on the web

Summary

One time yous've worked through all the sections above, your app should now have all the capabilities of a mod app, using a JWT and should be secure from the common major security gotchas that JWT implementations take!

Permit us know on twitter or in the comments below if you take any questions, suggestions or feedback!

Changelog

  • (12/28/2021) Recommendation to shop token in Cookie changed to sessionStorage, per OWASP JWT guidelines to accost Consequence: Token Storage on Client Side 0
  • (12/28/2021) Adopted OWASP Application Security Verification Standard v5 6 L1-L2 guidelines
    • Of annotation: Capacity 3 (Session Direction) and eight (Data Protection)
  • (12/28/2021) Modify section on Persisting Sessions to incorporate OWASP guidelines on this
  • (12/28/2021) Sample application repo code updated 1
    • Update from Next.js 9 -> 12, update @apollo libraries to v3.ten
    • Countersign hashing algorithm changed from bcrypt to native Node.js crypto.scrypt per OWASP guidelines 2 and to reduce number of external dependencies
    • Authentication on frontend and backend modified to make utilize of a user fingerprint in improver to a token, per OWASP guidelines on preventing Token Sidejacking 3
    • Example usage of TokenRefreshLink 4 to manage silent refresh workflow added
    • Server endpoints integrated through Hasura Deportment 5 rather than direct invoking from client
    • Prefer recommended employ of crypto.timingSafeEqual() to prevent timing attacks

brentlichannoosee.blogspot.com

Source: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/

0 Response to "Snowboy Fail to Read Token in Expecttoken()"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel