Laravel & Vue.js Authentication - javascript

In my vue.js application I've a login system. My main.js looks like this:
import Vue from 'vue';
import NProgress from 'nprogress';
import Resource from 'vue-resource';
import Router from 'vue-router';
import App from './App.vue';
import Login from './components/Authentication/Login.vue';
import auth from './Services/Authentication/Auth';
Vue.use(Router);
Vue.use(Resource);
auth.checkAuth();
export var router = new Router({
history: true
});
router.map({
'/': {
name: 'Login',
component: Login,
guest: true
}
});
router.beforeEach((transition) => {
if (transition.to.auth && !auth.user.authenticated) {
transition.redirect('/login');
} else if (transition.to.guest && auth.user.authenticated) {
transition.redirect('/');
} else {
transition.next();
}
});
Vue.http.interceptors.push((request, next) => {
NProgress.start();
const token = auth.getToken();
request.headers['Authorization'] = 'Bearer ' + token;
request.headers['X-CSRF-TOKEN'] = document.querySelector('meta[name="token"]').content;
request.respondWith(request.body);
next((response) => {
NProgress.done();
if (response.status == 404) {
router.go('/');
} else if (response.status == 401 && response.data.refreshed_token) {
// If you received 401 "Unauthorized" response
// with a refreshed_token in the payload,
// this means you've got to refresh your token
auth.setToken(response.data.refreshed_token);
}
return response;
});
});
So on every request I check the user auth.checkAuth(); that function looks like this (Auth.js):
checkAuth () {
var token || localStorage.getItem('jwt-token');
if (!token) {
return false;
}
Vue.http.get('/api/me')
.then(({data}) => {
this.user.id = data.id;
this.user.name = data.name;
this.user.email = data.email;
this.user.role = data.role;
this.user.authenticated = true;
}, ({data}) => {
router.go('/');
});
}
So my problem is that in my router.beforeEach -> auth.user.authenticated I check if the user is authenticated. But because the promise from auth.checkAuth(); is not returned so auth.user.authenticated is always false!
How can I fix this problem?
Would be very helpful!

For future users having the same problem
Vue.http.interceptors.push((request, next) => {
request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken);
next((response) => {
if(response.status == 401 ) {//or add any error code you want here
//Do another request for some endpoint here to get a fresh token and override your token variable
}
});
});

Related

GET request error 404 (not found) using axios

I have a problem with my nextjs pizzashop application. When i run the app on my computer i get this error :
The error goes away after the page refreshes.
This is my index.js
import axios from "axios";
import Head from "next/head";
import { useState } from "react";
import Add from "../components/Add";
import AddButton from "../components/AddButton";
import Featured from "../components/Featured";
import PizzaList from "../components/PizzaList";
import styles from "../styles/Home.module.css";
export default function Home({ pizzaList }) {
const [close, setClose] = useState(true);
return (
<div className={styles.container}>
<Head>
<title>Pizza Restaurant in New york</title>
<meta name="description" content="Best pizza shop in town" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Featured />
{<AddButton setClose={setClose} style={{ display: "none" }} />}
<PizzaList pizzaList={pizzaList} />
{!close && <Add setClose={setClose} />}
</div>
);
}
export const getServerSideProps = async (ctx) => {
const myCookie = ctx.req?.cookies || "";
let admin = false;
if (myCookie.token === process.env.TOKEN) {
admin = true;
}
const res = await axios.get("http://localhost:3000/api/products");
return {
props: {
pizzaList: res.data,
admin,
},
};
};
When i upload the application to vercel i get this error :
This is the error I'm getting in vercel server runtime
This is my /api/products/[id].js file code
import dbConnect from "../../../util/mongo";
import Product from "../../../models/Product";
export default async function handler(req, res) {
const {
method,
query: { id },
cookies,
} = req;
const token = cookies.token;
await dbConnect();
if (method === "GET") {
try {
const product = await Product.findById(id);
res.status(200).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "PUT") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
const product = await Product.findByIdAndUpdate(id, req.body, {
new: true,
});
res.status(200).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "DELETE") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
await Product.findByIdAndDelete(id);
res.status(200).json("The product has been deleted!");
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
}
This is my /api/products/index.js file code
import dbConnect from "../../../util/mongo";
import Product from "../../../models/Product";
export default async function handler(req, res) {
const { method, cookies } = req;
const token = cookies.token;
await dbConnect();
if (method === "GET") {
try {
const products = await Product.find();
res.status(200).json(products);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
if (method === "POST") {
if (!token || token !== process.env.token) {
return res.status(401).json("Not authenticated!");
}
try {
const product = await Product.create(req.body);
res.status(201).json(product);
} catch (err) {
console.log(err.response);
res.status(500).json(err);
}
}
}
I suspect the error is coming from my get request but i cannot diagnose why. Anyone have any idea?
I am assuming the server side application can be set up as separate deployment. In that case, you can simply follow something similar to what is given here: https://vercel.com/guides/handling-node-request-body
Instead of sending a request to http://localhost:3000/api/products, you can use the deployment url for the server side code like http://some-deployment-url/api/products to access the data you require.

How to unprotect route in next js middleware

so I want to unprotect the login page and here is my folder structure:
here is my middleware:
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
if (req.pathname === "login") {
return NextResponse.next();
}
const token = req.cookies.token;
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
so how do I make it so the middleware does not apply to login.js.
Edit: it now returns [webpack.cache.PackFileCacheStrategy] Caching failed for pack: Error: Unable to snapshot resolve dependencies
code for this project is here
so I solved the error I was not getting the pathname from req.nextUrl and here is the correct code:
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
const { pathname } = req.nextUrl;
const token = req.cookies.token;
if (pathname == "/login" && !token) {
return NextResponse.next();
}
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
You can check if the request is for the /login page itself, and bail early.
Checkout all properties on NextRequest that are available.
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req, ev) {
if( req.pathname === 'login'){
return NextResponse.next();
}
const token = req.cookies.token;
if (!token) {
return NextResponse.redirect("/login");
}
return NextResponse.next();
}
The Accepted answer helped me a lot. I'm adding my solution because I had to make some modifications.
I used [...nextauth].js file with jwt and session callbacks and when I used the solution from accepted answer, I was getting ERR_TOO_MANY_REQUESTS cycle. My solution was to add a few more URLs:
import { NextResponse } from "next/server";
import { getToken } from 'next-auth/jwt';
export default async function middleware(req, ev) {
const { pathname } = req.nextUrl;
const token = await getToken({ req, secret: process.env.JWT_SECRET });
if ((pathname == "/api/auth/signin" || pathname == "/favicon.ico" || pathname == "/api/auth/callback/credentials") && !token) {
return NextResponse.next();
}
if (!token) {
return NextResponse.rewrite(new URL('/api/auth/signin', req.url));
}
return NextResponse.next();
}

Authorizing the user

I am having a slight problem with authorizing the admin.
The backend code works, but i have got problems with requesting the admin route and authenticating the logged in user. First thing i have tried was to put the isAdmin value in the cookies, but it wasnt secure. Then i tried to verify the admin with cookies, i used cookie.get() to get the token. But it was not a success.
code Authorization:
const isAdmin = async (req, res, next) => {
if (!req.user.isAdmin) {
res.status(401).send({ msg: "Not an authorized admin" });
} else {
res.send(req.user.isAdmin);
// const token = req.header("auth-token");
// const verified = verify(token, process.env.SECRET);
// req.user = verified;
// next();
}
next();
};
code Admin route:
router.get("/adminPanel", isAuth, isAdmin, (req, res) => {});
code Login page:
const handleSubmit = e => {
e.preventDefault();
Axios.post("http://localhost:5000/users/login", {
email,
password,
})
.then(response => {
cookie.set("token", response.data.token, {
expires: 1,
});
setUser({
token: response.data.token,
});
if (response.data.isAdmin) {
alert("admin");
} else {
alert("not an admin");
}
// console.log(response.data.token);
// console.log(response.data.isAdmin);
})
.catch(err => {
console.log(err);
});
};
code Admin page:
import React, { useContext, useEffect, useState } from "react";
import Axios from "axios";
import { userContext } from "../../App";
export default function Home() {
const [user, setUser] = useContext(userContext);
const [content, setContent] = useState("login plz to display the content");
useEffect(() => {
// Axios.get("http://localhost:5000/users/adminPanel").then(response =>
// console.log(response.data),
// );
// async function fetchAdmin() {const result = await
Axios.get("http://localhost:5000/users/adminPanel", {
headers: {
Authorization: `Bearer ${user.isAdmin}`,
},
});
// }
// fetchAdmin();
// async function fetchProtected() {
// const result = await (
// await fetch("http://localhost:5000/users/adminPanel", {
// method: "GET",
// headers: {
// "Content-Type": "application/json",
// authorization: `Bearer ${user.token}`,
// },
// })
// ).json();
// if (result.isAdmin) setContent("Admin");
// }
// fetchProtected();
}, [user]);
return `${content}`;
}
Getting the token from cookies:
const [user, setUser] = useState({});
useEffect(() => {
setUser({ token: cookie.get("token") });
}, []);
console.log(user);
Taking into account your route
router.get("/adminPanel", isAuth, isAdmin, (req, res) => {});
I assume req.user.isAdmin is set in isAuth middleware, so your isAdmin middleware should check that parameter, let it pass if so, or reject it otherwise.
In the isAuth middleware after you validate the user, you should know if is an admin or not, so just set the parameter like this:
const isAuth = async (req, res, next) => {
// other code
req.user.isAdmin = true // put your logic here to reflect the status
next(); // pass the control to next middleware, in your example to isAdmin
}
Finally isAdmin could look like this:
const isAdmin = async (req, res, next) => {
if (!req.user.isAdmin) {
res.status(401).send({ msg: "Not an authorized admin" });
} else {
next();
}
};

want to redirect inside the axios code reacr

axios
.post('http://oud-zerobase.me/api/v1/users/signUp', toSent)
.then((response) => {
if (response.status === 200) {
/**redirect to home */
const authToken = response.data.token;
localStorage.setItem('accessToken', authToken);
console.log('token', authToken);
console.log(response);
/**redirect to home */
} else if (response.status === 400) {
errorMassage = response.statusText;
} else if (response.status === 401) {
/**Unauthorized */
errorMassage = response.statusText;
}
this.setState((prevState) => {
prevState.formErrors.mainError = errorMassage;
return prevState;
});
})
.catch((error) => {
console.log(error.response);
});
here I send the accessToken to a function outside I want to redirect the page when it successful
you can use hooks in react-router-dom to push your route:
import { useHistory } from "react-router-dom";
let history = useHistory();
history.push("/ROUTE_NAME");

How to set authorization header and protect routes in Vuejs and Node

I am using passport-jwt strategy to protect auth users in my app, once I login I am generating a jwt-token now I want to protect my welcome page rout so that user cannot open it without login
So when I login I am creating jwt-token with payload like this
my user.js file
const payload = { email: rows[0].email } // jwy payload
console.log('PAYLOAD')
console.log(payload)
jwt.sign(
payload,
key.secretOrKey, { expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: 'Bearer ' + token,
email
})
})
Now in my passport.js I am doing like this
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = keys.secretOrKey;
passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
let payLoadEmail = jwt_payload.email //payload data what I have passed in creating jwt
console.log("payload email :" + payLoadEmail)
User.fetchLogedInUser(payLoadEmail)
.then(([rows]) => {
if (rows.length > 0) {
return done(null, rows[0].email) // returning data what I need
}
return done(null, false)
})
.catch(err => console.log(err))
}));
Both are working fine.
Now I want to protect my welcome rout so in my router.js file
const express = require('express');
const router = express.Router();
const passport = require('passport')
const UsersCtrl = require('../controllers/users');
router.use('/login', UsersCtrl.login)
router.use('/welcome',passport.authenticate('jwt',{session:false}))
router.use('/logout', UsersCtrl.logout)
module.exports = router;
suppose user types localhost:8080/welcome without login then I want to protect it
So in my store.js file when user logs in I am doing this on login click and I have made a method getAuthUser. I don't know how to I pass this config to protect my welcome file
Here is my full store.js code
import axios from 'axios'
import jwt from 'jsonwebtoken'
function checkTokenValidity(token) { // token validity
if (token) {
const decodedToken = jwt.decode(token)
return decodedToken && (decodedToken.exp * 1000) > new Date().getTime()
}
return false
}
export default {
namespaced: true,
state: {
user: null,
isAuthResolved: false // this I am calling on my login page i am confused where should I call this or not to call this
},
getters: {
authUser(state) {
return state.user
},
isAuthenticated(state) {
return !!state.user
}
},
actions: {
loginWithCredentials({ commit }, userDate) {
return axios.post('/api/v1/users/login', userDate)
.then(res => {
const user = res.data
console.log(user.email)
localStorage.setItem('jwt-token', user.token)
commit('setAuthUser', user)
})
},
logout({ commit }) {
return new Promise((resolve, reject) => {
localStorage.removeItem('jwt-token')
commit('setAuthUser', null)
resolve(true)
})
},
getAuthUser({ commit, getters }) {
const authUser = getters['authUser']
const token = localStorage.getItem('jwt-token')
const isTokenValid = checkTokenValidity(token)
if (authUser && isTokenValid) {
return Promise.resolve(authUser)
}
const config = { // here what to do with this how can I pass this to protect my route
headers: {
'cache-control': 'no-cache',
'Authorization': token
}
}
}
},
mutations: {
setAuthUser(state, user) {
return state.user = user
},
setAuthState(state, authState) {
return state.isAuthResolved = authState
}
}
In my route.js vue file
import Vue from 'vue'
import Router from 'vue-router'
import store from './store'
Vue.use(Router)
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [{
path: '/welcome',
name: 'welcome',
meta: { onlyAuthUser: true },
component: () =>
import ('./views/Welcome.vue'),
}, ]
})
router.beforeEach((to, from, next) => {
store.dispatch('auth/getAuthUser')
.then((authUser) => {
const isAuthenticated = store.getters['auth/isAuthenticated']
if (to.meta.onlyAuthUser) {
if (isAuthenticated) {
next()
} else {
next({ name: 'PageNotAuthenticated' })
}
} else if (to.meta.onlyGuestUser) {
if (isAuthenticated) {
next({ name: 'welcome' })
} else {
next()
}
} else {
next()
}
})
})
export default router
My main problem is I want to protect routes and make the user authenticated using jwt and passport I am getting jwt once I login and want to check once my protected rout is access with out login for backend.
In front end (vue.js) I my store file in action> getAuthUsers I don't know how to pass config to other routes like my welcome.
Not sure if I understood your question entirely because you seem to be implementing route access correctly. You simply need to add routes as an array while the rest of your code remains the same.
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [{
path: '/welcome',
name: 'welcome',
meta: { onlyAuthUser: true },
component: () =>
import ('./views/Welcome.vue'),
},
{
path: '/login',
name: 'Login',
meta: { onlyGuesUser: true },
component: () =>
import ('./views/Login.vue'),
}]
})
For using Authentication: Bearer xxxxxxxx you can modify your axios code to directly use required headers instead of passing it through routes every time. Make a new folder called services and a file called base-api. You can obviously name it however you like, but this is my setup.
import axios from 'axios';
export default () => {
let headers = {
'cache-control': 'no-cache'
};
let accessToken = localStorage.getItem('jwt-token');
if (accessToken && accessToken !== '') {
headers.Authorization = accessToken;
};
return axios.create({
baseURL: 'SECRET_URL_',
headers: headers
});
}
Import this file in your store.js. Replace import axios from 'axios' with import axios from '#src/services/base-api.js. As the file is returning an axios instance you need to access it as axios(). Which means you function would be
return axios().post('/api/v1/users/login', userDate)
.then(res => {
// do whatever
})

Categories