I have been working on a login system for my react-redux app. The login should in essence do the following: allow the user to log in, access certain pages only after login, use the token/authentication from the user to make calls to the server using axios. For persisting the state of the redux store, I use redux persist. The state is indeed persisted and stored in the local storage. For accessing the different pages after the user logged in (without having to log in again), redux persist works great.
I had however trouble using the user authentication (in further development to change for a token) as the default authorization header in axios (see here and here for further explanation about default headers). Although the state of the redux store (where the authentication header of the user is stored at the moment of successful login) is indeed persisted (I can see it in the redux development tool and the local storage), axios was unable to use the persisted state. I managed to get it working in the end by calling the window.localStorage.getitem() (see code below). This seems however an extra complication.
Question 1: Although everything is working fine, I was wondering if there are better ways to get this working?
The flow is currently this. When the user logs in successfully, the authentication details (in the future to change for a token) are store in the redux state using the following steps:
handleLogin.js:
import axios from "axios";
import {store} from '../store'
import { loginRequest, loginSucces, loginFailure } from './loginActions';
import refreshPage from "../handlerefresh/handleRefresh";
const path = process.env.REACT_APP_Path; //stil to change for a default header in axios. cf supra.
export const handleLogin = content => {
var credentials = btoa(content.username + ':' + content.password);
var basicAuth = 'Basic ' + credentials;
return dispatch => {
dispatch(loginRequest(basicAuth));
axios
.get( `${path}/_security/_authenticate`,
{headers: {'Authorization': basicAuth}},
)
.then(res => {
console.log(res);
dispatch(loginSucces(res));
axios.defaults.headers.common['Authorization'] = store.getState().loginAPI.authentication; //function sets the default header of axios to the current users login information. this might be redundant (?)
// console.log("this should be the authorization " + axios.defaults.headers.common['Authorization'])
refreshPage()
})
.catch(error => {
dispatch(loginFailure(error.message))
console.log(error);
});
};
};
These actions are defined in: loginActions.js.
import {LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE } from './loginTypes'
export const loginRequest = (request) => {
return{
type: LOGIN_REQUEST,
payload: request
}
}
export const loginSucces = response => {
return{
type: LOGIN_SUCCESS,
payload: response
}
}
export const loginFailure = error => {
return{
type: LOGIN_FAILURE,
payload: error
}
}
With the following reducer: loginReducer.js
import {LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE } from './loginTypes';
import {LOGOUT_REQUEST, LOGOUT_SUCCESS, LOGOUT_FAILURE } from './logoutTypes';
const initialLoginState = {
loading: false,
loggedIn: false,
user: '',
error: '',
authentication: 'undefined',
}
const loginAPIReducer = (state = initialLoginState, action) => {
console.log(action.type)
switch(action.type){
case LOGIN_REQUEST:
return{
...state,
loading: true,
authentication: action.payload
}
case LOGIN_SUCCESS:
return{
...state,
loading: false,
user: action.payload,
error: '',
loggedIn: true
}
case LOGIN_FAILURE:
return{
loading: false,
user: '',
loggedIn: false,
error: action.payload
}
case LOGOUT_REQUEST:
return{
...state,
loading: true
}
case LOGOUT_SUCCESS:
return{
loading: false,
user: '',
loggedIn: false,
error: '',
authentication: action.payload
}
case LOGOUT_FAILURE:
return{
loading: false,
error: action.payload
}
default:
return state;
}
}
export default loginAPIReducer;
Because I was unable to get the persisted state from redux persist to be used as an authorization header for all the axios requests, I wrote the following function, which gets the authorization header from the local storage:
provideUserAuth.js
function provideUserAuth(){
// I split in these comments the authentication into smaller steps for clarity.
// var state = window.localStorage.getItem('reduxState');
// var JSONstate = JSON.parse(state);
// var authentication = JSONstate.loginAPI.authentication;
// return authentication;
var authentication = JSON.parse(window.localStorage.getItem('reduxState')).loginAPI.authentication;
return authentication
}
export default provideUserAuth;
This function is called in the index.js file, to set the default header for all the axios requests.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistedStore } from '../src/redux/store';
import Loader from './components/loader/loader';
import { fetchRVVBCases } from './redux/raadvoorvergunningsbetwistingen/rvvbReducer';
import { fetchBURGCases } from './redux/burgerlijkehovenenrechtbanken/burgReducer';
import {fetchREVIEWCases} from './redux/review/reviewReducer';
import provideUserAuth from './redux/auth/provideUserAuth';
const axios = require('axios');
axios.interceptors.request.use(async function (config) {
const token = provideUserAuth();
console.log("this is the " + token);
config.headers.Authorization = token;
return config;
});
axios.interceptors.request.use(request => {
console.log("current axios request: " + JSON.stringify(request, null, 2));
// Edit request config
return request;
}, error => {
console.log(error);
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
console.log(response);
// Edit response config
return response;
}, error => {
console.log(error);
return Promise.reject(error);
});
store.dispatch(fetchRVVBCases());
store.dispatch(fetchBURGCases());
store.dispatch(fetchREVIEWCases());
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={<Loader />} persistor={persistedStore}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: <omit>;
It works fine but it seems an extra complication.
Second question: Secondly, I was wondering whether this way of working is secure. The authorization will be changed for a token, but this token still persists in the local storage, even after logging out. Should I add a function localstorage.removeItem() or even .clear() to clean up the local storage after logging out or closing the browser?
Thank you for your time and have a nice weekend!