React, Redux, Auth state and API

Kirill Konshin
Apr 20, 2018 · 2 min read

One of the most frequently asked questions about React, Redux and REST is where to put authentication (OAuth tokens for example), how to wire it to Redux and how to add tokens to API calls.

Since user auth is a critical part of app state it makes sense to put it into Redux store. I suggest to add an API call method to Redux by utilizing the redux-thunk middleware’s functionality and dispatch Promise from API actions by using redux-promise-middleware. Let’s begin with store configuration:

import {applyMiddleware, createStore} from "redux";
import thunkMiddleware from "redux-thunk";
import promiseMiddleware from "redux-promise-middleware";
import isPromise from "is-promise";
import {request} from "./lib";
import reducers from "./reducers";
const HTTP_REJECTED = 'HTTP_REJECTED';const suppressedTypes = [HTTP_REJECTED];// this middleware suppresses uncaught rejections for HTTP actions
const errorMiddleware = () => next => action => {

if (!isPromise(action.payload)) return next(action);

if (suppressedTypes.includes(action.type)) {
// Dispatch initial pending promise, but catch any errors
return next(action).catch(error => {
console.warn('Middleware has caught an error', error);
return error;
});
}
return next(action);};export default (initialState) => { let store;

// this is some library that makes requests
const api = ({method = 'GET', url, data, query}) => request({
method,
url,
data,
query,
token: store.getState().token // here goes your selector
}).catch(e => {
// service action for reducers to capture HTTP errors
store.dispatch({
type: HTTP_REJECTED,
payload: e
});
throw e; // re-throw an error }); store = createStore(
reducers,
initialState,
applyMiddleware(
thunkMiddleware.withExtraArgument(api),
errorMiddleware, // must be before promiseMiddleware
promiseMiddleware()
)
);
return store;};

Note that we first initialize empty store that we will access later in api function because we need api function as parameter for thunkMiddleware.withExtraArgument which is used for store creation. This can be done in a more elegant way, of course, but for simplicity we do it this brutal way.

Then you can dispatch a Promise returned from API method that becomes available in actions (redux-thunk makes it possible):

export const loadUser = (id) => (dispatch, getState, api) => dispatch({
type: 'LOAD_USER',
payload: api({url: `/users/${id}`})
});
export const login = (username, password) =>
(dispatch, getState, api) => dispatch({
type: 'LOGIN',
payload: api({
method: 'POST',
url: `/login`,
body: {username, password}
})
});

In reducer you can now also invalidate token on HTTP 401 errors:

import {combineReducers} from "redux";const isHttpAuthError = (payload) => (
// make sure request library error has response in it
payload && payload.response && payload.response.status === 401
);
const token = (state = null, {type, payload}) => {
switch (type) {
case ('LOGIN_FULLFILLED'):
// here you get a token from login API response
return payload.token;
case (HTTP_REJECTED):
return (isHttpAuthError(payload)) ? null : state;
case ('LOGIN_REJECTED'):
case ('LOGOUT_FULLFILLED'):
// here you reset the token
return null;
default:
return state;
}
};
export default combineReducers({
token
});

This all makes your app state to be always consistent, you can capture network auth-related errors in the same place where you take care of other token-related processes.

This repo https://github.com/kirill-konshin/react-redux-router-auth contains all above mentioned concepts and a bit more.

D.i.S

Developer/ DJ / Producer / Photographer / Traveller

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store