Refresh auth0 token in SPA

You may have heard of auth0 before, if not, it's a service of user authentication, which provides functions for user sign up, login, social login etc, so you don’t have to implement them yourself.

I recently used auth0 in one of my SPA project, after the integration, the user can signup/login to my application, auth0 will return a id_token, which the application can store in the browser, use it to verify the user.

So far so good. However, for security concerns, the id_token have a relatively short expire time, so we need to handle the situation where the token expires while the user is using the application.

The expiration may occur on every api request, so I decide to handle it in the api request layer.

Here is my api module with axios:

import axios from 'axios'
import { getToken } from './auth'

const api = axios.create()

api.interceptors.request.use(config => {
  const token = getToken()
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}`
  }
  return config
})

It should be easy to understand: whenever an api request is sending to the server, the request interceptor will add an Authorization header with the id_token, if it exists.

Server side

the server should return a 401 Unauthorized error if there is no valid token in the request, an expired token is not a valid one of course. if you’re using koa, there’s koa-jwt, or express-jwt if you’re using express.

Client side

we need to add a response interceptor to handle the 401 error:

api.interceptors.response.use(undefined, (error) => {
  if (error.response.status !== 401 || error.config.__isRetryRequest) {
    throw error
  }
  return refreshToken()
    .then((newToken) => {
      error.config.__isRetryRequest = true
      error.config.headers['Authorization'] = `Bearer ${newToken}`
      return axios(error.config)
    })
    .catch((error) => {
      // can't refresh the token, guide the user to login again
    })
})

as you can see, if the response is a 401 error, we’ll refresh the token and resend the request with the new token. So wha’t inside refreshToken?

import { WebAuth } from 'auth0-js'
import Promise from 'bluebird'
import { AUTH0_CLIENT_ID, AUTH0_DOMAIN } from './config'

const auth0 = new WebAuth({
  domain: AUTH0_DOMAIN,
  clientID: AUTH0_CLIENT_ID,
  responseType: 'token id_token'
})

function refreshToken () {
  return Promise.fromCallback(callback => {
    return auth0.renewAuth({
      scope: 'openid',
      redirectUri: 'https://example.com/silent-callback.html',
      usePostMessage: true
    }, callback)
  }).then(authResult => authResult.idToken)
}

...

Here we first initialized a WebAuth instance with out domain and clientID, then what the refreshToken function is doing is to call auth0.renewAuth and return the resulting token as a promise.

Note the redirectUri here, it’s a dedicated callback page for refreshing the token, so your application don’t need to be loaded again in an iframe. This is what it is like:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.auth0.com/js/auth0/8.0.4/auth0.min.js"></script>
    <script type="text/javascript">
      var webAuth = new auth0.WebAuth({
        domain: '...',
        clientID: '...'
      })
      var result = webAuth.parseHash(window.location.hash, function(err, data) {
        parent.postMessage(err || data, "https://example.com/")
      })
    </script>
  </head>
  <body></body>
</html>

OK, so this is the approach of refreshing id_token I’m using, hope it will help someone.

comments powered by Disqus