Nuxt middleware to check if user is logged in not working - javascript

I am trying to check if a user is authenticated and redirect them depending on the page they are in. for example if the user is logged in and they try to visit the login or signup page, they should be redirected. I have a middleware for that.
when I log in the user, the authenticateUser action runs and the user is created, when I check my cookies and local storage on the browser, I see that it is set correctly, but when I visit the login page after logging in, it doesn't redirect me.
middleware/altauth.js
export default function (context) {
console.log(context.store.getters('profile/isAuthenticated'))
if (context.store.getters.isAuthenticated) {
context.redirect('/')
}
}
also the token is both saved using Cookies and local storage and is persistence is through this middleware
middleware/checkauth.js
export default function (context) {
if(context.hasOwnProperty('ssrContext')) {
context.store.dispatch('profile/initAuth', context.ssrContext.req);
} else {
context.store.dispatch('profile/initAuth', null);
}
}
and below are the values for my store
import Cookie from 'js-cookie';
export const state = () => ({
token: null,
})
export const mutations = {
setToken(state, token) {
state.token = token
},
clearToken(state) {
state.token = null
}
}
export const actions = {
async authenticateUser(vuexContext, authData) {
let authUrl = 'https://look.herokuapp.com/signup/'
if (authData.isLogin) {
authUrl = 'https://look.herokuapp.com/login/'
}
return this.$axios
.$post(authUrl, authData.form)
.then(data => {
console.log(data);
const token = data.token
vuexContext.commit('setToken', token)
localStorage.setItem("token", token)
Cookie.set('jwt', token);
})
.catch(e => console.log(e))
},
initAuth(vuexContext, req) {
let token
if (req) {
if (!req.headers.cookie) {
return;
}
const jwtCookie = req.headers.cookie
.split(';')
.find(c => c.trim().startsWith('jwt='));
if (!jwtCookie) {
return;
}
token = jwtCookie.split('=')[1];
} else {
token = localStorage.getItem('token');
if (!token) {
return;
}
}
vuexContext.commit('setToken', token);
}
}
export const getters = {
isAuthenticated(state) {
return state.token != null;
},
}
please help, i don't know what the problem can be

Here is a basic but full example for auth system in SSR nuxt
You will need two apis for this, one will return token info with user info, and the other will return user info only.
for example
POST http://example.com/api/auth/authorizations
{
token: 'abcdefghijklmn',
expired_at: 12345678,
user: {
name: 'Tom',
is_admin: true
}
}
// this need authed
GET http://example.com/api/auth/user
{
name: 'Tom',
is_admin: true
}
nuxt.config.js
plugins:[
'~plugins/axios',
],
buildModules: [
'#nuxtjs/axios',
],
router: {
middleware: [
'check-auth'
]
},
./pages/login.vue
<template>
<form #submit.prevent="login">
<input type="text" name="username" v-model="form.username">
<input type="password" name="password" v-model="form.password">
</form>
</template>
<script type="text/javascript">
export default{
data(){
return {
form: {username: '', password: ''}
}
},
methods: {
login(){
this.$axios.post(`/auth/authorizations`, this.form)
.then(({ data }) => {
let { user, token } = data;
this.$store.commit('auth/setToken', token);
this.$store.commit('auth/updateUser', user);
this.$router.push('/');
})
}
}
}
</script>
store/index.js
const cookieFromRequest = (request, key) => {
if (!request.headers.cookie) {
return;
}
const cookie = request.headers.cookie.split(';').find(
c => c.trim().startsWith(`${key}=`)
);
if (cookie) {
return cookie.split('=')[1];
}
}
export const actions = {
nuxtServerInit({ commit, dispatch, route }, { req }){
const token = cookieFromRequest(req, 'token');
if (!!token) {
commit('auth/setToken', token);
}
}
};
middleware/check-auth.js
export default async ({ $axios, store }) => {
const token = store.getters['auth/token'];
if (process.server) {
if (token) {
$axios.defaults.headers.common.Authorization = `Bearer ${token}`;
} else {
delete $axios.defaults.headers.common.Authorization;
}
}
if (!store.getters['auth/check'] && token) {
await store.dispatch('auth/fetchUser');
}
}
store/auth.js
import Cookie from 'js-cookie';
export const state = () => ({
user: null,
token: null
});
export const getters = {
user: state => state.user,
token: state => state.token,
check: state => state.user !== null
};
export const mutations = {
setToken(state, token){
state.token = token;
},
fetchUserSuccess(state, user){
state.user = user;
},
fetchUserFailure(state){
state.user = null;
},
logout(state){
state.token = null;
state.user = null;
},
updateUser(state, { user }){
state.user = user;
}
}
export const actions = {
saveToken({ commit }, { token, remember }){
commit('setToken', token);
Cookie.set('token', token);
},
async fetchUser({ commit }){
try{
const { data } = await this.$axios.get('/auth/user');
commit('fetchUserSuccess', data);
}catch(e){
Cookie.remove('token');
commit('fetchUserFailure');
}
},
updateUser({ commit }, payload){
commit('updateUser', payload);
},
async logout({ commit }){
try{
await this.$axios.delete('/auth/authorizations');
}catch(e){}
Cookie.remove('token');
commit('logout');
}
}
plugins/axios.js
export default ({ $axios, store }) => {
$axios.setBaseURL('http://example.com/api');
const token = store.getters['auth/token'];
if (token) {
$axios.setToken(token, 'Bearer')
}
$axios.onResponseError(error => {
const { status } = error.response || {};
if (status === 401 && store.getters['auth/check']) {
store.commit('auth/logout');
}
else{
return Promise.reject(error);
}
});
}
Then you can do what you want in your middleware, such as check auth
middleware/auth.js
export default function ({ store, redirect }){
if (!store.getters['auth/check']) {
return redirect(`/login`);
}
}

Related

Wrong authentication with Firebase

I have added authorization to my Nuxt app, but something is wrong. When i enter wrong password or email, I am still redirected to the main page of the application, although I have to stay on the authorization page and try to log in again.
Here is my code:
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut
} from 'firebase/auth'
export default {
data() {
return {
snackBar: false,
snackBarText: 'No Error Message',
auth: {
email: '',
password: ''
}
}
},
methods: {
login() {
let that = this
this.$fire.auth.signInWithEmailAndPassword(this.auth.email, this.auth.password)
.catch(function (error) {
console.log(error.message);
that.snackBarText = error.message
that.snackBar = true
// $nuxt.$router.push('/login')
}).then((user) => {
console.log(user);
$nuxt.$router.push('/')
})
}
}
}
middleware:
export default function ({ app, route, redirect }) {
if (route.path !== '/login') {
// we are on the protected route
if (!app.$fire.auth.currentUser) {
// take them to sign in in a page
return redirect('/login')
}
} else if (route.path === '/login') {
if (!app.$fire.auth.currentUser) {
// leave them on the sign in page
} else {
return redirect('/')
}
}
}
store:
const state = () => ({
user: null,
};
const mutations = {
SET_USER(state, user) {
state.user = user
},
}
const actions = {
async onAuthStateChangedAction(context, { authUser, claims }) {
if (!authUser) {
context.commit('SET_USER', null)
this.$router.push({
path: '/login'
})
} else {
const { uid, email } = authUser;
context.commit('SET_USER', {
uid,
email
})
}
}
}
const getters = {
getUser(state) {
return state.user
}
}
export default {
state,
actions,
mutations,
getters,
}
Form for authorization is in component popup, which is sent to page login.vue

How to commit vuex muation form within the same file (in Nuxt)

I followed this tutorial to setup a firebase auth store in vuex (https://www.youtube.com/watch?v=n9cERWIRgMw&list=PL4cUxeGkcC9jveNu1TI0P62Dn9Me1j9tG&index=10)
However, I'm using Nuxt which causes the last step to break.
My file:
import { auth } from "~/plugins/firebase.js";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
} from "firebase/auth";
export const state = () => ({
user: null,
authIsReady: false,
});
export const mutations = {
setUser(state, payload) {
state.user = payload;
console.log("user state changed:", state.user);
},
setAuthIsReady(state, payload) {
state.authIsReady = payload;
},
};
export const actions = {
async signup(context, { email, password }) {
console.log("signup action");
const res = await createUserWithEmailAndPassword(auth, email, password);
if (res) {
context.commit("setUser", res.user);
} else {
throw new Error("could not complete signup");
}
},
async login(context, { email, password }) {
console.log("login action");
const res = await signInWithEmailAndPassword(auth, email, password);
if (res) {
context.commit("setUser", res.user);
} else {
throw new Error("could not complete login");
}
},
async logout(context) {
console.log("logout action");
await signOut(auth);
context.commit("setUser", null);
},
};
const unsub = onAuthStateChanged(auth, (user) => {
store.commit("setAuthIsReady", true);
store.commit("setUser", user);
unsub();
});
Error:
Uncaught (in promise) ReferenceError: store is not defined
at eval (index.js?9101:56:1)
at eval (index-6de4cbb9.js?3d11:2453:1)
How do I commit a mutation from here? I tried loads of things, like this.$store, $store etc.

Verify and refresh JWT access token in using Vue Router and Vuex

I've created simple VueCLI auth module using axios and Vuex.
In store.js I've got all logic for tokens using api from session.js:
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import sessionSerivce from '#/services/session.js'
Vue.use(Vuex)
Vue.use(require('vue-cookies'))
export const store = new Vuex.Store({
state: {
status: '',
accessToken: $cookies.get('accessToken') || '',
refreshToken: $cookies.get('refreshToken') || '',
user: $cookies.get('user') || '',
},
actions: {
login({ commit }, data) {
return new Promise((resolve, reject) => {
commit('auth_request')
sessionSerivce
.logIn(data)
.then((resp) => {
const commitData = {
accessToken: resp.data.access_token,
refreshToken: resp.data.refresh_token,
user: resp.data.user,
}
$cookies.set('accessToken', commitData.accessToken)
$cookies.set('refreshToken', commitData.refreshToken)
$cookies.set('user', JSON.stringify(commitData.user))
axios.defaults.headers.common['Authorization'] =
commitData.accessToken
commit('auth_success', commitData)
resolve(resp)
})
.catch((err) => {
commit('auth_error')
$cookies.remove('accessToken')
$cookies.remove('refreshToken')
$cookies.remove('user')
reject(err)
})
})
},
verifyToken({ commit, state }) {},
register({ commit }, data) {
return new Promise((resolve, reject) => {
commit('auth_request')
sessionSerivce
.register(data)
.then((resp) => {
const commitData = {
accessToken: resp.data.access_token,
refreshToken: resp.data.refresh_token,
user: resp.data.user,
}
$cookies.set('accessToken', commitData.accessToken)
$cookies.set('refreshToken', commitData.refreshToken)
$cookies.set('user', JSON.stringify(commitData.user))
axios.defaults.headers.common['Authorization'] =
commitData.accessToken
commit('auth_success', commitData)
resolve(resp)
})
.catch((err) => {
commit('auth_error')
$cookies.remove('accessToken')
$cookies.remove('refreshToken')
$cookies.remove('user')
reject(err)
})
})
},
logout({ commit }) {
return new Promise((resolve, reject) => {
commit('logout')
$cookies.remove('accessToken')
$cookies.remove('refreshToken')
$cookies.remove('user')
delete axios.defaults.headers.common['Authorization']
resolve()
})
},
},
mutations: {
auth_request(state) {
state.status = 'loading'
},
auth_success(state, commitData) {
state.status = 'success'
state.accessToken = commitData.accessToken
state.refreshToken = commitData.refreshToken
state.user = commitData.user
},
auth_error(state) {
state.status = 'error'
},
refresh_token(state, accessToken) {
state.accessToken = accessToken
},
logout(state) {
state.status = ''
state.accessToken = ''
state.refreshToken = ''
state.user = ''
},
},
getters: {
isLoggedIn: (state) => {
return !!state.accessToken
},
authStatus: (state) => state.status,
},
})
In main.js I use this function to check:
router.beforeEach(async (to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (store.getters.isLoggedIn) {
next()
return
}
next('/login')
} else next()
})
The problem is that code above checks only if access token exists in Vuex. I want to verify using api before any route, that requires auth and if it's not successfully I want to refresh It with api using refresh token. If both are unsuccessful(access and refresh tokens are both invalid) user gonna log out.
Example route which requires auth:
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: {
requiresAuth: true,
},
I've tried code like this:
router.beforeEach(async (to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (store.state.accessToken) {
await store.dispatch('verifyToken')
if (store.getters.isLoggedIn) {
next()
return
}
}
next('/login')
} else next()
})
Action in Vuex:
verifyToken({ commit, state }) {
const accessToken = state.accessToken
const refreshToken = state.accessToken
sessionSerivce
.verifyToken(accessToken)
.then((resp) => {})
.catch((err) => {
sessionSerivce
.refreshToken(refreshToken)
.then((resp) => {
console.log('Refreshuje token')
const accessToken = resp.data.access_token
localStorage.setItem('accessToken', accessToken)
axios.defaults.headers.common['Authorization'] = accessToken
commit('refresh_token', accessToken)
})
.catch((err) => {
commit('logout')
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
delete axios.defaults.headers.common['Authorization']
})
})
},
Note that in code above i used localstorage but i've changed my mind and I'm using cookie, as You can see in previous code.
Unfortunately this code didn't work as expected - if (store.getters.isLoggedIn) { next(); return; } is starting to execute before await store.dispatch('verifyToken') ends, which is bad.
Any ideas?

Cannot initialise Vuex store from localStorage for my Auth in Nuxt

I'm using Nuxt.js and I'm trying to make my own authentication. It works fine but when I refresh the page the state is go back to initial data so I tried to initialise Vuex store from localStorage like this:
export const state = () => ({
status: '',
token: localStorage.getItem('token') || '',
loggedInUser: localStorage.getItem('user') || '',
})
but it give me this error localStorage is not defined but localStorage.setItem works fine in actions
This is the full code:
import axios from 'axios'
export const state = () => ({
status: '',
token: localStorage.getItem('token') || '',
loggedInUser: localStorage.getItem('user') || '',
})
export const getters = {
status (state) {
return state.status
},
authenticated (state) {
return !!state.token
},
token (state) {
return state.token
},
loggedInUser (state) {
return state.loggedInUser
},
}
export const mutations = {
auth_request(state) {
state.status = 'loading'
},
auth_success(state, token) {
state.status = 'success'
state.token = token
},
auth_error(state) {
state.status = 'error'
},
logout(state) {
state.status = ''
state.token = ''
state.loggedInUser = {}
},
auth_success2 (state, loggedInUser) {
state.loggedInUser = Object.assign({}, loggedInUser)
}
}
export const actions = {
login({commit}, data) {
return new Promise((resolve, reject) => {
commit('auth_request')
axios.post('http://127.0.0.1:8000/api/login', data)
.then((res) => {
const loggedInUser = Object.assign({}, res.data.data)
const token = res.data.meta.token
localStorage.setItem('token', token)
localStorage.setItem('user', loggedInUser.name)
axios.defaults.headers.common['Authorization'] = 'Bearer '+ token
commit('auth_success', token)
commit('auth_success2', loggedInUser)
this.$router.push('/')
resolve(res)
})
.catch((error) => {
commit('auth_error')
console.log(error)
reject(error)
})
})
}
}
You didn't include where this error is being thrown, but I'm going to assume it's in your server logs.
What's happening is Nuxt is initializing itself server side, begins to set up the store, and hits the localStorage declaration. The server does not have localstorage, so this will fail.
To get around this, I'd suggest using a plugin, with the .client suffix, and fetch the values from localStorage during the client side initialization:
// the .client suffix is required here to tell nuxt to only run this client side.
// ~/plugins/vuex-init.client.js
export default ({ store }) => {
const token = localStorage.getItem('token') || ''
const loggedInUser = localStorage.getItem('user') || ''
store.commit('setToken', token)
store.commit('setUser', user)
}
If you don't want to do the work yourself, I've used this before and have had great results with it.

NuxtServerInit sets Vuex auth state after reload

I'm setting a basic authentication on a Nuxt project with JWT token and cookies to be parsed by nuxtServerInit function.
On login with email/password, works as intended, setUser mutation is triggered and the appropriate user object is stored in state.auth.user.
On reload, nuxtServerInit will get the jwt token from req.headers.cookies, call the GET method and identify user.Works like a charm.
Problem starts when I hit the /logout endpoint. state.auth.user is set to false and Im effectively logged out... but If I refresh, I'm logged in again with the previous user data. Even if my cookies are properly empty (on below code, both user and cookie are undefined after logout and refresh, as expected)
So I really don't get why is my state.auth.user is back to its initial value...
store/index.js
import Vuex from "vuex";
import auth from "./modules/auth";
import axios from "~/plugins/axios";
const cookieparser = process.server ? require("cookieparser") : undefined;
const END_POINT = "api/users";
const createStore = () => {
return new Vuex.Store({
actions: {
async nuxtServerInit({ commit, dispatch}, { req }) {
let cookie = null;
console.log(req.headers.cookie)
if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie);
try {
cookie = JSON.parse(parsed.auth);
console.log("cookie", cookie)
const {accessToken} = cookie
const config = {
headers: {
Authorization: `Bearer ${accessToken}`
}
}
const response = await axios.get(`${END_POINT}/current`, config)
const user = response.data
console.log("user nuxt server init", user)
await commit('setUser', user)
} catch (err) {
// No valid cookie found
console.log(err);
}
}
}
},
modules: {
auth
}
});
};
export default createStore;
modules/auth.js
import axios from "~/plugins/axios";
const Cookie = process.client ? require("js-cookie") : undefined;
const END_POINT = "api/users";
export default {
state: {
user: null,
errors: {}
},
getters: {
isAuth: state => !!state.user
},
actions: {
login({ commit }, payload) {
axios
.post(`${END_POINT}/login`, payload)
.then(({ data }) => {
const { user, accessToken } = data;
const auth = { accessToken };
Cookie.set("auth", auth);
commit("setUser", user);
})
.catch(e => {
const error = e;
console.log(e);
commit("setError", error);
});
},
logout({ commit }) {
axios
.post(`${END_POINT}/logout`)
.then(({ data }) => {
Cookie.remove("auth");
commit("setUser", false);
})
.catch(e => console.log(e));
},
},
mutations: {
setUser(state, user) {
state.user = user;
},
setError(state, errors) {
state.errors = errors;
}
}
};
The way I logout my user is by creating a mutation called clearToken and commit to it in the action :
State :
token: null,
Mutations :
clearToken(state) {
state.token = null
},
Actions :
logout(context) {
context.commit('clearToken')
Cookie.remove('token')
}
This way, you token state revert back to null.

Categories

Resources