Reputation: 2029
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?
Upvotes: 0
Views: 2202
Reputation: 90068
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 the accessToken
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).
.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),
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.
Upvotes: 2