Skip to content

Instantly share code, notes, and snippets.

@duranmla
Last active October 1, 2018 19:32
Show Gist options
  • Save duranmla/37e2484692b904b45f045b00f07b73e1 to your computer and use it in GitHub Desktop.
Save duranmla/37e2484692b904b45f045b00f07b73e1 to your computer and use it in GitHub Desktop.
Handle the refresh token process

#27 Handle session refreshing using the provided refresh token

Author

Alexis Duran

Constrains

  • The token needed to make requests should not be needed to be passed from within async action, API should handle it.
  • If the user has a expired session and it couldn't refresh the application should notify and logout the user from the application.

Background

I. There is an overview of an approach related at: javascript - How to use Redux to refresh JWT token? - Stack Overflow which basically stands to create a middleware that will see for a request, verify if the token is expired and if so, make a request before continue, otherwise, it will continue.

II. Seems like it is popular to use middlewares approach for API, besides, there is a tool GitHub - agraboso/redux-api-middleware: Redux middleware for calling an API. which make some of the things that we need.Access Token Handling (Automatic Refresh) with React + Redux ▪ NMajor Blog

III. Here is also a good information that stands that the thunks that we use not only pass the dispatch fuction but also a getState method. javascript - Right way to use JWT auth token in React Redux app - Stack Overflow besides, a little bit lower there is a declaration that we can define defaults using axions instance javascript - Right way to use JWT auth token in React Redux app - Stack Overflow also reference for default params at: Support for axios.default.params? · Issue #229 · axios/axios · GitHub

IV. Diging more about the middlewares there is fairly simple middleware implementation here: react-redux-universal-hot-example/clientMiddleware.js at master · erikras/react-redux-universal-hot-example · GitHub that can give us some idea about how we will interact with the actions if we choose this path, besides, I stumble with this solution after the post Access Token Handling (Automatic Refresh) with React + Redux ▪ NMajor Blog

Theory of Operantion

The key points to keep in mind are:

  • The pieces that we have: redux-thunk, axios, API class (with instance as axios wrapper).
  • The key findings: we have getState on async actions, axios has default params, middlewares are popular way to solve problems related to api calls.

Approach

I will define N steps to be implemented where N.x and N.y are multiple ways to solve the same step.

Updating the token to be used over all requests:

A. We can add a method within the API class to update the token param.

class API {
  // ...
  
  public setDefaultTokenParam(token) {
    // set axios instance to have default param of token
  }
}

Define the way we verify if we need or not to refresh the token:

B.1 We can choose to not use the middleware and keep that verification within our API wrapper. the problem here may be to access to the session data which is within the application state, however, we can accomplish that by passing the getState from the thunk to the api method that perform the request. The other problem is that we are not able to send actions after perform any operation which again can be solved if we pass dispatch param to the api method calls. Lastly, we are assuming that even when we declare the function api call the default param used will be get when the request is performed

class API {
  // ...
  enqueueRequests = []
  
  private _isTokenExpired(session) {
    // access to the session information and compare: (session.created_at + session.expires_in) < now

    return isExpired;
  }
  
  private _request(apiCall, { getState, dispatch }) {
    let { session } = getState()
    
    if (this.isTokenExpired(session)) {
      this.enqueueRequests.push(apiCall);
      this._requestRefreshToken(session.refresh_token)
        .then((response) => this._onRequestRefreshTokenSucess(response, dispatch))
        .catch((e) => this._onRequestRefreshTokenFailure(e, dispatch))
    }
    
    return apiCall();
  
    
  private _requestRefreshToken(refreshToken) {
    // perform a request using our current approach and returning that promise so the flow won't break
  }
  
  private _onRequestRefreshTokenSucess(r, dispatch) { 
    // 1. dispatch update the session state over the application state UPDATE_SESSION_SUCCESS
    // 2. use the `setDefaultTokenParam` method to define the default param for further requests
    // 3. use the `_flushEnqueueRequests` method to run all pending requests using the new token param
  }
  
  private _onRequestRefreshTokenFailure(r, dispatch) {
    // 1. dispatch UPDATE_SESSION_FAILURE -> which will trigger an alert to notify the user that the session has expired
    // 2. use the `_clearEnqueueRequests` to clear the requests without execute them
  }
  
  private _flushEnqueueRequests() {
    this.enqueueRequests.each((req) => req());
  }
  
  private _clearEnqueueRequests() {
    this.enqueueRequests = []
  }
}

B.2 We may want to define our middleware that we will serve as a way to constantly check if we need to update, the middleware already has the methods that we miss on the above approach, however, What whould happen if there is another request triggered before the refresh token has been resolved? How we will avoid for asking new refresh token without need it? in that case as we are not dispatching the next method we are safe to assume that there is no other action dispatch until we do that next call.

import api from "path/to/api/index.js"

function apiMiddleware() {
  return ({ dispatch, getState }) => next => (action) => {
    // 1. IF the action is not a request action AND is not the refresh token one then just leave it
    if (!action.type includes ["REQUEST"] AND !action.type includes ["REFRESH_TOKEN"]) {
      return next(action);
    }
    
    // 2. IF the thresholdtime have been passed we need to make an additional request before move on
    const { session } = getState();
    
    // NOTE: in this case the methods implemented above works, nevertheless, some of them need to be public
    api.requestRefreshToken(session.refresh_token)
      .then((response) => {
        api.onRequestRefreshTokenSucess(response, dispatch)
        // NOTE: for this approach we can also drop the `enqueueRequests` related code above
        // 3. AFTER 2 has been resolved then we release the calls
        next(action)
      })
      .catch((e) => api.onRequestRefreshTokenFailure(e, dispatch))
  }
}
@duranmla
Copy link
Author

duranmla commented Oct 1, 2018

One update for the implementation is that we will just keep the redux related code over the middleware method and the rest of the code will be for the API class itself, (just in order to keep the responsibilities of the API class agnostic to redux)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment