Michael Seltenreich
Michael Seltenreich

Reputation: 3508

React-boilerplate: Implementing JWT authentication. Attempting to dispatch action from service

Environment: This is code I'm trying to implement on top of the tried and tested React-boilerplate. While this boilerplate is wonderful, it doesn't contain authentication. I'm trying to implement my own authentication logic in a way that meshes with the existing logic of the boilerplate.

Context:

I have a service/utility function that is used to send the server requests with a JWT authentication header and return its response.

Goal:

I'm trying to implement logic where when a request is made using an expired token, the access token is automatically refreshed before the request is sent to the server.

What's stopping me:

  1. I have a saga that handles the access token refresh. It works perfectly when it is called from within a container. But because this is a service, it is unaware of the redux store. For this reason I am unable to dispatch actions.

  2. The react-boilerplate structure works by injecting reducers and sagas on the fly. This means I'd need to inject the authentication saga and reducer from within the service (kinda feels wrong no?). However, the saga and reducer injectors are React side-effects and can, therefore, only be used inside of React components.

I feel like the task I'm trying to achieve is quite trivial (and I'm sure it was implementing a million times already), and yet, I can't think of a solution that doesn't seem to go against the entire logic of why use Redux or Sagas to begin with.

Can anyone offer some insights? In the attached image, the red text is the part I'm struggling to implement enter image description here

See code below:

/**
 * Requests a URL, returning a promise
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch".
 *
 * @return {object}           The response data
 */
export default function request( url, options ) {

    const token = makeSelectAccessToken();
    if (!token) throw new Error('No access token found.');

    // Refresh access token if it's expired.
    if (new Date(token.expires) - Date.now() <= 0) {
      // TODO: Attempt to use refresh token to get new access token before continuing
      /** dispatch(REFRESH_ACCESS_TOKEN) **/

      // PROBLEM: can't dispatch actions because the store is not exposed to this service.
      // Secondary challenge: Can't inject Saga or Reducer because the React-boilerplate injectors are React side-effects.
    }

  options = {
    ...options,
    Authorization: `Bearer ${token.token}`, // Adding the JWT token to the request
  };

  return fetch(url, options)
}

Upvotes: 1

Views: 658

Answers (2)

Onur Gelmez
Onur Gelmez

Reputation: 1044

There is multiple way to do that

1- Export the store

create new store.js

import { createStore } from 'redux';
import reducer from './reducer';
//you can add your middleware, sagas, ...
const store = createStore(reducer);

export default store;

import it to your service and use it everywhere but you’ll end up with a single store for all of your users

  • Cons: you’ll end up with a single store for all of your users(if your app is using Server Side Rendering don't use this method)

  • Pros: Easy to use

2- You can add your func to middleware and intercept an action

edit your create store code and add new middleware for authorization

const refreshAuthToken = store => next => action => {
  
  const token = makeSelectAccessToken();
  if (!token) throw new Error('No access token found.');

  if(new Date(token.expires) - Date.now() <= 0) {
    // you can dispatch inside store
    this.store.dispatch(REFRESH_ACCESS_TOKEN);
  }

  // continue processing this action
  return next(action);
}

const store = createStore(
  ... //add to your middleware list
  applyMiddleware(refreshAuthToken)
);
  • Cons: Maybe you can need redux-thunk library if this middleware not solve your problem (its easy job. You can't say this even a con)

  • Pros: Best and safer way for your need and it will work everytime you call an action. It will works like a charm on SSR apps

3- or you can send store as parameter to your request method from component

  • Cons: Not the best way it will run after your components initialize and you can make easily mistake or can forget to add to your component.

  • Pros: At least you can do what you want

Upvotes: 1

Fernando Bravo Diaz
Fernando Bravo Diaz

Reputation: 579

Maybe a better approach would be to store the token on the localStorage so that you don't need to access the Redux store to obtain it. Just fetch the token and store it this way:

localStorage.setItem('token', JSON.stringify(token))

Later in your service, just retrieve like this:

const token = JSON.parse(localStorage.getItem('token'));

If you don’t feel safe storing the token in the local storage, there's also the secure-ls package, that allows encryption for the data saved to the local storage.

Upvotes: -1

Related Questions