How to set global $axios header in NuxtJS - vue.js

I've been trying to get this to work for two days now. I'm a brand new user to Nuxt (although I've used Vue for a few years now), so I'm just trying to wrap my brain around how this all works.
In my Nuxt project I have the Axios module installed:
nuxt.config.js
export default {
plugins: [
...
'~/plugins/axios',
],
axios: {
baseURL: 'https://my-url.com/wp-json/wp-v2',
https: true,
},
}
plugins/axios.js
export default ({ $axios, env }) => {
$axios.onRequest(config => {
$axios.setToken(env.WP_API_KEY, 'Bearer');
});
}
And in my page, I'm trying to use the asyncData function to pull data from my WordPress API, as such:
export default {
async asyncData(context) {
const data = await context.$axios.$get('/media');
console.log(data);
return { data };
}
}
I keep receiving a 401 Not Authorized error however, essentially stating that my Authorization: Bearer <token> isn't being passed through. Using Postman however, I can verify that this endpoint does indeed work and returns all of the JSON I need, so the problem must lie in the way I have the axios global header set up.
It's been tough finding any real example on how to set a global header using the Nuxt/Axios module. I see in the docs how to use setToken, however it doesn't exactly show where to place that.
What do I have set up wrong, and how do I fix it?

Pretty typical that I get it working 15 minutes after I post a question.
Setting the header like this instead seems to work. I'm not sure why the setToken method didn't want to work.
export default ({ $axios, env }) => {
$axios.onRequest(config => {
config.headers.common['Authorization'] = `Bearer ${env.WP_API_KEY}`;
});
}

If you are using Nuxt auth module, Here is how I have achived.
// nuxt.config.js
modules: [
'#nuxtjs/auth',
'#nuxtjs/axios',
],
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/auth/login', method: 'post', propertyName: 'accessToken' },
logout: false,
user: { url: '/auth/me', method: 'get', propertyName: false }
},
}
},
redirect: {
login: '/auth/signin',
logout: '/auth/signin',
callback: false,
home: false,
},
cookie: false,
token: {
prefix: 'token',
},
plugins: ['~/plugins/auth.js'],
},
// plugins/axios.js
export default function ({ $axios, $auth, redirect, store }) {
$axios.onRequest((config) => {
config.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': store.state.auth.tokenlocal, // refers to nuxt.config.js->auth.token
}
})
$axios.onError((error) => {
if (error.response.status === 500) {
redirect('/error')
}
})
}
// store/index.js
export const getters = {
authenticated(state) {
return state.loggedIn;
},
user(state) {
return state.user;
}
};
export const state = () => ({
busy: false,
loggedIn: false,
strategy: "local",
user: false,
});

If you need to customize axios by registering interceptors and changing global config, you have to create a nuxt plugin.
export default ({ $axios, env }) => {
$axios.onRequest(config => {
config.headers.common['Authorization'] = `Bearer ${env.WP_API_KEY}`;
});
}
Adding axios interceptors

Related

how can i encode a token in my url using vuejs

i have an app which recieves token in my broswer url
http://localhost:8081/reset/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MmU2YWJmMmMzMzI0Mjk1NGQyNmVjZjIiLCJpYXQiOjE2NTk1MDIwNTEsImV4cCI6MTY1OTUwMjk1MX0.GIlKy_GI7HlfuB1WgD9HPxOGRZUX2_uOtOclrDTW3Y8
how can i remove (.) from my url
this is how i go to my route
{ name: "reset", path: "/reset/:token", component: Reset },
this is my script tag on how i call the function
<script>
import axios from "axios";
export default {
data() {
return {
password: "",
confirm_password: ""
};
},
mounted() {
console.log("the id is :" + this.$route.params.token);
},
methods: {
async submit() {
let token = this.$route.params.token;
let encoded = encodeURI(token);
return axios({
method: "post",
data: {
password: this.password,
token: this.$route.params.token
},
url: "http://localhost:5000/api/auth/updatePassword",
headers: {
"Content-Type": "application/json"
}
})
.then(res => {
console.log(res);
this.$router.push({ name: "login" });
})
.catch(error => {
console.log(error);
});
},
clear() {
this.$refs.form.reset();
}
}
};
</script>
i can't get the reset page until i remove the (.) please how can i encode the token
The token that you have is a JWT token, which should contain the two dots. I don't think removing them is a good idea. However, it looks like Vue router interprets the dots like a separator or something, causing the router to fail in finding the route.
What you might do is use a query string instead of a route param. You add the token to the url like:
http://localhost:8081/reset?token=eyJhbGciOiJ...
You should change the route to:
{ name: "reset", path: "/reset", component: Reset },
Now you can get it from the router with:
this.$route.query.token

Add header to all apollo query

As the title suggest, I am trying to add a header to all queries and mutations made by apollo. I know I can do
context: {
headers: {
'Accept-Language': $this.i18n.current;
}
}
but that is only for one query or mutation. I am using nuxt with vue and my current nuxt.config.js is as follows
apollo: {
clientConfigs: {
default: '~/plugins/apollo-config.js'
},
defaultOptions: {
$query: {
fetchPolicy: 'network-only',
context: { // does not work
headers: {
"Accept-Language": $this.i18n.current, // not sure if this works as it is in config
}
}
}
},
errorHandler: '~/plugins/apollo-error-handler.js'
},
I'm pretty sure I'm using context wrong in this case but not sure how else I should do it. Any help would be very much appreciated.
I'm not at all a professional regarding GraphQL but last year, I've achieved something that works well (with a JWT header), here is what I had back at the time
nuxt.config.js
apollo: {
clientConfigs: {
default: '#/plugins/nuxt-apollo-config.js',
},
defaultOptions: {
$query: {
loadingKey: 'loading',
fetchPolicy: 'network-only',
},
},
authenticationType: 'Bearer',
},
and here is my
nuxt-apollo-config.js file
import { setContext } from 'apollo-link-context'
import { from } from 'apollo-link'
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'
import { createHttpLink } from '#apollo/client/core'
import schema from '../apollo/schema.json'
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: schema,
})
export default ({ app, $config: { baseUrlGraphql } }) => {
const headersConfig = setContext(() => ({
credentials: 'same-origin',
headers: {
Authorization: app.$cookies.get('auth._token.local'),
'x-company-id': app.$cookies.get('company_id'),
},
}))
const cache = new InMemoryCache({ fragmentMatcher, resultCaching: false })
return {
defaultHttpLink: false,
link: from([
headersConfig,
createHttpLink({
credentials: 'include',
uri: baseUrlGraphql,
fetch: (uri, options) => {
return fetch(uri, options)
},
}),
]),
cache,
}
}
import { setContext } from 'apollo-link-context' worked well for me. I'm not sure that it's the best because there is maybe something baked-in right now but this one worked for me last year.

Cant mock axios api call with axios-mock-adapter in Storybook with Vuejs

Been trying to use storybook with my VueJS project and Im stuck with mocking api calls.
I tried using axios-mock-adapter without luck.
My storybook file code is:
import { storiesOf } from '#storybook/vue';
import { action } from '#storybook/addon-actions';
import { withKnobs, boolean } from '#storybook/addon-knobs';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import fileUpload from './fileUpload.vue';
const mock = new MockAdapter(axios);
mock
.onPost('https://s3.amazonaws.com')
.reply(200, []);
storiesOf('Common|File CSV Upload', module)
.addDecorator(withKnobs)
.add('Default', () => ({
components: { fileUpload },
data: () => ({
}),
template: `
<v-flex>
<file-upload></file-upload>
</v-flex>`,
methods: {
action: action('file upload'),
},
}));
Am I using it right?
My strong recommendation is to use storybook-addon-mock for mocking (axios) API calls in Storybook.
It is nicley integrated into Storybook, setup in the different stories is easy and the data can be alteresd in the corresponding panel.
These 4 steps are needed:
Add the additional dependency: yarn add storybook-addon-mock
adapt the config and add in .storybook/main.js:
module.exports = {
addons: [
...
'storybook-addon-mock',
],
configure the behaviour and add mock data for general/ repeating API calls in .storybook/preview.js. These are mocks that will be available in every story.
export const parameters: Parameters = {
mockAddonConfigs: {
globalMockData: [
{
url: 'api/token',
method: 'POST',
status: 200,
response: () => '1234567abcdefg',
},
],
refreshStoryOnUpdate: true, // This re-render the story if there's any data changes
// disable: true, // This disables the panel from all the stories
}
in your story file add:
export default {
title: 'components/myComponentName',
component: MyComponent,
parameters: {
mockData: [
{
url: '/api/myendpoint/',
method: 'GET',
status: 200,
delay: 500,
response: () => 'some content',
},
{
url: '/api/myendpoint/',
method: 'POST',
status: 200,
delay: 1000,
response: {
data: 'some response',
},
},
],
},
Hint: Have a look into the different responses - function, string, etc to match them with the real response. There can be a pitfall with the data entry, that can be avoided with response: () => 'some content'

Not able to access token using axios and djangorestframework-jwt

I am trying to make an application which servers api through django rest framework and the frontend is done in vue.js2.
I am referring this blog to help me authenticating jwt via axios. I also tried this one when I couldn't make the first one run correctly
Here is my settings.py file
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework.authtoken',
'admindash',
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser',
),
'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 20,
}
JWT_AUTH = {
'JWT_ALLOW_REFRESH': True,
'JWT_EXPIRATION_DELTA': timedelta(hours=1),
'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7),
}
#Cors origin
CORS_ORIGIN_WHITELIST = (
'localhost:8080'
)
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
Here is my vue store
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
export default new Vuex.Store({
state: {
authUser: {},
isAuthenticated: false,
jwt: localStorage.getItem('token'),
endpoints: {
obtainJWT: 'http://127.0.0.1:8000/api/v1/auth/obtain_token/',
refreshJWT: 'http://127.0.0.1:8000/api/v1/auth/refresh_token/',
baseUrl: 'http://127.0.0.1:8000/api/v1'
}
},
mutations: {
setAuthUser(state, {
authUser,
isAuthenticated
}) {
Vue.set(state, 'authUser', authUser)
Vue.set(state, 'isAuthenticated', isAuthenticated)
},
updateToken(state, newToken) {
localStorage.setItem('token', newToken);
state.jwt = newToken;
},
removeToken(state) {
localStorage.removeItem('token');
state.jwt = null;
}
},
actions: {
obtainToken(context, {username, password}) {
const payload = {
username: username,
password: password
}
const headers= {
'Content-Type': 'application/json'
}
axios.post(this.state.endpoints.obtainJWT, headers, payload)
.then((response) => {
this.commit('updateToken', response.data.token);
console.log(this.state.jwt);
})
.catch((error) => {
console.log(error);
})
},
refreshToken () {
const payload = {
token: this.state.jwt
}
axios.post(this.state.endpoints.refreshJWT, payload)
.then((response) => {
this.commit('updateToken', response.data.token);
console.log(this.state.jwt)
})
.catch((error) => {
console.log(error)
})
}
}
})
and here is login.vue
<script>
import axios from 'axios'
import FormInput from './FormInput'
export default {
name: 'Login',
components: {
FormInput
},
data () {
return {
username: '',
password: ''
}
},
computed: {
/* ...mapState([
'jwt',
'endpoints'
]) */
},
methods: {
/* ...mapActions([
'obtainToken'
]), */
authenticateBeforeSubmit () {
this.$store.dispatch('obtainToken', {
username: this.username,
password: this.password
}).then(() => {
this.$router.push('/')
}).catch((error) => {
console.log(error)
})
/* const payload = {
username: this.username,
password: this.password
}
axios.post(this.$store.state.endpoints.obtainJWT, payload)
.then((response) => {
this.$store.commit('updateToken', response.data.token)
console.log(this.$store.state.jwt);
const base = {
baseUrl: this.$store.state.endpoints.baseUrl,
headers: {
Authorization: `JWT ${this.$store.state.jwt}`,
'Content-Type': 'application/json'
},
xhrFields: {
withCredentials: true
}
}
const axiosInstance = axios.create(base)
axiosInstance({
url: "/user/",
method: "get",
params: {}
})
.then((response) => {
this.$store.commit("setAuthUser",
{authUser: response.data, isAuthenticated: true}
)
this.$router.push({name: 'Home'})
})
})
.catch((error) => {
console.log(error);
console.debug(error);
console.dir(error);
}) */
}
}
}
</script>
Now the problem is I am getting two errors
Just as I load login view in browser, i get this error
Uncaught (in promise) TypeError: Cannot read property 'protocol' of undefined
at isURLSameOrigin (VM34519 isURLSameOrigin.js:57)
at dispatchXhrRequest (VM34513 xhr.js:109)
at new Promise ()
at xhrAdapter (VM34513 xhr.js:12)
at dispatchRequest (VM34521 dispatchRequest.js:59)
isURLSameOrigin # VM34519 isURLSameOrigin.js:57
dispatchXhrRequest # VM34513 xhr.js:109
xhrAdapter # VM34513 xhr.js:12
dispatchRequest # VM34521 dispatchRequest.js:59
18:29:09.976
I don't have slightest idea what this error is about, I searched it and i didn't find anything that works
This I get when I click submit which fires authenticateBeforeSubmit method
Uncaught TypeError: Cannot read property 'dispatch' of undefined
at VueComponent.authenticateBeforeSubmit (VM34576 Login.vue:68)
at invoker (VM34494 vue.esm.js:2026)
at HTMLButtonElement.fn._withTask.fn._withTask (VM34494 vue.esm.js:1825)
authenticateBeforeSubmit # VM34576 Login.vue:68
invoker # VM34494 vue.esm.js:2026
fn._withTask.fn._withTask # VM34494 vue.esm.js:1825
18:29:30.912
What i understand it is saying that i am calling dispatch on action incorrectly but i don't get how to make it work
As I click submit it "redirects" to
http://127.0.0.1:8080/login?username=f1uk3r&password=thisissparta
but it doesn't recieves any token
I have tested token using
curl -X POST -H "Content-Type: application/json" -d '{"username":"f1uk3r","password":"thisissparta"}' http://127.0.0.1:8000/api/v1/auth/obtain_token/
and it gives me a token so there shouldn't be any problem in django part I think. What am I doing wrong, how can I rectify it.
I figured it out, so answering for future reference and if anybody else finds this useful
TypeError: Cannot read property 'protocol' of undefined at isURLSameOrigin
this can be solved by importing axios and VueAxios correctly
I initialized it like this
import Vue from 'vue'
import VueAxios from 'vue-axios'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex);
Vue.use(axios, VueAxios);
While it should have been initialized like this
import Vue from 'vue'
import VueAxios from 'vue-axios'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex);
Vue.use(VueAxios, axios);
Cannot read property 'dispatch' of undefined at VueComponent
Again I wasn't initializing store correctly so i made a directory in "src" directory named it "store" and in this directory I made a file named "store.js"
Then in main.js import store and initialize in the instance
import store from './store/store.js';
Vue.use(VeeValidate);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})

Nuxt Auth Module Authenticated User Data

I have an API api/auth that is used to log users in. It expects to receive an access_token (as URL query, from Headers, or from request body), a username, and a password. I've been using the Vue Chrome Developer Tool and even though I get a 201 response from the server, the auth.loggedIn state is still false. I think that might be the reason why my redirect paths on the nuxt.config.js isn't working as well. Can anyone point me to the right direction on why it doesn't work?
This is a screenshot of the Vue Chrome Developer Tool
This is the JSON response of the server after logging in. The token here is different from the access_token as noted above.
{
"token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"user": {
"user_name": "xxxxxxxxxxxxxxxxxx",
"uid": "xxxxxxxxxxxxxxxxxx",
"user_data": "XXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
Here is the relevant part of nuxt.config.js
export default {
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth',
['bootstrap-vue/nuxt', { css: false }]
],
router: {
middleware: [ 'auth' ]
},
auth: {
strategies: {
local: {
endpoints: {
login: {
url: '/api/auth?access_token=XXXXXXXXXXXXXXXXXXXXXX',
method: 'post',
propertyName: 'token'
},
logout: {
url: '/api/auth/logout',
method: 'post'
},
user: {
url: '/api/users/me',
method: 'get',
propertyName: 'user'
}
}
}
},
redirect: {
login: '/',
logout: '/',
home: '/home'
},
token: {
name: 'token'
},
cookie: {
name: 'token'
},
rewriteRedirects: true
},
axios: {
baseURL: 'http://localhost:9000/'
}
}
And my store/index.js
export const state = () => ({
authUser: null
})
export const mutations = {
SET_USER: function (state, user) {
state.authUser = user
}
}
export const actions = {
nuxtServerInit ({ commit }, { req }) {
if (req.session && req.user) {
commit('SET_USER', req.user)
}
},
async login ({ commit }, { username, password }) {
const auth = {
username: username,
password: password
}
try {
const { user } = this.$auth.loginWith('local', { auth })
commit('SET_USER', user)
} catch (err) {
console.error(err)
}
}
}
The login action in the store is triggered by this method in the page:
export default {
auth: false,
methods: {
async login () {
try {
await this.$store.dispatch('login', {
username: this.form.email,
password: this.form.password
})
} catch (err) {
this.alert.status = true
this.alert.type = 'danger'
this.alert.response = err
}
}
}
}
P.S. I realize I'm explicitly including the access_token in the URL. Currently, I don't know where a master_key or the like can be set in the Nuxt Auth Module.
Try this in your store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = () => new Vuex.Store({
state: {
authUser: null
},
mutations: {
SET_USER: function (state, user) {
state.authUser = user
}
},
actions: {
CHECK_AUTH: function(token, router) {
if (token === null) {
router.push('/login')
}
}
}
})
export default store
And for the router, this should work globally:
$nuxt._router.push('/')