infinitered / ignite-andross

The original React Native boilerplate from Infinite Red - Redux, React Navigation, & more
https://infinite.red/ignite
MIT License
475 stars 151 forks source link

What is the proper way to set auth headers in api ? #206

Open chawax opened 6 years ago

chawax commented 6 years ago

I need to set authentication tokens in headers on api calls but the tokens are not known at the time the api is created : they are stored in the Redux store after the user was logged. So I need to retrieve them from the store on any api calls. I could retrieve them in sagas and pass them as parameter for api calls.

In saga :

export function* getAccounts(api, action) {
  const jwtToken = yield select(AuthSelectors.getJwtToken)
  const response = yield call(api.loadAccounts, {
    jwtToken,
  })
  if (response.ok) {
    yield put(AccountsActions.accountsSuccess(response.data))
  } else {
    yield put(AccountsActions.accountsFailure())
  }
}

In api :

const loadAccounts = (data) => {
  const options = {
    headers: {
      Authentication: `Bearer ${data.jwtToken}`,
  },
  return api.get('/accounts', null, options)
}

But it means I have to do it in every saga.

I thought about exporting the store from Containers/App.js file, importing it in api and using addRequestTransform to set headers, but I know it was bad practice to export store.

What is the proper way to resolve such a problem ?

chawax commented 6 years ago

I use apisauce.

I found a solution storing tokens in api and using addResponseTransform and addRequestTransform. It means the auth tokens are not in Redux store, but it could make sense if you can consider the auth tokens are part of api implementation.

const tokens = {}

const create = (baseURL = Config.API_ENDPOINT) => {

  const api = apisauce.create({
    baseURL,
    headers: {
      'Cache-Control': 'no-cache',
      Accept: 'application/json',
    },
    timeout: 10000
  })

  api.addResponseTransform((response) => {
    if (response.ok) {
      if (response.headers.authentication) {
        tokens.jwtToken = response.headers.authentication
      }
    }
  })

  api.addRequestTransform((request) => {
    if (tokens.jwtToken) {
      request.headers.Authentication = `Bearer ${tokens.jwtToken}`
    }
  })

  ....
}
ruddell commented 6 years ago

I added the following methods into in my Api.js file for configuring the auth header, which I call from the sagas:

  const setAuthToken = (userAuth) => api.setHeader('Authorization', 'Bearer ' + userAuth)
  const removeAuthToken = () => api. deleteHeader('Authorization')

https://github.com/ruddell/ignite-jhipster/blob/master/boilerplate/App/Services/Api.js.ejs#L51-L52

Amurmurmur commented 6 years ago

@chawax I personally do a setHeader inside the Api.js in every api endpoint call. like so:

const register = (token, data) => {
    api.setHeader('Authorization', 'Bearer ' + token)
    return api.post('api/register', { ...data })
  }

That way you just select the token from the Store/Offline cache with a selector, inside your saga and pass it to the api function along with the data you want to submit to your backend.

chawax commented 6 years ago

So it means you have to do it in any saga that calls the api. And that's what I wanted to avoid.

Amurmurmur commented 6 years ago

@chawax You normally would use Sagas for the api calls.

If you want to directly call the api from the component you can do something like:

import React, {Component} from 'react'
import { View } from 'react-native'
import API from '../Services/Api'

class TestComponent extends Component{
componentDidMount(){
  const yourBearerToken = '1234567890'
  const api = API.create('https://jsonplaceholder.typicode.com/')
  api.setHeader('Authorization', 'Bearer ' + yourBearerToken)
  api.get('posts/1'}).then(result => console.log(result))
}

render(){
  return(
   <View />
  )
}
}

export default TestComponent

UPDATE: Sorry didnt get your question at first, than realised my answer was not what you were looking for. But as for sagas, you can use selectors in every saga that needs your token, and do it that way.

You can use an awesome library https://github.com/reduxjs/reselect huge benefit of it is what exactly they state on their main page:

Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
xgenvn commented 6 years ago

In my recent implementations, I didn't try to use the token in the store but from AsyncStorage, or Secure Store instead. I wrapped API calls as a client SDK, and then I can have only one API instance in root store/global.

neel96 commented 6 years ago

If we try to token passed in header ....Try this method

const api = 'your api'; 
const token = JSON.parse(sessionStorage.getItem('data'));
const token = user.data.id;       /*take only token and save in token variable*/
axios.get(api , { headers: {"Authorization" : `Bearer ${token}`} })
.then(res => {
console.log(res.data);
.catch((error) => {
  console.log(error)
});
jamonholmgren commented 6 years ago

Does someone want to add some documentation to this effect?

cristofer commented 5 years ago

This works for me: https://github.com/axios/axios#global-axios-defaults