axios-module icon indicating copy to clipboard operation
axios-module copied to clipboard

axios module: axios.setToken where to run?

Open uptownhr opened this issue 7 years ago • 38 comments

I am currently running this in nuxtServerInit if a token is available. Progressing a little further, I noticed that the token is no longer available from the client. Meaning I also need to run setToken again from the client side. From which nuxt lifecycle stage should I set the token with $axios.setToken?

This question is available on Nuxt.js community (#c53)

uptownhr avatar Jul 25 '17 01:07 uptownhr

one solution for me was to create an axios plugin that exports $axios

let axios = null

export default ({store, $axios}) => {
  console.log('axios plugin init')
  $axios.setToken(store.state.token)
  axios = $axios
}

export {
  axios
}

now i'm able to import {axios} from '~plugins/axios' again.

Looks a bit weird for me so would love some feedback if this is a bad approach.

uptownhr avatar Jul 25 '17 04:07 uptownhr

You are on a right way. The only change required for that plugin is that you can directly access app.$axios and call setToken on it. No need to import/export.

plugins/auth.js

export default ({store, app: { $axios }}) => {
  $axios.setToken(store.state.token)
}

pi0 avatar Jul 26 '17 16:07 pi0

I tried to provide a solution that did not know if it was feasible.

seekcx avatar Jul 26 '17 16:07 seekcx

@seekcx I've seen that PR. But directly integration of store into axios makes it optinized and maybe not everyone wants using vuex in their project. We need an enhancement in Nuxt core indeed that plugins need a way adding things to __NUXT__ variable and access it on client init. Meanwhile whats wrong with simply using per-project plugins like above to call setToken with store state?

pi0 avatar Jul 26 '17 17:07 pi0

@pi0 Sorry, just did not pay attention to see you on a comment, really perfect solution to this problem.

seekcx avatar Jul 26 '17 17:07 seekcx

@pi0 the export import was added in for a different reason. It was so I can import from my store so I don't have to pass in axios to all my actions.

I remember you mentioned there is an issue with this approach. Is there an issue or is this an OK approach?

uptownhr avatar Jul 26 '17 19:07 uptownhr

Also what is the difference between accessing $axios from the context vs grabbing from ctx.app

uptownhr avatar Jul 26 '17 19:07 uptownhr

@uptownhr It works but unsafe for SSR. (Because global variables will be shared between concurrent requests and this is probably not what we want!). We can access token from context only ( $axios, app, store, etc )

pi0 avatar Jul 26 '17 19:07 pi0

Hello @pi0

I am confused. Do I need to call $axios.setToken both on SSR and CSR?

I ask becouse now I have o code like this, plugin:

export default function ({ store, route, redirect, req, res, isClient, isServer, app: { $axios } }) {
  if (isServer) {
    const cookies = new Cookies(req, res)
    authToken = cookies.get(AUTH_TOKEN_KEY)
    if (authToken) {
      $axios.setToken(authToken)
    }
    [...]
  }

And the user is logged correctly. But later for CSR axios don't send Authorization header. Why is it?

When I add this to the plugin everything works fine:

  if (isClient && authToken) {
    $axios.setToken(authToken, 'Bearer')
  }

awronski avatar Jul 28 '17 11:07 awronski

Yes you do have to set on both. I'd break down your get cookie logic with isserver and isclient

On Fri, Jul 28, 2017, 4:40 AM awronski [email protected] wrote:

Hello @pi0 https://github.com/pi0

I am confused. Do I need to call $axios.setToken both on SSR and CSR?

I ask becouse now I have o code like this, plugin:

export default function ({ store, route, redirect, req, res, isClient, isServer, app: { $axios } }) { if (isServer) { const cookies = new Cookies(req, res) authToken = cookies.get(AUTH_TOKEN_KEY) if (authToken) { $axios.setToken(authToken) } [...] }

And the user is logged correctly. But later for CSR axios don't send Authorization header. Why is it?

When I add this to the plugin everything works fine:

if (isClient && authToken) { $axios.setToken(authToken, 'Bearer') }

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nuxt-community/modules/issues/89#issuecomment-318630677, or mute the thread https://github.com/notifications/unsubscribe-auth/AA-Utrxzodzhv7pZ_pZox2_s43fPLHJqks5sScijgaJpZM4Oh9dS .

uptownhr avatar Jul 28 '17 16:07 uptownhr

@awronski As of rc2 we have a magical feature which allows doing that once. Just give us little more time as there are lots of works while preparing final release :)

pi0 avatar Jul 28 '17 16:07 pi0

@pi0 by any chance is there a sample code to also inject axios into the vuex action dispatcher?

On Fri, Jul 28, 2017, 9:55 AM Pooya Parsa [email protected] wrote:

@awronski https://github.com/awronski As of rc2 we have a magical feature which allows doing that once. Just give us little more time as there are lots of works while preparing final release :)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nuxt-community/modules/issues/89#issuecomment-318706713, or mute the thread https://github.com/notifications/unsubscribe-auth/AA-UtnrKbrwsUSvFXRmBvZSbKs9NBmbLks5sShKQgaJpZM4Oh9dS .

uptownhr avatar Jul 28 '17 17:07 uptownhr

@pi one more question. It the global variables shared between concurrent requests fixed in the rc.3?

I have strange behavior.

Plugin code:

export default function ({ store, route, app: { $axios } }) {
  console.log( $axios.defaults.headers.common.Authorization )
   [...]
}
  1. I log user lets say with the Chrome browser.
  2. Than I access the page with the IE browser (without login)
  3. In the console log of the server I see Bearer of the first user.

In the CSR the tokens are set correctly.

awronski avatar Jul 28 '17 17:07 awronski

I don't think you want to use defaults. That affects it globally. The axios module has a set token method avail for you to use.

On Fri, Jul 28, 2017, 10:54 AM awronski [email protected] wrote:

@pi https://github.com/pi one more question. It the global variables shared between concurrent requests fixed in the rc.3?

I have strange behavior.

Plugin code:

export default function ({ store, route, app: { $axios } }) { console.log( $axios.defaults.headers.common.Authorization ) [...] }

  1. I log user lets say with the Chrome browser.
  2. Than I access the page with the IE browser (without login)
  3. In the console log of the server I see Bearer of the first user.

In the CSR the tokens are set correctly.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nuxt-community/modules/issues/89#issuecomment-318721089, or mute the thread https://github.com/notifications/unsubscribe-auth/AA-UtqS3VMf4z92rDbqQ6QiOP2gwtV9Kks5sSiBogaJpZM4Oh9dS .

uptownhr avatar Jul 28 '17 22:07 uptownhr

@uptownhr The problem is the axios module use defaults internally. Just check: https://github.com/nuxt-community/modules/blob/master/modules/axios/plugin.js#L33

In the present implementation the module cannot be use on server. Therefore I cannot hydrate the store for server side rendering.

I am thinking about diffrent implementation. Where the token is not put in the axios defaults but retrived from the store from axios interceptors. This should be thread safe.

awronski avatar Jul 29 '17 07:07 awronski

Interesting.

I do have a working version on my project right now. My approach was to set the token into the store during nuxtserverinit and then set the token again in the plugin for the client.

plugin

export default ({store, $axios}) => {
  $axios.setToken(store.state.token)
}

nuxtServerInit

nuxtServerInit ({commit}, {req, route, app, store}) {
    let cookies = new Cookies(req)
    let token = cookies.get('access-token')

    if (!token) return

    commit('setToken', token)
    app.$axios.setToken(token, 'Bearer')
  }

uptownhr avatar Jul 29 '17 08:07 uptownhr

@uptownhr Hi James!

thanks for sharing your code.

Your example is working but I think it is not correct.

You set token in the nuxtServerInit, so the token is set on the shared axios server instance. The same for every client.

In my opinion this is potentially security problem.

awronski avatar Jul 29 '17 08:07 awronski

I did a quick fix inside the nuxtjs/axios/plugin.js:

function tokenHandler(config, store) {
  if (store.getters.authToken) {
    config.headers.common['Authorization'] = store.getters.authToken
  }
  return config
}

  //Token handling
  axios.interceptors.request.use(config => tokenHandler(config, store), errorHandler.bind(ctx))

Now I do not need to use setToken at all and it workds both server and client side.

I will try to fork a repo and make a PR.

awronski avatar Jul 29 '17 09:07 awronski

Awesome solution. Where is the store passed in from and which store instance is this?

But man handling axios is pretty scary. I would have thought axios is segmented as long as you use axios provided in the context.

uptownhr avatar Jul 29 '17 16:07 uptownhr

Axios module has a new home and finally, SSR is safe to use setToken and setHeader. The main cause was totally crazy! commit and this commit.

Upgrade to >= 3.1.3 is recommended for everyone!

yarn add @nuxtjs/axios@^3.1.3
# or
npm i @nuxtjs/axios@^3.1.3

pi0 avatar Aug 13 '17 14:08 pi0

Thank you!

On Sun, Aug 13, 2017, 7:41 AM Pooya Parsa [email protected] wrote:

Axios module has a new home https://github.com/nuxt-community/axios-module and finally, SSR is safe to use setToken and setHeader. The main cause was totally crazy! commit https://github.com/nuxt-community/axios-module/commit/935522853674bd986380eb754b532da2dc94dcc7 . Upgrade to >= 3.1.3 is recommended for everyone!

yarn add @nuxtjs/axios@^3.1.3# or npm i @nuxtjs/axios@^3.1.3

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nuxt-community/modules/issues/89#issuecomment-322046142, or mute the thread https://github.com/notifications/unsubscribe-auth/AA-UtjqmaqFmHR6TjUk65PYwRWFgyH1sks5sXwsZgaJpZM4Oh9dS .

uptownhr avatar Aug 13 '17 17:08 uptownhr

@awronski As of rc2 we have a magical feature which allows doing that once. Just give us little more time as there are lots of works while preparing final release :)

@pi0 i'm on rc8 and tried to use this magical feature of using setToken just once. Can you walk me through this? Currently i'm calling setToken from the nuxtServerInit app.$axios.setToken. Works went request is made from the server but client looses the token.

uptownhr avatar Sep 01 '17 05:09 uptownhr

@pi0 the same problem as @uptownhr has.

After $axios.setToken on the server side, the $axios instance loses token on the client side.

Could you explain, how to use the "magic" feature of the setting once?

TheDeveloperTom avatar May 21 '18 09:05 TheDeveloperTom

Same thing here, I do a this.$axios.setToken(token, 'Bearer') in nuxtServerInit() then the authorization header disappears on client side :(

Tinostarn avatar May 24 '18 13:05 Tinostarn

[2] After $axios.setToken on the server side, the $axios instance loses token on the client side.

Same thing here, what is solution?

@TheDeveloperTom @supertino7 @uptownhr @pi0

adrianoresende avatar Aug 17 '18 18:08 adrianoresende

same thing here, is there any beforeRequest method to set token before sending a request?

vadim-givola avatar Sep 03 '18 22:09 vadim-givola

Hope this helps someone:

i have this:

/plugins/axios.js

import axios from 'axios';

function tokenHandler(config, store) {
    if (store.state.account.user && store.state.account.user.jwt) {
          config.headers.common['Authorization'] =  `Bearer ${store.state.account.user.jwt}`;
   }
    return config
}

export default (context) => {
    //store is the current store, both server and client side. and unique for nuxtServerInit
    let {store, app} = context;
    axios.interceptors.request.use(config => tokenHandler(config, store),
        function (error) {
            return Promise.reject(error);
        });
}

Of course add the 'plugin' to your nuxt.config.js

  plugins: [
        '@/plugins/vuetify',
        '@/plugins/axios'   <-- 
    ],

and inside the nuxtServerInit i set the needed store values (from cookies for example)

This is just a more complete example of some of the suggestions elsewhere..

opgbaudouin avatar Nov 01 '18 13:11 opgbaudouin

@opgbaudouin thanks a lot for this. Searched for two hours how to do this.

codeofsumit avatar Jan 26 '19 16:01 codeofsumit

IMPORTANT.

UPDATE: This solution will APPEAR to work, but only makes the problem less visible. I.e. I simply do not know what Nuxt 'Does' on a new request - i suspect it will not 'reimport' anything - so by setting a module variable i just have another global that will not be correct.

I can see NO way expect passing the / using the $axios instance and passing it to the point where it is needed... (i.e. pages, vuex stores) but no longer 'clean' service files.

Sorry...

Note there is nothing wrong with nuxtjs/axios just the hack i use


I updated to Nuxt 2.4.0 and i saw some pretty strange behaviour from the code i posted before:.

import axios from "Axios"
export default (context) => {
    //store is the current store, both server and client side. and unique for nuxtServerInit
    let {store, app} = context;
    axios.interceptors.request.use(config => tokenHandler(config, store),
        function (error) {
            return Promise.reject(error);
        });
}

This causes the request handler to be ADDED for each time a full SSR page is rendered. This means you might 'phantom' tokens.

I do not know if this was the case in pre 2.4.0 - but i suspect it was also doing this.

I now use @nuxtjs/axios . I looked at the code and noticed the issues (about re-using the axios 'global').

However i didn't want to change my whole code (i.e. my JWT is stored in the store, so 'reactive').

so my code is now: nuxt.config.js:

    modules: [
        '@nuxtjs/axios',
    ],
 plugins: [
       '@/plugins/axios',
    ],

And plugins/axios.js (so both server / client in the new plugin way for 2.4)

function tokenHandler(config, store) {
    if (store.state.account.user && store.state.account.user.jwt) {
       //we could just use $axios.setToken
      
        config.headers.common['Authorization'] = `Bearer ${store.state.account.user.jwt}`;
    }
    return config
}

//https://github.com/nuxt-community/axios-module/issues/28
import { setAxiosInstance } from '~/services/http'
export default ({ app, store }) => {
    let axiosInstance = app.$axios;
    //install the INSTANCE based handler. So each request this will be called
    axiosInstance.onRequest(config => tokenHandler(config, store));
  
    setAxiosInstance(axiosInstance);

}

then my services/http.js - this used to be a simple 'import axios from axios'. is now:

//NOTE: NO import axios. 
let axiosInstance = null;

export function setAxiosInstance(instance) {
    axiosInstance = instance;
    axiosInstance.defaults.baseURL = API_ROOT;
}

//rest of code now uses axiosInstance where axios used to be.

Nothing else needed to change, and my code isn't clobbered by $axios. statements still .

opgbaudouin avatar Jan 30 '19 23:01 opgbaudouin

I'm pretty lost. :'( What is the recommended way of setting the token now? When I set it on the server side only, its lost in the client side.

m4tty-d avatar Feb 26 '19 21:02 m4tty-d