How to save Simple JWT Token in local storage using vue.js, vuex and django rest framework? - vue.js

I have a problem to save JWT Token in Local Storage (or cookie). Now, when I refresh my page, I need to login again. When I POST to /api-token with username and password in response I've got access token and refresh token, and now, don't now how to store them and where.
My loginForm.vue:
(<form...)
<script>
import axios from 'axios';
export default {
name: 'LoginForm',
data(){
return{
username: '',
password: '',
}
},
methods: {
login(){
this.$store.dispatch('userLogin', {
username: this.username,
password: this.password
})
.then(() => {
this.$router.push({ name: 'home'})
})
.catch(err => {
console.log(err)
})
}
}
}
</script>
and my store.js:
import Vue from 'vue'
import Vuex from 'vuex'
import { getAPI } from './api'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
accessToken: null,
refreshToken: null,
},
mutations: {
updateStorage (state, { access, refresh }) {
state.accessToken = access
state.refreshToken = refresh
},
destroyToken (state) {
state.accessToken = null
state.refreshToken = null
}
},
getters: {
loggedIn (state) {
return state.accessToken != null
}
},
actions: {
userLogin (context, usercredentials){
return new Promise((resolve, reject) => {
getAPI.post('/api-token/', {
username: usercredentials.username,
password: usercredentials.password
})
.then(response => {
context.commit('updateStorage', {access: response.data.access, refresh: response.data.refresh})
resolve()
})
})
},
userLogout (context) {
if (context.getters.loggedIn) {
context.commit('destroyToken')
}
}
}
})
I'm assuming I need to save them in local storage by store.js, after update and before destroy. Could you help me?

You need something like this:
You must save access token in default header's requests to auth user after every requests . also save token in localstorage:
axios.post('login', this.user)
.then(r=>
{
axios.defaults.headers.common['Authorization'] = 'Bearer ' + r.data.token;
localStorage.setItem( 'token', JSON.stringify(r.data.token) );
}
and add to default headers on refresh: (top of main.js file)
let token = JSON.parse( localStorage.getItem('token') );
if( token ){
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
}
now you can send request from every component

in the login page in (script) tag add this code
axios.post('/api/v1/token/login/', this.form)
.then((r) => {
axios.defaults.headers.common['Authorization']='Bearer'+r.data.auth_token;
localStorage.setItem('token', JSON.stringify(r.data.auth_token));
})
and in the main.js add
let token = JSON.parse( localStorage.getItem('token') );
if( token ){
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
}

Related

Catch(error) on dispatched method in store not working in Vue 3

I am working on login of a vue 3 app, both the login and registration work fine, but i still need to throw send back a meaningful response to user if login in credentials are rejected by the back-end, i have tried every possible means to log the rejection response from server to console but to no avail, the login is fine when credential is correct, but the console just stay mute when incorrect credential is entered
this is my login.vue
import store from "../store"
import { useRouter } from "vue-router";
import { ref } from "vue";
const router = useRouter()
const user = { email: '', password: '', remember : false }
let errorMsg = ref('');
async function login(ev) {
ev.preventDefault();
await store.dispatch('login', user)
.then(()=> {
router.push({
name: 'Dashboard'
})
})
.catch((err) => {
errorMsg = err.response.data.error
console.log(err)
})
}
and this is my vuex store
import {createStore} from 'vuex'
import axiosClient from "../axios";
const store = createStore({
state: {
user: {
data: {},
token: sessionStorage.getItem('TOKEN')
}
},
getters: {},
setters: {},
actions: {
register({commit}, user) {
return axiosClient.post('/register', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
login({commit}, user) {
return axiosClient.post('/login', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
},
mutations: {
logout: state => {
state.user.data = {};
state.user.token = null;
},
setUser: (state, userData)=> {
state.user.token = userData.token;
state.user.data = userData.user;
sessionStorage.setItem('TOKEN', userData.token)
}
},
modules: {}
})
export default store;
And here is my axios js file
import axios from "axios";
import store from "./store";
const axiosClient = axios.create({
baseURL: 'http://localhost:8000/api'
})
axiosClient.interceptors.request.use(config=> {
config.headers.Authorization = `Bearer ${store.state.user.token}`
return config;
})
export default axiosClient;
Response from backend as seen from Network Tab
{"error":"The provided credentials are incorrect","0":422}
After checking through my controller in my Laravel project, I discovered that I did not set the status code for the response properly.
Incorrect code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
422
]);
}
Corrected code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
], 422);
}
Axios does not treat the response received as a rejection; which needs to get its catch triggered.
Therefore my console.log that I had in my try/catch does not run at all.
I'm very happy we got this solved, big thanks to every one.

how to keep user authenticated after refreshing the page in nuxtjs?

I'm using laravel passport for API's and nuxt.js for frontend after a successful login if I refresh the page the user is not authenticated anymore and loggedIn returns false, its my first nuxt.js project so I have no idea how to deal with that, any advise is appreciated
login.vue
<script>
import { mapActions } from 'vuex'
export default {
data() {
return {
email: "",
password: ""
}
},
methods:{
async login(){
const succesfulLogin = await this.$auth.loginWith('local', {
data: {
email: this.email,
password: this.password
},
})
this.$store.commit("saveUser",succesfulLogin.data)
this.$store.commit("saveToken", succesfulLogin.data.token)
if (succesfulLogin) {
await this.$auth.setUser({
email: this.email,
password: this.password,
})
this.$router.push('/profile')
}
}
}
}
</script>
store/index.js
export const state = () => ({
user:{},
token: ""
})
export const mutations = {
saveUser(state, payload) {
state.user=payload;
},
saveToken(state, token) {
state.token= token
}
}
export const actions = {
saveUserAction({commit}, UserObject){
commit('saveUser');
},
logoutUser({commit}){
commit('logout_user')
}
}
export const getters = {
getUser: (state) => {
return state.user
},
isAuthenticated(state) {
return state.auth.loggedIn
},
loggedInUser(state) {
return state.user.user
}
}
after a successful login
after refreshing the page
We do use a global middleware right after my auth module authentication
/middleware/global.js
export default async ({ app, store }) => {
if (store?.$auth?.$state?.loggedIn) {
if (!app.$cookies.get('gql.me_query_expiration')) {
// do some middleware logic if you wish
await app.$cookies.set('gql.me_query_expiration', '5min', {
// maxAge: 20,
maxAge: 5 * 60,
secure: true,
})
}
}
}
nuxt.config.js
router: {
middleware: ['auth', 'global'],
},
We're using cookie-universal-nuxt for handling secure cookies quickly, working great!
While accessing or refreshing the webapp (we do redirect to the /login page if not authenticated) and we use this basic GraphQL configuration where the cookie is needed.
/plugins/nuxt-apollo-config.js
export default ({ app }) => {
const headersConfig = setContext(() => ({
credentials: 'same-origin',
headers: {
Authorization: app.$cookies.get('auth._token.local'), // here
},
}))
[...]
}
Checking gql.me_query_expiration allows us to see if the user has authenticated lately/is currently authenticated or if he needs to refresh his token.
And auth._token.local is our actual JWT token, provided by the auth module.
As told above, it is more secure to have a secure cookie than some localStorage, this is also why we are not using it
nuxt.config.js
auth: {
localStorage: false, // REALLY not secure, so nah
...
}
You can just use localStorage and implement it yourself e.g.:
saveToken(state, token) {
localStorage.setItem("authToken", token);
state.token= token
},
saveUser(state, payload) {
localStorage.setItem("authUser", payload);
state.user=payload;
},
And then retrieving the localStorage when initializing your store you need to do something like this:
export const state = () => {
const localUser = localStorage.getItem("authToken")
const localToken = localStorage.getItem("authUser")
let user = {}
let token = ""
if (localUser) user = localUser
if (localToken) token = localToken
return {
user: user,
token: token
}
}
As #mbuechmann pointed out, be aware of the security risk when storing sensitive information in localStorage. Better to use cookies for tokens, but localStorage is the 'simple' solution.
or use a package like nuxt-vuex-localstorage

Axios interceptor is not getting the current user auth token from vuex store

I'm using Axios to send user input to DRF api and it returns an auth token. I'm saving the token in vuex store. In another component. I'm trying to request another api endpoint with Axios with the latest token in the request headers. The issue I'm having is that Axios will either send the request with no token at all or with the token of the previous user that was logged in. It does not get the current token from the vuex store. I used Axios interceptors hoping that would help but it did not.
Login.vue
<script>
export default {
name: 'Login',
data () {
return{
email: null,
password: null,
token: '',
}
},
props: {
},
methods: {
submitForm () {
this.$store.dispatch('loginUser',{
email: this.email,
password: this.password
}).then(() =>{
this.$router.push({ name: 'List' })
}) .catch(err =>{
console.log(err)
})
},
}
}
</script>
store/index.js
import axios from 'axios'
import { createStore } from 'vuex'
export default createStore({
state: {
token: localStorage.getItem('token'),
},
mutations: {
getToken(state, token) {
localStorage.setItem('token', token)
state.token = token
}
},
actions: {
loginUser({ commit }, data){
axios({
method: 'POST',
url: 'http://localhost:8000/auth/login/',
headers: {'Content-Type': 'application/json'},
data: {
'email': data.email,
'password': data.password,
}
}).then(response =>{
commit('getToken', response.data['key'])
})
}
},
modules: {
}
})
List.vue
<script>
import axios from 'axios'
import store from '/src/store'
export default {
name:'List',
data () {
return {
entry: []
}
},
created() {
axios.interceptors.request.use(function (config){
let token = store.state.token
config.headers['Authorization'] = 'Token ' + token;
return config;
})
axios({
method: 'GET',
url: 'http://localhost:8000/journal/',
headers: {'Content-Type': 'application/json'},
}).then(response =>{
this.entry = response.data
}) .catch(err =>{
console.log(err)
})
}
}
</script>
I thought the point of the interceptor was to get the token before actually making the get request, but it does not seem to be doing that.
Not exactly sure why this works but rewriting my loginUser action like this solves my issue.
actions: {
loginUser({ commit }, data){
return new Promise ((resolve, reject) => {
axios({
method: 'POST',
url: 'http://localhost:8000/auth/login/',
headers: {'Content-Type': 'application/json'},
data: {
'email': data.email,
'password': data.password,
}
}).then(response =>{
commit('getToken', response.data['key'])
resolve()
}).catch(err => {
reject(err)
})
})
}
},
I think it's because return new Promise basically interrupts the the initial promise in Login.vue making sure the client doesn't make an api request without the correct token from the server but I'm not sure.

NextJs/ Apollo Client/ NextAuth issue setting authorization Bearer Token to headers correctly

I cannot correctly set my jwt token from my cookie to my Headers for an authenticaed gql request using apollo client.
I believe the problem is on my withApollo.js file, the one that wraps the App component on _app.js. The format of this file is based off of the wes bos advanced react nextjs graphql course. What happens is that nextauth saves the JWT as a cookie, and I can then grab the JWT from that cookie using a custom regex function. Then I try to set this token value to the authorization bearer header. The problem is that on the first load of a page with a gql query needing a jwt token, I get the error "Cannot read property 'cookie' of undefined". But, if I hit browser refresh, then suddenly it works and the token was successfully set to the header.
Some research led me to adding a setcontext link and so that's where I try to perform this operation. I tried to async await setting the token value but that doesn't seem to have helped. It just seems like the headers don't want to get set until on the refresh.
lib/withData.js
import { ApolloClient, ApolloLink, InMemoryCache } from '#apollo/client';
import { onError } from '#apollo/link-error';
import { getDataFromTree } from '#apollo/react-ssr';
import { createUploadLink } from 'apollo-upload-client';
import withApollo from 'next-with-apollo';
import { setContext } from 'apollo-link-context';
import { endpoint, prodEndpoint } from '../config';
import paginationField from './paginationField';
const getCookieValue = (name, cookie) =>
cookie.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`)?.pop() || '';
let token;
function createClient(props) {
const { initialState, headers, ctx } = props;
console.log({ headers });
// console.log({ ctx });
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError)
console.log(
`[Network error]: ${networkError}. Backend is unreachable. Is it running?`
);
}),
setContext(async (request, previousContext) => {
token = await getCookieValue('token', headers.cookie);
return {
headers: {
authorization: token ? `Bearer ${token}` : '',
},
};
}),
createUploadLink({
uri: process.env.NODE_ENV === 'development' ? endpoint : prodEndpoint,
fetchOptions: {
credentials: 'include',
},
headers,
}),
]),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// TODO: We will add this together!
// allProducts: paginationField(),
},
},
},
}).restore(initialState || {}),
});
}
export default withApollo(createClient, { getDataFromTree });
page/_app.js
import { ApolloProvider } from '#apollo/client';
import NProgress from 'nprogress';
import Router from 'next/router';
import { Provider, getSession } from 'next-auth/client';
import { CookiesProvider } from 'react-cookie';
import nookies, { parseCookies } from 'nookies';
import Page from '../components/Page';
import '../components/styles/nprogress.css';
import withData from '../lib/withData';
Router.events.on('routeChangeStart', () => NProgress.start());
Router.events.on('routeChangeComplete', () => NProgress.done());
Router.events.on('routeChangeError', () => NProgress.done());
function MyApp({ Component, pageProps, apollo, user }) {
return (
<Provider session={pageProps.session}>
<ApolloProvider client={apollo}>
<Page>
<Component {...pageProps} {...user} />
</Page>
</ApolloProvider>
</Provider>
);
}
MyApp.getInitialProps = async function ({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
pageProps.query = ctx.query;
const user = {};
const { req } = ctx;
const session = await getSession({ req });
if (session) {
user.email = session.user.email;
user.id = session.user.id;
user.isUser = !!session;
// Set
nookies.set(ctx, 'token', session.accessToken, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
}
return {
pageProps,
user: user || null,
};
};
export default withData(MyApp);
api/auth/[...nextAuth.js]
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
import axios from 'axios';
const providers = [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
Providers.Credentials({
name: 'Credentials',
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'jsmith' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
const user = await axios
.post('http://localhost:1337/auth/local', {
identifier: credentials.username,
password: credentials.password,
})
.then((res) => {
res.data.user.token = res.data.jwt;
return res.data.user;
}) // define user as res.data.user (will be referenced in callbacks)
.catch((error) => {
console.log('An error occurred:', error);
});
if (user) {
return user;
}
return null;
},
}),
];
const callbacks = {
// Getting the JWT token from API response
async jwt(token, user, account, profile, isNewUser) {
// WRITE TO TOKEN (from above sources)
if (user) {
const provider = account.provider || user.provider || null;
let response;
let data;
switch (provider) {
case 'google':
response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/google/callback?access_token=${account?.accessToken}`
);
data = await response.json();
if (data) {
token.accessToken = data.jwt;
token.id = data.user._id;
} else {
console.log('ERROR No data');
}
break;
case 'local':
response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/local/callback?access_token=${account?.accessToken}`
);
data = await response.json();
token.accessToken = user.token;
token.id = user.id;
break;
default:
console.log(`ERROR: Provider value is ${provider}`);
break;
}
}
return token;
},
async session(session, token) {
// WRITE TO SESSION (from token)
// console.log(token);
session.accessToken = token.accessToken;
session.user.id = token.id;
return session;
},
redirect: async (url, baseUrl) => baseUrl,
};
const sessionPreferences = {
session: {
jwt: true,
},
};
const options = {
providers,
callbacks,
sessionPreferences,
};
export default (req, res) => NextAuth(req, res, options);

Permission denied after page refresh in vuejs?

I'm trying to implement authentication in vuejs 3. I'm django and django as a backend and simple jwt for generating token. Every things is working fine. Generated token are in this format.
And here is my auth store , by the way I'm using axios and vuex
import axios from "axios";
import { API_ENDPOINTS } from "../../constants/API";
const authStore = {
state: {
status: "",
access_token: localStorage.getItem("access_token") || "",
refresh_token: localStorage.getItem("refresh_token") || "",
},
mutations: {
auth_request(state) {
state.status = "loading";
},
auth_success(state, access_token, refresh_token, user) {
state.status = "success";
state.access_token = access_token;
state.refresh_token = refresh_token;
state.user = user;
},
auth_error(state) {
state.status = "error";
},
logout(state) {
state.status = "";
state.access_token = "";
state.refresh_token = "";
},
},
actions: {
login({ commit }, user) {
return new Promise((resolve, reject) => {
commit("auth_request");
axios({
url: API_ENDPOINTS.CREATE_TOKEN,
data: user,
method: "POST",
})
.then((resp) => {
console.log(resp);
const access_token = resp.data.access;
const refresh_token = resp.data.refresh;
const user = resp.data.user;
localStorage.setItem("access_token", access_token);
localStorage.setItem("refresh_token", refresh_token);
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${access_token}`;
commit("auth_success", access_token, refresh_token, user);
resolve(resp);
})
.catch((err) => {
commit("auth_error");
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
reject(err);
});
});
},
logout({ commit }) {
// eslint-disable-next-line no-unused-vars
return new Promise((resolve, reject) => {
commit("logout");
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
delete axios.defaults.headers.common["Authorization"];
resolve();
});
},
},
getters: {
isLoggedIn: (state) => !!state.access_token,
authStatus: (state) => state.status,
},
};
export default authStore;
Above code is working fine, only issue is that whenever I refresh my page, I can't able to perform any operation, Like get or post.
Even token is available in localstorage.
Note:-
I think I'm missing some things like refresh token, I think i need to use refresh token, but I've no any idea that how can i use refresh token if refresh token is problem.
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${access_token}`;
This code is noly called when user login, so maybe you should call it when the token is availble at localstorage.