JWT header in Vue-FilePond - vue.js

I'm looking for a way to update the settings (options) dynamically in Vue-FilePond. It is setup as following in my component:
import store from '#/store'
import vueFilePond from 'vue-filepond'
export default {
name: 'MyComponent',
components: {
FilePond
},
data: function () {
return {
...
serverOptions: {
url: 'https://my.backend/',
process: {
url: './process/',
headers: {
Authorization: 'Bearer ' + this.getToken(),
'x-access-token': this.getToken()
}
},
// other endpoints are configured in the same way
},
...
},
methods: {
getToken: function () {
return store.state.access_token
},
...
}
As you might see, my token is stored in Vuex. I also tried to set the header without the method:
process: {
url: './process/',
headers: {
Authorization: 'Bearer ' + store.state.access_token,
'x-access-token': store.state.access_token
}
},
This doesn't work either. I also tried to set the serverOptions in computed
computed: {
...mapState({
token: state => state.access_token
}),
serverOptions () {
process: {
url: './process/',
headers: {
Authorization: 'Bearer ' + this.token,
'x-access-token': this.token
}
}
}
}
without any success either. This seems to update the data in my component and Vue-FilePond but doesn't reflect in FilePond xhr calls.
My issue is that the token expires and, let's say if I send a very big file, the patch calls will start to get an HTTP 401.
The token is updated before expiring but I can't seem to find a way to pass that to FilePond.
Anyone with an idea would be greatly appreciated.
Thanks
//Geo

Related

How to set session using Nuxt-Auth?

I am trying to use Nuxt/auth, but run into problem with session saving in localStorage.
Login.vue
methods: {
sendLoginLink() {
this.$auth.loginWith('local', {
data: {
username: "test#gmail.com",
password: "testpassword"
}
}).then((date) => {
console.log("data", date)
}).catch((err) => {
console.error("err", err)
})
}
Nuxt.config.js
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/dashboard', method: 'post', propertyName: 'token' }
},
tokenType: ''
}
}
axios: {
baseURL: 'http://localhost:1234'
},
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth-next',
]
When the user logs in, the function sendLoginLink is called and throw error in console:
Which means that the auth-token is so large that it cannot be stored in localStorage, but all other things related to this function are saved in localStorage. For example:
I googled a lot but didn't find a good solution to the problem. For example, I tried to clear all localStorage memory before running sendLoginLink() function but the result is the same. Different browsers have the same problem

Vue Dynamic URL From state. In axios call

I want to parse the axios url call from a variable state in vuex. As simple as this:-
state: {
getOrganizationURL: "opensource",
}
actions: {
getOrganizationDashboard({commit, state}) {
axios
.get(`"https://xxx/api/report/"${state.getOrganizationURL}`, {
headers: {
Authorization: `Bearer ${state.currentAuthToken}`,
},
})
.then(response => {
commit("setOrganizationsDashboardData",response.data)
});
},
}
when checking the network on the browser. The API call is going to a weird url instead.
e.g
Request URL: http://localhost:8080/%22https://xxxx/api/report/%22opensource
which leads to page not found...
I think it a minor mistake. Any tips please?

Send silent request to refresh tokens

I am using Vue.js in frontend and JWT based authentication system. I have refresh tokens and access tokens. Access tokens have short amount of expiration time whereas refresh tokens have way longer. I want to send a request to server to refresh user's access token silently. I know when the access token will be expired. I want to refresh it 1 minute, or something, before it expires. How can I implement this? I thought to do it with putting a counter to my root component but I have no an exact solution. Thanks.
I have a similar problem as you do and found this Vue JWT Auth in the same search that pulled your answer. Implementation was a little more challenging than I had originally anticipated.
My application needs to have the JWT tokens refresh on page reloads and immediately before API calls. To do this I use axios to consume the APIs, allowing the use of an interceptor to check the validity of the tokens. To keep the UX smooth, I use the vuex store to maintain the tokens, escalating to localStorage, and then to making an external request for new tokens if each previous stage was not successful.
The components outside of the store look like:
src/utils/apiAxios.js: used to consume APIs
import axios from 'axios'
import config from '../../config'
import store from '../store'
const apiAxios = axios.create({
baseURL: `${config.dev.apiURL}api/`,
timeout: 1000,
headers: {'Content-Type': 'application/json'}
})
// before any API call make sure that the access token is good
apiAxios.interceptors.request.use(function () {
store.dispatch('isLoggedIn')
})
export default apiAxios
To src/main.js added these lines:
import store from './store'
router.beforeEach((to, from, next) => {
let publicPages = ['/auth/login/', '/auth/register/']
let authRequired = !publicPages.includes(to.path)
let loggedIn = store.dispatch('isLoggedIn')
if (authRequired && !loggedIn) {
return next('/auth/login/')
}
next()
})
src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'
import auth from './modules/auth'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production'
export default new Vuex.Store({
modules: {
auth
},
strict: debug,
plugins: debug ? [createLogger()] : []
})
src/store/modules/auth.js:
import axios from 'axios'
import jwtDecode from 'jwt-decode'
import router from '../../utils/router'
import apiAxios from '../../utils/apiAxios'
import config from '../../../config'
export default {
state: {
authStatus: '',
jwt: {
refresh: '',
access: ''
},
endpoints: {
obtainJWT: config.dev.apiURL + 'auth/',
refreshJWT: config.dev.apiURL + 'auth/refresh/',
registerJWT: config.dev.apiURL + 'auth/register/',
revokeJWT: config.dev.apiURL + 'auth/revoke/',
verifyJWT: config.dev.apiURL + 'auth/verify/'
}
},
mutations: {
UPDATE_TOKEN (state, newToken) {
apiAxios.defaults.headers.common['Authorization'] = `Bearer ${newToken.access}`
localStorage.setItem('jwtAccess', newToken.access)
localStorage.setItem('jwtRefresh', newToken.refresh)
state.authStatus = 'success'
state.jwt = newToken
},
UPDATE_STATUS (state, statusUpdate) {
state.authStatus = statusUpdate
},
REVOKE_TOKEN (state) {
delete apiAxios.defaults.headers.common['Authorization']
localStorage.removeItem('jwtAccess')
localStorage.removeItem('jwtRefresh')
state.authStatus = ''
state.jwt = {
refresh: '',
access: ''
}
}
},
getters: {
authStatus: state => state.authStatus,
isLoggedIn: getters => {
// quick check of the state
return getters.authStatus === 'success'
}
},
actions: {
login ({ state, commit }, { email, password }) {
axios({
url: state.endpoints.obtainJWT,
method: 'POST',
data: {
email: email,
password: password
},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
register ({ state, commit }, { email, password, firstName, lastName }) {
axios({
url: state.endpoints.registerJWT,
method: 'POST',
data: {
email: email,
password: password,
first_name: firstName,
last_name: lastName
},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
logout ({ state, commit }) {
let refresh = localStorage.getItem('jwtRefresh')
axios({
url: state.endpoints.revokeJWT,
method: 'POST',
data: { token: refresh },
headers: {'Content-Type': 'application/json'}
})
.then(commit('REVOKE_TOKEN'))
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
refreshTokens ({ state, commit }) {
let refresh = localStorage.getItem('jwtRefresh')
axios({
url: state.endpoints.refreshJWT,
method: 'POST',
data: {refresh: refresh},
headers: {'Content-Type': 'application/json'}
})
.then((response) => {
this.commit('UPDATE_TOKEN', response.data)
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
},
verifyToken ({ state, commit, dispatch, getters }) {
let refresh = localStorage.getItem('jwtRefresh')
if (refresh) {
axios({
url: state.endpoints.verifyJWT,
method: 'POST',
data: {token: refresh},
headers: {'Content-Type': 'application/json'}
})
.then(() => {
// restore vuex state if it was lost due to a page reload
if (getters.authStatus !== 'success') {
dispatch('refreshTokens')
}
})
.catch((error) => {
commit('UPDATE_STATUS', error)
console.log(error)
})
return true
} else {
// if the token is not valid remove the local data and prompt user to login
commit('REVOKE_TOKEN')
router.push('/auth/login/')
return false
}
},
checkAccessTokenExpiry ({ state, getters, dispatch }) {
// inspect the store access token's expiration
if (getters.isLoggedIn) {
let access = jwtDecode(state.jwt.access)
let nowInSecs = Date.now() / 1000
let isExpiring = (access.exp - nowInSecs) < 30
// if the access token is about to expire
if (isExpiring) {
dispatch('refreshTokens')
}
}
},
refreshAccessToken ({ dispatch }) {
/*
* Check to see if the server thinks the refresh token is valid.
* This method assumes that the page has been refreshed and uses the
* #verifyToken method to reset the vuex state.
*/
if (dispatch('verifyToken')) {
dispatch('checkAccessTokenExpiry')
}
},
isLoggedIn ({ getters, dispatch }) {
/*
* This method reports if the user has active and valid credentials
* It first checks to see if there is a refresh token in local storage
* To minimize calls it checks the store to see if the access token is
* still valid and will refresh it otherwise.
*
* #isLoggedIn is used by the axios interceptor and the router to
* ensure that the tokens in the vuex store and the axios Authentication
* header are valid for page reloads (router) and api calls (interceptor).
*/
let refresh = localStorage.getItem('jwtRefresh')
if (refresh) {
if (getters.isLoggedIn) {
dispatch('checkAccessTokenExpiry')
} else {
dispatch('refreshAccessToken')
}
return getters.isLoggedIn
}
return false
}
}
}
I'm using django for my backend and django-rest-framework-simplejwt for the tokens. The returned JSON is formatted like:
{
access: "[JWT string]",
refresh: "[JWT string]"
}
with a token structure of:
header:
{
"typ": "JWT",
"alg": "HS256"
}
payload:
{
"token_type": "access",
"exp": 1587138279,
"jti": "274eb43bc0da429a825aa30a3fc23672",
"user_id": 1
}
When accessing the refresh endpoint, SimpleJWT requires in the data the refresh token be named refresh; for the verification and the revocation (blacklisting) endpoints the refresh token needs to be named token. Depending on what you are using for your backend will require modification from what I did.
The access token is only used in the api Authentication header and is updated when the mutations are called.
To get the token so I could decode it I used a simple shell script:
#!/usr/bin/env bash
EMAIL="my#email.com"
PASSWORD="aReallyBadPassword"
echo "API Login Token"
JSON_FMT='{"email":"%s","password":"%s"}'
JSON_FMT=` printf "$JSON_FMT" "$EMAIL" "$PASSWORD" `
curl \
--request POST \
--header Content-Type:application/json \
--data $JSON_FMT \
http://localhost:8000/api/auth/
echo ""

Problem with PUT request in axios with vue.js

I'm building a smart home application. I have a problem with sending PUT request to my rest api (I building it with flask), but when I try send request it gives me HTTP 400 error (( Uncaught (in promise) Error: Request failed with status code 400 )) . Can you help me?
import axios from 'axios';
export default {
data: function() {
return {
value: 0,
lampName: 'Kitchen',
};
},
mounted () {
axios
.get("http://127.0.0.1:5000/lamp/" + this.$route.params.id)
.then(response => (this.value = response.data))
},
methods: {
updateValue () {
axios
.put('http://127.0.0.1:5000/lamp/' + this.$route.params.id,
{value: this.value},
{headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
})
}
}
}
try to add the method field to the form and send it in post way like this
formData.append('_method', 'PUT')
then try to send the data regularly
I think it work like this:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js">
import axios from 'axios';
export default {
data: function() {
return {
value: 0,
lampName: 'Kitchen',
};
},
mounted () {
axios
.get("http://127.0.0.1:5000/lamp/" + this.$route.params.id)
.then(response => (this.value = response.data))
},
methods: {
updateValue () {
let formData = new FormData();
formData.append("value", this.value);
formData.append("lampName", this.lampName);
formData.append('_method', 'PUT');
axios
.post('http://127.0.0.1:5000/lamp/' + this.$route.params.id,
formData
})
}
}
}
</script>
So this is the failing request:
axios
.put('http://127.0.0.1:5000/lamp/' + this.$route.params.id,
{value: this.value},
{headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
})
I don't know what your server is expecting but you're setting a content-type of application/x-www-form-urlencoded while sending JSON data. It seems likely this mismatch is the cause of your problems. You should be able to see this if you inspect the request in the Network section of your browser's developer tools.
If you need to use application/x-www-form-urlencoded then I suggest reading the axios documentation as you can't just pass in a data object like that:
https://github.com/axios/axios#using-applicationx-www-form-urlencoded-format
In short, you need to build up the body string manually, though there are utilities to make that less onerous.
If you actually want JSON data then just remove the content-type header. Axios should set a suitable content-type for you.
Pass your method as post method and define put in form data formData.append('_method', 'PUT') .
updateValue () {
axios
.post('http://127.0.0.1:5000/lamp/' + this.$route.params.id,
{value: this.value, _method: 'PUT'},
{headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
})
}

how to handle error 401 in simple ember auth?

The problem is the session will expire after a predetermined amount of time. Many times when this happens the ember.js app is still loaded. So all requests to the backend return a 401 {not autorized} response.
so i need to redirect user to the login page and clear the last token from the session so that isauthenticated property becomes false.
I am using custom authenticator.
import Base from 'ember-simple-auth/authenticators/base';
import ENV from '../config/environment';
import Ember from 'ember';
export default Base.extend({
restore: function(data) {
return new Ember.RSVP.Promise(function (resolve, reject) {
if (!Ember.isEmpty(data.token)) {
resolve(data);
}
else {
reject();
}
});
},
authenticate: function(options) {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
type: "POST",
contentType: 'application/json',
url: ENV.CONSTANTS.API_URL + '/authentication',
data: JSON.stringify({
username: options.username,
password: options.password
})
}).then(function(response) {
if(!response.token){
Ember.run(function(){
reject(response.message);
});
} else {
Ember.run(function() {
resolve(response);
});
}
}, function(xhr, status, error) {
Ember.run(function() {
reject(xhr.responseJSON || xhr.responseText);
});
});
});
},
invalidate: function(data) {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
type: "GET",
url: ENV.CONSTANTS.API_URL + '/authentication/logout'
}).then(function(response) {
Ember.run(function() {
resolve(response);
});
}, function(xhr, status, error) {
Ember.run(function() {
reject(xhr.responseJSON || xhr.responseText);
});
});
});
}
});
I am using ember simple auth 1.0.0. Anybody have a working solution to this problem?
If you're using the DataAdapterMixin that will automatically handle all 401 response to Ember Data requests and invalidate the session if it gets one. If you're making your own AJAX requests you'd have to handle these responses yourself.
Automatic authorization of all requests as well as automatic response handling was removed in 1.0.0 as it lead to a lot of problems with global state and made the whole library much harder to reason about.