In my vue/cli 4/vuex / vue-resource project I read data from protected area with passport from backend api as in routes/api.php:
Route::group(['middleware' => 'auth:api', 'prefix' => 'adminarea', 'as' => 'adminarea.'], function ($router) {
...
Route::get('dashboard', 'API\Admin\DashboardController#index');
I found how to set access token at
vue-resource interceptor for auth headers
and in src/main.js I added method :
Vue.http.interceptors.push((request, next) => {
/*
console.log('request::')
console.log(request)
console.log('this::')
console.log(this)
console.log('Vue::')
console.log(Vue)
console.log('Vue.$store::')
console.log(Vue.$store)
console.log('Vue.$store.getters.token::')
console.log(Vue.$store.getters.token)
*/
request.headers.set('Authorization', 'Bearer XXX key')
request.headers.set('Accept', 'application/json')
next()
})
and it works for me if I fill bearer token manually, but I did not find how to read it from store?
In my code above I failed to read $store.getters value...
Which is the valid way ?
UPDATED BLOCK # 1:
If in src/main.js I comment line :
import store from './store'
and add line :
import store from './store.js';
then in console I got error :
ERROR Failed to compile with 1 errors 12:55:54 PM
This relative module was not found:
* ./store.js in ./src/main.js
But with
import store from './store'
I failed get access to store data
Thanks!
Try importing your Vuex file instead of trying to access the $store via the Vue instance.
import store from './store.js';
Vue.http.interceptors.push((request, next) => {
request.headers.set('Authorization', `Bearer ${store.getters.token}`)
request.headers.set('Accept', 'application/json')
next()
})
I found decision in writing token into localStorage on login and
reading this value from ue.http.interceptors.push((request, next) => {
let token = localStorage.getItem('token')
request.headers.set('Authorization', 'Bearer '+token)
request.headers.set('Accept', 'application/json')
next()
})
That works for me!
Related
i've a vue app which requires a token when sending a request each time i try to send a request i keep getting token not defined... here's the error
this how i call my methods property in script tag
<script>
import { mapActions } from "vuex";
import axios from "axios";
export default {
name: "Products",
data() {
return {
addresses: [],
products: []
};
},
methods: {
onDeleteAddress(id, index) {
axios
.delete(`http://localhost:5000/api/addresses/${this.$route.params.id}`,
{
headers: {
Authorization: "Bearer" + token,
"x-access-token": token
}
}
)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
}
}
};
</script>
this my vue template
Delete
this works in my mounted life cycle hook but when i input it in my methods component i get an error
const token = localStorage.getItem("token");
please how can i get the token stored in my local storage and define it in my vue methods conponent
In first make sure the item is already stored in the localstorage
Second instead of calling it from the localstorage it is better to define it in main.js file as global variable so you can use it free every where
Example
Vue.prototype.$globalData = Vue.observable({ token: localStorage.getItem("token") });
And now you can use it in your methods like this
this.$globalData.token
You can as much as you want variable in the globalData object
I have a Vuex action that gets run each time a page loads (not router.push), this function seems to run fine in the sense that it checks for a token and if the token exists it moves on. My issue is that I am dispatching another action which requires this token.
Okay so a bit clearer, I am using Axios with Vue.js to send API request. I have the authorization header set to a Vuex store value in my main.js file. I then have my App.uve load which triggers a default action to run which checks for the existance of a token (JWT). This default action also dispatches another action called storeUser which sends off a GET request to a user info API endpoint. When sending this user info API call I am seeing on my back end that it is not an authorized API call. Checking into the headers I need the authorization header is undefined. Below is what I believe to be the relavent code.
Default action that runs on App.vue load
tryAutoLogin({commit, dispatch}) {
const token = localStorage.getItem('token')
if(!token) {return}
commit('authUser',{
token
})
dispatch('storeUser')
},
second action that is causing the issue
storeUser({commit, state}, userData) {
if(!state.token) return
axios.get('/user/userInfo')
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err)
})
},
main.js needed parts
import store from './store/store.js'
axios.defaults.headers.common['Authorization'] = store.token
new Vue({
render: h => h(App),
store,
router
}).$mount('#app')
I cut out a ton in main.js to make it cleaner but those are the related parts to this issue. I don't think that there is anything else.
store.js state
state: {
token: null,
name: '',
companyName: ''
},
Thanks to Phil in the comments on the OP I have the below code working.
axios.interceptors.request.use(function (config) {
config.headers.Authorization = store.state.token;
return config;
}, function (error) {
return Promise.reject(error);
});
In nuxtjs project, I created an auth middleware to protect page.
and using vuex-persistedstate (also tried vuex-persist and nuxt-vuex-persist) to persist vuex store.
Everything is working fine when navigating from page to page, but when i refresh page or directly land to protected route, it redirect me to login page.
localStorage plugin
import createPersistedState from 'vuex-persistedstate'
export default ({ store }) => {
createPersistedState({
key: 'store-key'
})(store)
}
auth middleware
export default function ({ req, store, redirect, route }) {
const userIsLoggedIn = !!store.state.auth.user
if (!userIsLoggedIn) {
return redirect(`/auth/login?redirect=${route.fullPath}`)
}
return Promise.resolve()
}
I solved this problem by using this plugin vuex-persistedstate instead of the vuex-persist plugin. It seems there's some bug (or probably design architecture) in vuex-persist that's causing it.
With the Current approach, we will always fail.
Actual Problem is Vuex Store can never be sync with server side Vuex store.
The fact is we only need data string to be sync with client and server (token).
We can achieve this synchronization with Cookies. because cookies automatically pass to every request from browser. So we don't need to set to any request. Either you just hit the URL from browser address bar or through navigation.
I recommend using module 'cookie-universal-nuxt' for set and remove of cookies.
For Setting cookie after login
this.$cookies.set('token', 'Bearer '+response.tokens.access_token, { path: '/', maxAge: 60 * 60 * 12 })
For Removing cookie on logout
this.$cookies.remove('token')
Please go through the docs for better understanding.
Also I'm using #nuxt/http module for api request.
Now nuxt has a function called nuxtServerInit() in vuex store index file. You should use it to retrieve the token from request and set to http module headers.
async nuxtServerInit ({dispatch, commit}, {app, $http, req}) {
return new Promise((resolve, reject) => {
let token = app.$cookies.get('token')
if(!!token) {
$http.setToken(token, 'Bearer')
}
return resolve(true)
})
},
Below is my nuxt page level middleware
export default function ({app, req, store, redirect, route, context }) {
if(process.server) {
let token = app.$cookies.get('token')
if(!token) {
return redirect({path: '/auth/login', query: {redirect: route.fullPath, message: 'Token Not Provided'}})
} else if(!isTokenValid(token.slice(7))) { // slice(7) used to trim Bearer(space)
return redirect({path: '/auth/login', query: {redirect: route.fullPath, message: 'Token Expired'}})
}
return Promise.resolve()
}
else {
const userIsLoggedIn = !!store.state.auth.user
if (!userIsLoggedIn) {
return redirect({path: '/auth/login', query: {redirect: route.fullPath}})
// return redirect(`/auth/login?redirect=${route.fullPath}`)
} else if (!isTokenValid(store.state.auth.tokens.access_token)) {
return redirect({path: '/auth/login', query: {redirect: route.fullPath, message: 'Token Expired'}})
// return redirect(`/auth/login?redirect=${route.fullPath}&message=Token Expired`)
} else if (isTokenValid(store.state.auth.tokens.refresh_token)) {
return redirect(`/auth/refresh`)
} else if (store.state.auth.user.role !== 'admin')
return redirect(`/403?message=Not having sufficient permission`)
return Promise.resolve()
}
}
I have write different condition for with different source of token, as in code. On Server Process i'm getting token from cookies and on client getting token store. (Here we can also get from cookies)
After this you may get Some hydration issue because of store data binding in layout. To overcome this issue use <no-ssr></no-ssr> wrapping for such type of template code.
I created a new vue project using the command vue create axe using vue-cli-3.0.016beta. Then installed axios using npm install axios --save. In the main.js file I imported axios as shown below.
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
Vue.config.productionTip = false
Vue.use(axios)
new Vue({
render: h => h(App)
}).$mount('#app')
There is not a bit of code change other than this. Still I get an error like the following:
Unhandled promise rejection
TypeError
columnNumber: 7
fileName: "http://localhost:8080/app.js line 1065 > eval"
lineNumber: 57
message: "parsed is undefined"
stack: "isURLSameOrigin#webpack-internal:///./node_modules/axios/lib/helpers/isURLSameOrigin.js:57:7\ndispatchXhrRequest#webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:109:50\nPromise#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:177:7\nxhrAdapter#webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:12:10\ndispatchRequest#webpack-internal:///./node_modules/axios/lib/core/dispatchRequest.js:59:10\nrun#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:75:22\nnotify/<#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:92:30\nflush#webpack-internal:///./node_modules/core-js/modules/_microtask.js:18:9\n"
__proto__: Object { stack: "", … }
I want to axios globally to use interceptors, hence calling it here in main.js. But if I use it in a view-page there is no error!
is this a bug or I'm doing it wrong? Kindly help me to fix this and use axios globally.
Thanks
so the error I see is here
Vue.use(axios)
Vue.use expects a vue installable plugin.
You could have a look at vue-axios
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
but I would highly discourage it.
It's best to create your own ApiHandler.js file that handles all the remote stuff separately, and you can easily call from anywhere including vue components and vuex.
here is the beginning of my class
<script>
import axios from 'axios';
class ApiHandler{
constructor(apiUrl) {
this.axios = axios;
this.apiUrl = apiUrl || ''; // this line allow passing a custom endpoint for testing
this.config = {
headers: { 'Cache-Control': 'no-cache' }, // can setup to prevent all caching
baseURL: this.apiUrl,
};
}
/**
* #param {Object} payload
* #param {String} payload.username
* #param {String} payload.password
*/
login({ username, password }) {
return new Promise((resolve, reject) => {
this.axios.post('/api/login', { username: username.toLowerCase(), password }, this.config)
.then((response) => {
if (response.code === 200 && response.body && response.body.token) {
resolve(response.body.token);
} else {
reject('Bad Login');
}
})
.catch((err) => {
reject('internal error');
});
});
}
}
</script>
you can then call this from anywhere by...
<script>
import ApiHandler from '../lib/ApiHandler';
const apiRequest = new ApiRequest();
// and then anywhere in the script
let payload = {
username:'someuser',
password:'somepassword',
};
apiRequest.login(payload)
.then(()=>{
// yay - I'm logged in
})
.catch(err => {
// oh oh, display error
})
</script>
this gives you much more flexibility and allows you to separate the remote actions and allows doing first-leg response handling separate of your component, which allows more re-usability.
instead of
Vue.use(axios);
you should
Vue.prototype.$axios = axios;
then you can use it globally
login() {
this.$axios.post('<host>/api/login', data)
.then((res) => { // dosomething })
.catch((err) => { // dosomething });
}
if you want to add globally interceptors with axios, you can
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
// and
Vue.prototype.$axios = axios;
I store token after success login call in vuex store like this:
axios.post('/api/auth/doLogin.php', params, axiosConfig)
.then(res => {
console.log(res.data); // token
this.$store.commit('login', res.data);
})
axiosConfig is file where I only set baseURL export default { baseURL: 'http://localhost/obiezaca/v2' } and params is just data sent to backend.
My vuex file looks is:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
logged: false,
token: ''
},
mutations: {
login: (state, response) => {
state.logged = true;
state.token = response;
console.log('state updated');
console.log('state.logged flag is: '+state.logged);
console.log('state.token: '+state.token);
},
logout: (state) => {
state.logged = false;
state.token = '';
}
}
});
It is working correctly, I can re-render some of content in my SPA basing on v-if="this.$store.state.logged" for logged user. I'm able to access this.$store.state.logged from any component in my entire app.
Now I want to add my token to every request which call my rest API backend. I've created basic axios http interceptor which looks like this:
import axios from 'axios';
axios.interceptors.request.use(function(config) {
const token = this.$store.state.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
Now I have 2 problems/questions about it.
I know that it is available to use this.$store.state.logged or this.$store.state.token across every component but can I use it same way in single javascript file?
Where should I execute/start my interceptor javascript file? It is independent file which lays in my app main folder but I am not calling it anywhere, in angularJS which I was working before, I had to add $httpProvider.interceptors.push('authInterceptorService'); in config but I don't know how to do same thing in vue architecture. So where should I inject my interceptor?
EDIT
I followed GMaiolo tips I added
import interceptor from './helpers/httpInterceptor.js';
interceptor();
to my main.js file and I refactor my interceptor to this:
import axios from 'axios';
import store from '../store/store';
export default function execute() {
axios.interceptors.request.use(function(config) {
const token = this.$store.state.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
}
Result of this changes is that every already existing backend calls ( GET ) which don't need token to work stopped working but it is logical because I didn't clarified to which request it should add token so it is trying to add it everywhere and in my interceptor something is still wrong and that is why every already exisitng request stopped working.
When I try to do backend POST call in browser console I still get this error:
TypeError: Cannot read property '$store' of undefined
Although I import store to my interceptor file. Any ideas? I can provide some more information if any needed.
I additionally add screenshot of this main, store and interceptor tree structure so you can see that I'm importing fron correct path:
1.
First of all I'd use a Vuex Module as this Login/Session behavior seems to be ideal for a Session module. After that (which is totally optional) you can set up a Getter to avoid accessing the state itself from outside Vuex, you'd would end up with something like this:
state: {
// bear in mind i'm not using a module here for the sake of simplicity
session: {
logged: false,
token: ''
}
},
getters: {
// could use only this getter and use it for both token and logged
session: state => state.session,
// or could have both getters separated
logged: state => state.session.logged,
token: state => state.session.token
},
mutations: {
...
}
With those getters set, you can get the values a bit easier from components. With either using this.$store.getters.logged (or the one you'd want to use) or using the mapGetters helper from Vuex [for more info about this you can check the getters docs]:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters([
'logged',
'token'
])
}
}
2.
I like to run Axios' interceptors along with Vue instantation in main.js creating, importing and executing an interceptors.js helper. I'd leave an example so you get an idea, but, then again, this is my own preference:
main.js
import Vue from 'vue';
import store from 'Src/store';
import router from 'Src/router';
import App from 'Src/App';
// importing the helper
import interceptorsSetup from 'Src/helpers/interceptors'
// and running it somewhere here
interceptorsSetup()
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
});
interceptors.js
import axios from 'axios';
import store from 'your/store/path/store'
export default function setup() {
axios.interceptors.request.use(function(config) {
const token = store.getters.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
}
And there you'd end up having all the behavior cleanly encapsulated.
I did the same logic. however, I just change the file name. I used axios/index.js but the store is undefined there. so I just change the file name axios/interceptor.js and Don't know store data is accessible look at my below image