I am building an application using vue 3 composition api and pinia store. When a user login is successful, I store the token in localStorage
then push the user to the dashboard. On mounting the dashboard view, I want to make use of the store data which is meant to call an authorized only endpoint so I have created a pinia store profile
import { defineStore } from "pinia";
import http from '../http-common';
export const useProfile = defineStore("profile", {
state: () => {
return {
userSummary: {},
error: null
};
},
getters: {
async userSummary() {
try {
const res = await http.get('dashboard');
this.userSummary = res.data.data;
} catch (error) {
this.error = error;
}
}
}
});
In http-common.js
, I am using axios and getting the stored token from localStorage and using that for the api call
import axios from "axios";
export default axios.create({
baseURL: "https://api-domain/api/",
headers: {
"Authorization": `Bearer ${localStorage.getItem('accessToken')}`
}
});
signin where accessToken
his stored in localStorage
const loginUser = async () => {
try {
const res = await axios.post(
"https://api-domain/api/login",
signin.value,
{
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
);
localStorage.setItem("accessToken", res.data.data.accessToken);
localStorage.setItem("verified", res.data.data.verified);
// redirect home
router.push({ name: "dashboard" });
} catch (error) {
error = error.response.data.message;
alert(error);
}
};
and in the dashboard view, I am using the profile store
<script setup>
import {onMounted} from "vue";
import {useProfile} from "../stores/profile";
const profile = useProfile();
onMounted(() => {
profile.userSummary;
});
</script>
The problem is that when the dashboard view is mounted, the api call fails and I can see that request authorization in null Authorization: Bearer null
. Checking from developer tools, I can see that the accessToken
is actually set and not null.
How do I fix this issue?
CodePudding user response:
You are expecting localStorage to be reactive here:
headers: {
"Authorization": `Bearer ${localStorage.getItem('accessToken')}`
}
But it's not. The string set to axios config's header.Authorization
will not magically change later on, when you overwrite the value of the token in local storage.
Here's what you're currently doing:
You're configuring
axios
by reading theaccessToken
from local storage (the call to authenticate has not yet been made). Even if there is a token in local storage, it's there from a previous session and can therefore be expired.You log the user in, and set the token to local storage.
You continue using the
axios
instance, configured at step 1.
You probably want to use axios interceptors. That's because interceptors are run when the request/response is made/received, not when axios is configured.
you need a request interceptor: if the current call goes to your API and if it's not an authentication request, you read the token from some storage (typically the auth store).
- if the token is truthy, attach the header to the call and return it
- if the token is falsy, wrap the request in a promise, push that promise into an array and return the promise (it will be resolved later, after you have a token).
You need a watch on the token. When the token changes, if it's truthy, re-send all the members of your promises array and empty the array.
When I say resend, I mean resolve the stored promise with a new axios request, created using the.config
of the original request. By doing this, you're re-queuing the request, which makes it go through the request interceptor again. They will now pass, since you have a token and they will get the authentication header attached.
you also need a response interceptor: if the current call has returned with
401
(it means the token has just expired),- push the call to the array of promises,
- remove the token from the store
- instantiate a new authentication call.
While that call resolves, if the app is making any other calls to the API, they will end up in the array of promises (because you don't have a token now).
Obviously, if the error has a different code (other than 401
), you need to throw it: it's legit.
That's pretty much it.
It does have one minor problem, though: if your authentication is broken and, instead of providing valid tokens it provides tokens which do not work, your app will continuously try to authenticate after getting a faulty token, which will fail again and then a new authentication request will be made, in an endless loop.
To fix this, you probably want to put a counter on authentication failures. I din't include it above as I thought it was important to present the main authentication logic as clearly as possible.