I have created admin dashboard for one client. Project is created using Vue.js with Nuxt.js. Backend is Directus and it was created by my colleague.
Problem is that auth middleware is not working as I need.
When I log in, I save AUTH_TOKEN and REFRESH_TOKEN in cookies. Then, I am firing up one API call, if response.message is: 'Token expired', I send new API call with REFRESH_TOKEN to refresh. Then, from response I save new REFRESH_TOKEN and new AUTH_TOKEN to cookies again and if response is not 200, I redirect user to /login.
Here is my code (/middleware/authenticated.js):
import authService from '../services/authService';
export default function ({ $cookies, redirect, store, $toast, $router }) {
const access_token = $cookies.get('access_token');
const refresh_token = $cookies.get('refresh_token');
if (!access_token) {
$cookies.remove('access_token');
$cookies.remove('refresh_token');
store.commit('RESET_USER');
return redirect('/login');
}
if(access_token){
store.commit('SET_USER');
setInterval(function(){
try{
authService.retrieveCurrentUser({headers: {
"Content-type": "application/json",
"Authorization": `Bearer ${access_token}`,
}})
.then(() => {
console.log('ok');
})
.catch ((error) => {
console.log('prvy catch error', error);
if(error.response.data.errors[0].message == 'Token expired.'){
const config = {
"refresh_token": refresh_token
}
authService.refreshToken(config)
.then((response) => {
$cookies.set('access_token', response.data.data.access_token);
$cookies.set('refresh_token', response.data.data.refresh_token);
store.commit('SET_USER');
return redirect();
})
.catch((err) => {
console.log('druhy catch error', err);
$cookies.remove('access_token');
$cookies.remove('refresh_token');
store.commit('RESET_USER');
$toast.error('Platnosť vášho prihlásenia vypršala, prihláste sa prosím znova.', { timeout: 5000 });
clearInterval(this);
return redirect('/login');
})
}
})
}
catch (err){
console.log('treti catch error', err);
$cookies.remove('access_token');
$cookies.remove('refresh_token');
store.commit('RESET_USER');
$toast.error('Platnosť vášho prihlásenia vypršala, prihláste sa prosím znova.', { timeout: 5000 });
clearInterval(this);
return redirect('/login');
}
}, 300000)
}
};
Here is authService:
import api from '#/services/api';
export default {
login (credentials){
return api().post('/auth/login', credentials);
},
refreshToken(config) {
return api().post('/auth/refresh', config);
},
logout (refresh_token){
return api().post('/auth/logout', refresh_token);
},
retrieveCurrentUser(refresh_token){
return api().get('/users/me', refresh_token);
}
};
And this is how I call middleware inside page:
middleware: 'authenticated',
Also I need that setInterval because I want to check if token is still valid every 5 minutes.
When I use this code, I am receiving automatic log outs, or spamming of that toast notification.
Related
I've notice (using some console.log) that the method checkAuth is called some times only before the login methods and not after, so
when the login is accomplished correctly, the token is stored in the browser's local storage and at the end the method login returns the resolved promise,
the checkAuth is not anymore invoked and the page redirect is not performed by the dashboard
If then i change manually the page, it works correctly because the token is in the localstorage and the checkAuth method is able to check it normally
This is my AuthProvider
import axios from "axios";
export default {
// called when the user attempts to log in
login: ({ username, password }) => {
username = encodeURIComponent(username);
password = encodeURIComponent(password);
const tokenUrl = "https://myendpoint/profili/token";
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
const config = {
headers
};
const data = `username=${username}&password=${password}&grant_type=password`;
axios.post(tokenUrl,data, config)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response;
})
.then(response => {
localStorage.setItem('token', response.data);
})
.catch((error) => {
throw new Error('Network error');
});
console.log("LOGIN");
return Promise.resolve();
},
// called when the user clicks on the logout button
logout: () => {
localStorage.removeItem('token');
return Promise.resolve();
},
// called when the API returns an error
checkError: ({ status }) => {
if (status === 401 || status === 403) {
console.log("passato in checkError");
localStorage.removeItem('token');
return Promise.reject();
}
return Promise.resolve();
},
// called when the user navigates to a new location, to check for authentication
checkAuth: () => {
console.log("CHECK AUTH");
return localStorage.getItem('token')
? Promise.resolve()
: Promise.reject();
},
// called when the user navigates to a new location, to check for permissions / roles
getPermissions: () => Promise.resolve(),
};
I think you did the wrong returns.
Your login function returns Promise.resolve() right at the moment it emits the axios HTTP POST request, long before it gets the response from the authentication server.
Instead replace the return by of the function by :
return axios.post(tokenUrl,data, config)
.then(response =>
{
if (response.status < 200 || response.status >= 300)
{
throw new Error(response.statusText);
}
return response;
}).then(response =>
{
localStorage.setItem('token', response.data);
})
.catch((error) => {
throw new Error('Network error');
});
This way the Promise resolution will only happens after your authentication server responds.
I am trying to get refresh tokens working.
It works for the most part, but the problem is I'm not sure how to return to where the original call was before the access token needed refreshing.
It doesn't work the first time it refreshes, after that the token is refreshed and then it works ok again until it expires.
So the problem is I cant get it returning to where it started on the refresh
Here is an example from the component
created(){
axios.get("http://localhost:63861/api/sampledata/WeatherForecasts").then(response => {
console.log(response.data);
//**** DO STUFF WITH THE DATA. I WANT TO GET BACK HERE AFTER REFRESH
})
.catch(error => {
console.log(error);
});
I need to get back to the point where it can do stuff with the data once it has refreshed and reset the access tokens.
my interceptor:
import axios from "axios";
import store from "../store";
import Storage from "../services/storage";
import { REFRESH } from "../store/actions.type";
export default function execute() {
// Request
axios.interceptors.request.use(
config => {
var token = Storage.getAccessToken();
if (token) {
console.log("Bearer " + token);
config.headers["Authorization"] = "Bearer " + token;
}
return config;
},
error => {
return Promise.reject(error);
}
);
// Response
axios.interceptors.response.use(
response => {
return response;
},
error => {
console.log("Error need to refresh");
const originalRequest = error.config;
// token expired
if (error.response.status === 401) {
originalRequest._retry = true;
let tokenModel = {
accessToken: Storage.getAccessToken(),
client: "Web",
refreshToken: Storage.getRefreshToken()
};
var refreshPath = "auth/" + REFRESH;
store
.dispatch(refreshPath, { tokenModel })
.then(response => {
console.log(response);
return axios(originalRequest);
})
.catch(error => {
console.log(error);
// Logout
});
}
return Promise.reject(error);
}
);
}
You need to return your refresh promise.
return store
.dispatch(refreshPath, { tokenModel })
.then(response => {
console.log(response);
return axios(originalRequest);
})
.catch(error => {
console.log(error);
// Logout
});
What is happening now is you dispatch the action, then your return Promise.reject(error) is ran. By returning the refresh promise, you ensure axios waits for that chain to finish
I am new to vue and stuck on this problem for quite some time. I have a login method that retrieves an API token and stores it in localStorage. The login API call is the only call that does not send Auth headers. After the Login every call should add the API token to the header.
When I login the interceptor does not set the new header. It needs a page refresh in the browser to work. Why is that, what am I doing wrong?
In my Login component I have this method:
methods: {
login() {
api.post('auth/login', {
email: this.email,
password: this.password
})
.then(response => {
store.commit('LOGIN');
localStorage.setItem('api_token', response.data.api_token);
});
this.$router.push('reservations')
}
}
Additionally I have this axios base instance and an interceptor:
export const api = axios.create({
baseURL: 'http://backend.local/api/',
// headers: {
// 'Authorization': 'Bearer ' + localStorage.getItem('api_token')
// },
validateStatus: function (status) {
if (status == 401) {
router.push('/login');
} else {
return status;
}
}
});
api.interceptors.request.use((config) => {
config.headers.Authorization = 'Bearer ' + localStorage.getItem('api_token');
return config;
}, (error) => {
return Promise.reject(error);
});
I have a SPA that is built on vuejs. When a user is logged in via API, the token is stored in local storage.
I need a global solution which will logout and prompt the user when the token is no longer valid. At the moment, I get "invalid token" error when accessing private API endpoints.
How do I rig axios so that ALL response of invalid tokens will trigger the logout/prompt code?
Here is an simple example with axios. It use a Bearer token for authentification.
import axios from "axios";
import { useUserStore } from "#/store/userStore";
const apiClient = axios.create({
baseURL: ""http://127.0.0.1:8001",
headers: {},
});
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const config = error?.config;
if (error?.response?.status === 401) {
const result = await refreshToken();
if (result) {
config.headers = {
...config.headers,
authorization: `Bearer ${result?.token}`,
};
}
return axios(config);
}
);
const refreshToken = async () => {
/* do stuff for refresh token */
// if refresh token failed
try {
useUserStore().actionLogout();
} catch (error) {
console.log(error);
} finally {
loacalStorage.clear();
}
};
you can write a function that clears your local storage after some time and logout user
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.