14

I'm trying to develop a React/Redux application for learning purposes and I'm wondering if what I've done so far is a good practice or is not recommended. The first thing that I had to deal with was handle the authorized requests. I have a restfull api Rails back end. This server respond to the login request appending in the headers parameters such as access-token. This access-token is valid just for the next request, that in turn, return a new token valid for the next one and so on.

I implemented this flow in this way: The store dispatch the action that perform the login request passing username and password. Then when the response is ready I store the credentials in the redux store. When I need to perform an authorized request I set those parameters in the header request. When I receive the response I update the credentials in the store with the new ones that I get from the response.

Here my code for more clearity:

import { combineReducers } from 'redux'
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const initialState = {
  currentUser: {
    credentials: {},
    user: {}
  },
  test: {},
  users: []
}

export const SUBMIT_LOGIN = 'SUBMIT_LOGIN'
export const SET_USER = 'SET_USER'
export const TEST = 'TEST'
export const SET_USERS = 'SET_USERS'
export const SET_CREDENTIALS = 'SET_CREDENTIALS'

//actions
const submitLogin = () => (dispatch) => {
  return postLoginRequest()
    .then(response => {
      dispatch(setCredentials(
        response.headers.get('access-token'),
        response.headers.get('client'),
        response.headers.get('expiry'),
        response.headers.get('token-type'),
        response.headers.get('uid')
      ));
      return response
    })
    .then(response => {
      return response.json();
    })
    .then(
      (user) => dispatch(setUser(user.data)),
    );
}

const performRequest = (api) => (dispatch) => {
  return api()
    .then(response => {
      dispatch(setCredentials(
        response.headers.get('access-token'),
        response.headers.get('client'),
        response.headers.get('expiry'),
        response.headers.get('token-type'),
        response.headers.get('uid')
      ));
      return response
    })
    .then(response => {return response.json()})
    .then(json => {console.log(json)})
}

const setUsers = (users) => {
  return {
    type: SET_USERS,
    users
  }
}

const setUser = (user) => {
  return {
    type: SET_USER,
    user
  }
}

const setCredentials = (
  access_token,
  client,
  expiry,
  token_type,
  uid
) => {
  return {
    type: SET_CREDENTIALS,
    credentials: {
      'access-token': access_token,
      client,
      expiry,
      'token-type': token_type,
      uid
    }
  }
}

const currentUserInitialState = {
  credentials: {},
  user: {}
}

const currentUser = (state = currentUserInitialState, action) => {
  switch (action.type) {
    case SET_USER:
      return Object.assign({}, state, {user: action.user})
    case SET_CREDENTIALS:
      return Object.assign({}, state, {credentials: action.credentials})
    default:
      return state
  }
}

const rootReducer = combineReducers({
  currentUser,
  test
})

const getAuthorizedHeader = (store) => {
  const credentials = store.getState().currentUser.credentials
  const headers = new Headers(credentials)
  return headers
}

//store creation

const createStoreWithMiddleware = applyMiddleware(
  thunk
)(createStore);

const store = createStoreWithMiddleware(rootReducer);

const postLoginRequest = () => {
  return fetch('http://localhost:3000/auth/sign_in', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      email: 'test@test.com',
      password: 'password',
    })
  })
}

const getUsers = () => {
  const autorizedHeader = getAuthorizedHeader(store)
  debugger
  return fetch('http://localhost:3000/users',
    {
      method: 'GET',
      headers : autorizedHeader
    }
  )
}


store.dispatch(submitLogin())

setTimeout(() => {
  console.log(store.dispatch(performRequest(getUsers)))
}, 2000)

I'm wondering if store sensible data in the store in a good practice and if not I'm open to any suggestions to develop this workflow in a better way.

I have also this parameter on my server

config.batch_request_buffer_throttle = 5.seconds

Sometimes it's necessary to make several requests to the API at the same time. In this case, each request in the batch will need to share the same auth token. This setting determines how far apart the requests can be while still using the same auth token.

In this way I could not dispatch the setCredentials action in my performRequest function if the token is still valid but I don't really have ideas how to check it.

Thanks

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
TWONEKSONE
  • 3,918
  • 3
  • 19
  • 26
  • 2
    Redux store is in memory (unless you're storing it somewhere else). If that's the case, credential and/or session are lost as soon as you reload the page. Sounds reasonable to me. – danielepolencic Feb 13 '16 at 10:15
  • 6
    Doesn't sound so reasonable to me. With a mobile or a single-page app, it may take several weeks till the user "reloads the page" and all this time he has his credentials stored somewhere in the memory. Sounds like a possible vulnerability. – VaclavSir Jan 29 '17 at 11:40
  • 1
    @VaclavSir so which is your solution? – TWONEKSONE Jan 30 '17 at 12:32
  • I don't have any. :-( – VaclavSir Jan 31 '17 at 10:27
  • A good solution is to sync you state slice to save your credentials in the localStorage. My solution was to create a middleware to handle localStorage access. When an specific action is dispatched, the localStorage item is got and stored in the state. I think this is more reliable. Similarly you can use AsyncStorage for native apps. – Andrey Luiz Jun 15 '17 at 16:50
  • 4
    I agree with @AndreyLuiz. Put the token / credentials in localStorage and check localStorage for the credentials whenever you need to make a server request. If the credentials are no longer there or invalid, remove everything from the store and perform a LOGOUT action. In your reducer, the LOGOUT action should return an empty state so all information about the previous session is removed from state as well. If you really want to make sure no one is using the application with expired credentials, you can also implement periodic checks of the credentials in localStorage to ensure they are valid – Dude Oct 18 '17 at 17:51
  • Redux? LocalStorage? Use an HTTP cookie. – lux May 25 '18 at 14:09
  • 1
    i would say that storing variables in session state is safer than local storage because any site can access your information from localstorage https://stackoverflow.com/questions/17745292/how-to-retrieve-all-localstorage-items-without-knowing-the-keys-in-advance – Joey Gough Sep 26 '18 at 13:06
  • 2
    @JoeyGough any site can access your local storage, but local storage is domain based. So if the website www.abc.com saved anything in your local storage, when you access www.def.com it won't have access to any information of www.abc.com has saved. – aquilesb Jan 09 '19 at 00:44
  • oh thanks a million. This is something I have been wondering about... do you think it is safe to store authentication JWTs in local storage - for the purpose of re-visits, as in this tutorial http://jasonwatmore.com/post/2017/12/07/react-redux-jwt-authentication-tutorial-example – Joey Gough Jan 09 '19 at 09:40
  • 1
    DO NOT store sensitive information in Local storage. Yes, it may be protected from cross domain, but it is NOT safe from [XSS](https://owasp.org/www-community/attacks/xss/). [See here](https://medium.com/redteam/stealing-jwts-in-localstorage-via-xss-6048d91378a0) – GBourke Dec 19 '20 at 11:08

2 Answers2

1

No. It is not a good practise. If you wish to store user sessions better using cookie sessions to store JWT incase of React.js.

In case of react-native using react-native-keychain to store your passwords that too I would recommend as JWT token or Crypted text and not as plain text.

Your implementation mostly depends on your back end. If your using libraries like passport.js ( in case of node.js server) you use the crypt token to authorise requests. But whatever means you have to do auth don't store it in state plainly.

Naveen Vignesh
  • 1,351
  • 10
  • 21
0

Using Modern Storage API will help you.

Cause when you Fetched your auth data in Redux Store, if page is refreshed or history moves(back || go), then Store Values are no longer exist. and your app will requests Auth data after refresh .. refresh ..

...

Using window.sessionStorage can remains data after refresh or history move.


  [AFTER LOGIN EVENT]

    window.sessionStorage.setItem(key,vlaue)

  [AFTER LOGOUT EVENT]

    window.sessionStorage.clear() <- removes all records in sessionStorage.
    window.sessionStorage.removeItem(key) 

함성준
  • 1
  • 1