how to pass csrf token from vuex axios to Django API using a global interceptor in vuex. Currently, I am passing csrf with each post, put and delete request like
putResponseResults: function (context, postData) {
axios.post('/api/responses/', postData, { headers: { 'X-CSRFToken': Cookie.get('csrftoken')} })
.then(function (response) {
// console.log(response.data)
})
Is there any global way to pass csrf token using vuex, so I don't need to write it every time.
You can set a CSRF token as a default header (so that it will be attached to every subsequent request) as follows:
axios.defaults.headers.common['X-CSRFToken'] = 'your token';
More information on axios config defaults can be found in axios documentation.
Related
I'm pretty new in React-Native programming, but here is the context.
We are using React Query and Axios libraries in our project. As AuthManager we are using Keycloak and for the library managing auth status we have React Native Keycloak. We encounter a tedious problem with our server responding randomly 401 at our requests after a certain amount of time, bringing also to the app crash sometimes.
We reproduced the error making the Bearer Token of Keycloak expire after only 1 minute. This caused almost immediatly the 401 error and we wondered why this is happening.
Let's say we have a screen with some "Activities" and this screen is the first thing the user will see. For handling requests, in our code we use some custom hooks that reference useQuery, for example:
export function useActivities(): UseQueryResult<ActivityList> {
const { headers } = useHeaders();
return useQuery(
['activities', today.start],
() => getActivitiesList(headers), // Note 1
{
enabled: !!today.start,
}
);
}
The important point of it is that we useHeaders to get our updated headers with the Keycloak token and our realm settings. useHeaders is almost everywhere in our app.
export function useHeaders(): UseHeaders {
const { keycloak } = useKeycloak();
const KEYCLOAK_REALM = remoteConfig().getString('KEYCLOAK_REALM');
const headers = {
Authorization: `Bearer ${keycloak?.token}`,
Realm: KEYCLOAK_REALM,
};
return { headers };
}
Now, the getActivitiesList is simple as five:
async function getActivitiesList(headers: UseHeaders['headers']): Promise<ActivityList> {
const url = `${BASE_URL}${VERSION}/activities/grouped?end=${end}&start=${start}`;
// Note 2
return axios
.get(url, { headers })
.then((res) => res.data)
.catch((e) => console.error('Error fetching grouped activities:', e));
}
The problem with all of that is that whenever Keycloak will trigger the refresh token, the token inside keycloak object is changed, the headers inside useActivities are changed BUT if I print the headers inside my getActivitiesList (// Note 2), or even inside the query function (// Note 1), headers will not be updated. Sometimes it just causes to make two requests (one that fails and show error, the other one actually working), some other times the app crashes without any explain. This makes me wonder why the query function will not update its headers and passed the "old" headers inside getActivitiesList.
For now we are mitigating this problem in two different points.
After keycloak init, we pass immediatly to axios a global header with axios.defaults.headers.common.Realm = KEYCLOAK_REALM;
After receiving a valid token from Keycloak, we overwrite the Authorization header with a new one: axios.defaults.headers.common.Authorization = 'Bearer ${keycloak?.token}';
This is not a perfect solution and we are here to search some info about this problem.
Someone get something similar? How to manage the token refresh in useQuery function?
Im looking to use $auth inside my Nuxt project, specially inside an axios plugin.
Here is my code:
plugins/api.js
export default function ({ $axios }, inject) {
const api = $axios.create({
headers: {
common: {
Accept: 'text/plain, */*',
},
},
})
// Set baseURL to something different
api.setBaseURL('http://localhost:4100/')
// Inject to context as $api
inject('api', api)
}
Now the problem comes when I try to use $auth from #nuxtjs/auth-next package.
As stated in the docs:
This module globally injects $auth instance, meaning that you can
access it anywhere using this.$auth. For plugins, asyncData, fetch,
nuxtServerInit and Middleware, you can access it from context.$auth.
I tried the following:
This results in $auth being undefined
export default function ({ $axios, $auth }, inject) {
This one was near
export default function ({ $axios, app }, inject) {
console.log(app) //This one logs $auth in the object logged
console.log(app.$auth) // I don't understand why but this one returns undefined
My main goal here is to make use of this.$auth.strategy.token.get()and pass it (if the token exists of course) to the headers of every request made using this.$api
I have been looking for similar questions and answers but none has helped me to solve this, I could just add the token every time I write this.$api but that would increase the code unnecessarily.
Thanks in advance to all the people for your time and help.
EDIT:
Okay, now I made a test. and the next code is actually logging the $auth object correctly, it seems some time is needed to make it work but now Im afraid that using setTimeout could cause an error because I can't know exactly how much time is needed for $auth to be available.
export default function ({ $axios, app }, inject) {
setTimeout(() => {
console.log('After timeout', app.$auth)
}, 50)
EDIT 2:
So now I have made more tests, and using 0 milliseconds instead of 50 works too, so I will use setTimeout with 0 milliseconds for now, I hope anyone find a better solution or explain why $auth is not available before using setTimeout so I can decide what to do with my code.
EDIT 3:
After trying to wrap all my previous code inside setTimeout I noticed that the code fails, so that isn't a solution.
I have found a solution so I will post it so that every person that could have the same problem in the future can solve it.
It turns out that I could easily solve it using interceptors.
export default function ({ $axios, app }, inject) {
// At this point app.$auth is undefined. (Unless you use setTimeout but that is not a solution)
//Create axios instance
const api = $axios.create({
headers: {
common: {
Accept: 'application/json', //accept json
},
},
})
// Here is the magic, onRequest is an interceptor, so every request made will go trough this, and then we try to access app.$auth inside it, it is defined
api.onRequest((config) => {
// Here we check if user is logged in
if (app.$auth.loggedIn) {
// If the user is logged in we can now get the token, we get something like `Bearer yourTokenJ9F0JFODJ` but we only need the string without the word **Bearer**, So we split the string using the space as a separator and we access the second position of the array **[1]**
const token = app.$auth.strategy.token.get().split(' ')[1]
api.setToken(token, 'Bearer') // Here we specify the token and now it works!!
}
})
// Set baseURL to something different
api.setBaseURL('http://localhost:4100/')
// Inject to context as $api
inject('api', api)
}
Also Nuxt Auth itself has provided a solution for this issue:
https://auth.nuxtjs.org/recipes/extend/
GOAL
I want to populate my Authorization header with an access token. I want to store that access token in the Apollo cache as Auth0 explicitly state not to store access tokens in local storage (I don't know why the Apollo Client docs seem to think it's okay).
Failing that, I want to store my access token securely and be able to add it to each Apollo Client request to the Apollo Server.
const apolloClient = withApollo(({ctx, headers, initialState}) => {
const cache = new InMemoryCache();
// Code here to access local cache.
return new ApolloClient({
cache,
link: new HttpLink({
uri: <apollo server endpoint>,
headers: {
Authorization: `Bearer ${accessToken}`,
'Access-Control-Allow-Origin': 'http://localhost:3000/',
...headers
},
credentials: 'include'
}),
typeDefs,
resolvers
})
})
class MyApp extends App {
render() {
const { Component, pageProps, apollo } = this.props;
return (
<ApolloProvider client={apollo}>
<Component {...pageProps} />
</ApolloProvider>
);
}
}
export default apolloClient(MyApp);
TRIED
I've tried to access localStorage from within the withApollo function but it is SSR'd so I'm unable to access that. I was going to use the localStorage to set a boolean to check in the withApollo function so it'll know the access token has been added to the apollo cache after the redirect.
I've tried just using the cache with a key storing a boolean to check if the user has signed in which is set in the same function where the access token. If that is true I'd access the access token key and add that to the Authorization header. But I was getting issues with setting the initial state overriding the cache set in sign in function.
OTHER IDEAS
I thought I could pass the access token through in the ctx argument, but I'm not familiar enough with Next.js to figure out if that's valid way to do it.
I thought I could pass in a props to the withApollo function from a component, but that doesn't seem like it'd be possible.
QUESTIONS
What is the best way to store the access key so as to add it to the Apollo Client with each request?
I noticed some people are using a fetch polyfill, would that be appropriate for this situation? If so, how would that work?
How does the withApollo HOC work and why is it needed to work with Next.js? I've read some of the code behind this, but I don't fundamentally understand why it is needed.
I was able to find a solution to my question. I just didn't fully understand the Apollo Client and how to use all of the required packages.
SOLUTION
I used setContext from the apollo-link-context library. It's part of the link set of libraries that Apollo Client offers to customise the network request after a graphql operation has started. I set the header in the setContext function. Like so:
const authLink = setContext((_, { headers }) => {
// It is also possible to use the local storage method in here.
const data = cache.readQuery({
query: ACCESS_TOKEN
});
return {
headers: {
...headers,
authorization: accessToken ? `Bearer ${data.accessToken}` : ""
}
}
});
The following is outside the withApollo function.
const httpLink = new HttpLink({
uri: '<server uri>',
});
The following is inside the withApollo function.
return new ApolloClient({
cache,
link: authLink.concat(httpLink),
typeDefs,
resolvers
})
The docs for how setContext are here and the docs for the `apollo-link-context' are here.
I think I have a better solution so I'm gonna post it over here. I guess you want to configure your client properly instead of passing the token to each query. I remembered how I used react-query's client. You can do the same here.
My client looks like this:
import { ApolloClient, InMemoryCache } from '#apollo/client'
export const client = new ApolloClient({
uri: 'https://api.github.com/graphql',
headers: {
Authorization: `bearer ${localStorage.getItem('personal-access-token')}`
},
cache: new InMemoryCache()
})
If I don't have a token in local storage it'll be set to 'bearer null'. But after I get a new token you should get the Apollo Client's instance and edit Authorization header like this:
import { useApolloClient } from '#apollo/client'
const setToken = token => {
client.link.options.headers.Authorization = `bearer ${token}`
localStorage.setItem('personal-access-token', token)
}
I hope this helps someone.
P.S. Make sure to wrap the component which alters the Apollo Client's instance with ApolloProvider.
I have an frontend Web app interfaces with API built in Laravel with Passport.
My problem is when I refresh my page (in SPA written with Vuejs/Vuex) I should refresh my token, for refresh session with my Api.
I tried in main.js but he problem is that the request is async and the response arrived after routing.
main.js
if (localStorage.getItem('refresh_token')) {
store.dispatch('refresh_token').then(function(response){
console.log(response);
});
}
new Vue({
router,
store,
env,
render: h => h(App)
}).$mount('#app')
The function refresh token, make a call to my Api, and with response set the new token and the new refresh token.
But my problem is that I make this call in this way I can make the first async call in my "dashboard" with old token and then with the new.
So I've tried in different ways but I don't know if there is a best practice.
So my question is:
Where I should refresh token in Vuejs App with vuex store?
I suggest putting this in the mounted property of you toplevel Vue component. If you have other components that depend on your token being refreshed, you can couple this with a state variable in your store that signals the refresh is completed. For example, with a top level component App.vue:
...
mounted () {
store.dispatch('refresh_token')
}
...
Adding the state variable to your vueex store:
const store = new Vuex.Store({
state: {
sessionRefreshed: false
},
..
mutations: {
[REFRESH_TOKEN] (state) {
// existing mutations, and..
state.sessionRefreshed = true
},
},
..
actions: {
refreshToken ({ commit }) {
myAsyncFetchToken().then(() => commit(REFRESH_TOKEN))
},
}
This ensures your entire application is aware of the state of your refresh without forcing it to be synchronous. Then if you have components which require the token to be refreshed, you can show loading widgets, placeholders, etc., and use a watcher to do things when the state changes.
How about using router#beforeEach guard? I use it to figure out if authentication token is stored in a cookie before accessing any "restricted" component. If token is not set I redirect to /login.
I realize that my scenario is exactly what you are are asking for but I hope you can use it to augment your implementation.
In the main.js file i have set the vue resource to use auth headers with every requests:
Vue.use( VueResource )
let auth = validToken()
if( auth ) {
Vue.http.interceptors.push( ( request, next ) => {
request.headers.set( 'Authorization', auth.token )
request.headers.set( 'Accept', 'application/json' )
next()
} )
}
And in the logout, i am trying to delete the header this way,
resetVueRsr: () => {
this.$http.headers.common['Authorization'] = null
}
And when a user log in, then resetting the new token to the global Vue resource like this,
this.$http.headers.common['Authorization'] = res.body.token
But once a new user is logging in the site, the vue resource still sending previous token with each request. What i am missing here?
I checked the source, and it looks like all defined interceptors are added in some internal array, and then used for requests.
According to your code sample, you obtain an object with initial token somehow and then pass it inside your arrow function, so JS creates a closure for this to make this object available inside the function, and I think a lifetime of this closure is equal to a lifetime of your app. I assume that you have a sort of SPA.
Also from the source it looks like interceptors are not related to common headers, so your way of deleting and resetting won't change your interceptor.