Advertisement
Guest User

Express+VueJS Auth Code Excerpts

a guest
Feb 2nd, 2020
224
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. ////////// Express Server Backend //////////
  2.  
  3. // server.js (EXCERPT)
  4.  
  5. const express = require("express");
  6. const bodyParser = require('body-parser');
  7. var cookieParser = require('cookie-parser');
  8. const authRouter = require('./routes/auth.js');
  9.  
  10. app.use(bodyParser.urlencoded({ extended: false }));
  11. app.use(bodyParser.json());
  12. app.use(cookieParser());
  13.  
  14. // Set up auth routes
  15. app.use(authRouter);
  16.  
  17. var server = require('http').createServer(app);
  18.  
  19. server.listen(port, () => {
  20.     console.log(`${new Date()} Server started on ${port}`);
  21. });
  22.  
  23.  
  24.  
  25. // routes/auth.js (FULL FILE (minus the ldap part commented below))
  26.  
  27. const express = require('express');
  28. const router = express.Router();
  29. const jwt = require('jsonwebtoken');
  30. //const bcrypt = require('bcryptjs');
  31. const uuidv1 = require('uuid/v1');
  32.  
  33. // These are sqlite database methods defined in user.js, not including here as it will be dependent on how you are storing user data on the server side
  34. const { findUserByEmail, findUserByUserid, createUser } = require('../db/user.js');
  35.  
  36. const SECRET_KEY = process.env.SOME_LONG_UNGUESSABLE_STRING;
  37.  
  38. // Auth API
  39.  
  40. const authViaLdaps = async (userid, password, cb) => {
  41.     let authResult = false;
  42.  
  43.     // Doing an ldap test auth call, if the user/password is valid, it returns true, otherwise false for failed auth attempt
  44.     // Code not included because it's rather specific to my project
  45.  
  46.     return authResult;
  47. }
  48.  
  49. const createSplitAccessToken = (user, expiresIn = '1h') => {
  50.     const accessToken = jwt.sign(
  51.         { ...user },
  52.         SECRET_KEY,
  53.         {
  54.             expiresIn,
  55.             jwtid: uuidv1()
  56.         }
  57.     );
  58.     // Split jwt into two parts so we can send it as two separate cookies
  59.     const arr = accessToken.split(/\./);
  60.     return {
  61.         hpayload: `${arr[0]}.${arr[1]}`,
  62.         signature: arr[2]
  63.     };
  64. }
  65.  
  66. router.post('/api/users', (req, res) => {
  67.     const email = req.body.email;
  68.     const userid = req.body.userid;
  69.    
  70.     let method, param;
  71.     if (email) {
  72.         method = findUserByEmail;
  73.         param = email;
  74.     } else if (userid) {
  75.         method = findUserByUserid;
  76.         param = userid;
  77.     } else {
  78.         res.status(400).send({
  79.             "user_found": false
  80.         });
  81.     }
  82.  
  83.     method(param, (err, user) => {
  84.         if (err) return res.status(500).send('Server error!');
  85.         found = user ? true : false;
  86.  
  87.         res.status(200).send({
  88.             "user_found": found
  89.         });
  90.     });
  91. });
  92.  
  93. router.post('/api/register', async (req, res) => {
  94.  
  95.     const userid = req.body.userid;
  96.     const name = req.body.name;
  97.     const email = req.body.email;
  98.     const password = req.body.password;
  99.  
  100.     // Not using bcrypt with a saved pw in database anymore, using ldap
  101.     // const password = bcrypt.hashSync(req.body.password);
  102.    
  103.     const result = await authViaLdaps(userid, password);
  104.     if (!result) {
  105.         return res.status(401).send('Invalid password!');
  106.     }
  107.  
  108.     // Check if the user already exists
  109.     findUserByUserid(userid, async (err, user) => {
  110.         // if (err) {
  111.             // console.error(`Error when trying to find user by id: ${err}`);
  112.             // return res.status(500).send('Server error!');
  113.         // }
  114.         if (user) {
  115.             console.error(`User already exists and attempted to re-register: ${userid}`);
  116.             return res.status(405).send('User already registered!');
  117.         } else {
  118.             // userid, name, email, registeredTs, lastAccessTs
  119.             createUser([ userid, name, email, new Date().convertDateToLogDate(), new Date().convertDateToLogDate() ], (err) => {
  120.                 if (err) return res.status(500).send("Server error!");
  121.  
  122.                 res.status(200).send({ registrationSuccessful: true });
  123.                 // Client should redirect to login
  124.             });
  125.         }
  126.     });
  127.  
  128. });
  129.  
  130. const hour = 3600000;
  131. const setTokenCookies = (res, user, rememberMe) => {
  132.     const splitAccessToken = createSplitAccessToken(user, rememberMe ? '7d' : '1hr');
  133.     const expireDate = rememberMe ? new Date(new Date().getTime() + hour * 24 * 7) : new Date(new Date().getTime() + hour);
  134.     res.cookie('hpayloadCookie', splitAccessToken.hpayload, {
  135.         // secure: true, // Can't remember why I disabled this setting
  136.         sameSite: true,
  137.         expires: expireDate
  138.     });
  139.     res.cookie('signatureCookie', splitAccessToken.signature, {
  140.         httpOnly: true,
  141.         // secure:true,
  142.         sameSite: true,
  143.         expires: new Date('2038-01-19 04:14:07') // Distant future
  144.     });
  145. };
  146.  
  147. router.post('/api/login', (req, res) => {
  148.     const userid = req.body.userid;
  149.     const password = req.body.password;
  150.     const rememberMe = req.body.rememberMe;
  151.  
  152.     findUserByUserid(userid, async (err, user) => {
  153.         if (err) {
  154.             console.error(`Error when trying to find user by id: ${err}`);
  155.             return res.status(500).send('Server error!');
  156.         }
  157.         if (!user) {
  158.             console.error(`User not found on attempted login: ${userid}`);
  159.             return res.status(404).send('User not found!');
  160.         }
  161.  
  162.         // const result = bcrypt.compareSync(password, user.password);
  163.         const result = await authViaLdaps(userid, password);
  164.  
  165.         if (!result) {
  166.             console.error(`Ldap password not valid for user: ${userid}`);
  167.             return res.status(401).send('Password not valid!');
  168.         }
  169.         console.log(`Login for ${userid} successful!`);
  170.  
  171.         setTokenCookies(res, user, rememberMe);
  172.  
  173.         res.status(200).send('Login successful!');
  174.     });
  175. });
  176.  
  177. // Verify the token
  178. function verifyToken(hpayloadCookie, signatureCookie)
  179. {
  180.   return jwt.verify(`${hpayloadCookie}.${signatureCookie}`, SECRET_KEY);
  181. }
  182.  
  183. const verifyCookies = (req, res, next) =>
  184. {
  185.     console.log(`/api/verify called`);
  186.  
  187.     const hpayloadCookie = req.cookies.hpayloadCookie;
  188.     const signatureCookie = req.cookies.signatureCookie;
  189.  
  190.     if (!hpayloadCookie || !signatureCookie) {
  191.         console.error(`Cookies not found!`);
  192.         const status = 401;
  193.         const message = 'Unauthorized';
  194.         res.status(status).json({ status, message });
  195.     }
  196.  
  197.     try {
  198.         // If the verify fails, it will throw an error
  199.         const decoded = verifyToken(hpayloadCookie, signatureCookie);
  200.  
  201.         const userid = decoded.userid;
  202.         const exp = new Date(decoded.exp * 1000).getTime();
  203.         const now = new Date().getTime();
  204.         if (exp <= now) {
  205.             throw new Error ('Cookie is expired');
  206.         }
  207.  
  208.         next();
  209.  
  210.     } catch (err) {
  211.         const status = 401;
  212.         const message = 'Unauthorized';
  213.         console.error(`Cookie authentication was NOT successful! ${err}`);
  214.         res.status(status).json({ status, message });
  215.     }
  216. }
  217.  
  218. router.get('/api/verify', verifyCookies, (req, res, next) => {
  219.     // console.log(`here01`);
  220.     const status = 200;
  221.     const message = 'Cookie authentication successful!';
  222.     console.log(message);
  223.     res.status(status).json({ status, message });
  224. });
  225.  
  226. // END AUTH API
  227.  
  228. module.exports = router;
  229.  
  230.  
  231.  
  232.  
  233.  
  234. ////////// VueJS Client Website //////////
  235.  
  236. // main.js (EXCERPT)
  237.  
  238. import axios from 'axios';
  239. axios.defaults.withCredentials = true; // Required to send cookies and receive via Axios
  240.  
  241. // utils/cookieUtilities.js (FULL FILE)
  242.  
  243. export function getCookie(cname) {
  244.     var name = cname + '=';
  245.     var decodedCookie = decodeURIComponent(document.cookie);
  246.     var ca = decodedCookie.split(';');
  247.     for (var i = 0; i < ca.length; i++) {
  248.         var c = ca[ i ];
  249.         while (c.charAt(0) === ' ') {
  250.             c = c.substring(1);
  251.         }
  252.         if (c.indexOf(name) === 0) {
  253.             return c.substring(name.length, c.length);
  254.         }
  255.     }
  256.     return '';
  257. }
  258.  
  259. export function decodeHpayloadCookie(hpayloadCookieString) {
  260.     const arr = hpayloadCookieString.split('.');
  261.     const header = JSON.parse(window.atob(arr[ 0 ]));
  262.     const payload = JSON.parse(window.atob(arr[ 1 ]));
  263.     return { header, payload };
  264. }
  265.  
  266. export function getPayload() {
  267.     const hpayloadCookieString = getCookie('hpayloadCookie');
  268.     if (hpayloadCookieString === '') {
  269.         return undefined;
  270.     }
  271.     const { payload } = decodeHpayloadCookie(hpayloadCookieString);
  272.     return payload;
  273. }
  274.  
  275.  
  276.  
  277. // store.js (EXCERPT)
  278.  
  279. import Vue from 'vue';
  280. import Vuex from 'vuex';
  281. import axios from 'axios';
  282.  
  283. import { getPayload } from '@/utils/cookieUtilities';
  284.  
  285. Vue.use(Vuex);
  286.  
  287. export default new Vuex.Store({
  288.     state: {
  289.         tokenPayload: null,
  290.         logoutHandler: null,
  291.         loginStatus: null      
  292.     },
  293.     getters: {
  294.         isAuthenticated(state) {
  295.             // eslint-disable-next-line
  296.             return state.tokenPayload ? true : false;
  297.         }
  298.     },
  299.     mutations: {
  300.         setAuthData(state, tokenPayload) {
  301.             state.tokenPayload = tokenPayload;
  302.         },
  303.         clearAuthData(state) {
  304.             state.tokenPayload = null;
  305.         },
  306.         setLogoutHandler(state, logoutHandler) {
  307.             state.logoutHandler = logoutHandler;
  308.         },
  309.         setLoginStatus(state, loginStatus) {
  310.             state.loginStatus = loginStatus;
  311.         }
  312.     },
  313.     actions: {
  314.         setLogoutTimer({ state, dispatch }, msToExpire) {
  315.             if (!msToExpire) {
  316.                 msToExpire = new Date(state.tokenPayload.exp * 1000).getTime() - new Date().getTime();
  317.             }
  318.             // timeout handler
  319.             const logoutHandler = setTimeout(() => {
  320.                 dispatch('logout');
  321.             }, msToExpire);
  322.             this.commit('setLogoutHandler', logoutHandler);
  323.         },
  324.         register({ commit, dispatch }, authData) {
  325.             commit('setLoginStatus', 'Authenticating');
  326.             axios
  327.                 .post('/api/register', {
  328.                     userid: authData.userid,
  329.                     email: authData.email,
  330.                     password: authData.password,
  331.                     name: authData.name
  332.                 })
  333.                 .then(res => {
  334.                     // console.log(JSON.stringify(res, undefined, 2));
  335.  
  336.                     if (res.data.registrationSuccessful === true) {
  337.                         commit('setLoginStatus', 'Successful');
  338.                         console.log(`Registration successful!`);
  339.                         router.replace('/login');
  340.                     } else {
  341.                         console.error(`Unknown login failure!`);
  342.                         commit('setLoginStatus', 'Unknown error');
  343.                     }
  344.                 })
  345.                 .catch(error => {
  346.                     if (error.response) {
  347.                         const res = error.response;
  348.                         console.error(`Registration failed!`);
  349.                         if (res.status === 401) {
  350.                             commit('setLoginStatus', 'Invalid password');
  351.                         } else if (res.status === 405) {
  352.                             commit('setLoginStatus', 'User already registered');
  353.                         } else if (res.status === 500) {
  354.                             commit('setLoginStatus', 'Server error');
  355.                         } else {
  356.                             commit('setLoginStatus', 'Unknown error');
  357.                         }
  358.                     } else {
  359.                         commit('setLoginStatus', 'Communication error');
  360.                         console.error(error);
  361.                     }
  362.                 });
  363.         },
  364.         login({ commit, state, dispatch }, authData) {
  365.             commit('setLoginStatus', 'Authenticating');
  366.             axios
  367.                 .post('/api/login', {
  368.                     userid: authData.userid,
  369.                     password: authData.password,
  370.                     rememberMe: authData.rememberMe
  371.                 })
  372.                 .then(res => {
  373.                     if (res.status === 200) {
  374.                         commit('setLoginStatus', 'Successful');
  375.                         const payload = getPayload();
  376.                         commit('setAuthData', payload);
  377.                         dispatch('getAvailableCriteria');
  378.                         dispatch('setLogoutTimer');
  379.                         console.log(state.toRoute); // NOTE I didn't include toRoute in the store in this example, I'm basically saving the route in my navigation guards when a user tries to access a URL, if you need similar logic I can send more details, just ask
  380.                         if (state.toRoute) {
  381.                             // This option is for cases where a token-bearing user tries to navigate directly to a subpath rather than using the UI links to get there
  382.                             router.replace({ path: state.toRoute.path, query: state.toRoute.query });
  383.                         } else {
  384.                             router.replace('/dashboard');
  385.                         }
  386.                     } else {
  387.                         console.error(`Unknown login failure!`);
  388.                         commit('setLoginStatus', 'Unknown error');
  389.                     }
  390.                 })
  391.                 .catch(error => {
  392.                     if (error.response) {
  393.                         const res = error.response;
  394.                         if (res.status === 401) {
  395.                             console.error(`Login attempt failed due to invalid password!`);
  396.                             commit('setLoginStatus', 'Invalid password');
  397.                         } else if (res.status === 404) {
  398.                             console.error(`Login attempt failed due to user not found!`);
  399.                             commit('setLoginStatus', 'User not found');
  400.                         } else if (res.status === 500) {
  401.                             console.error(`Login attempt failed due to server error when looking up user!`);
  402.                             commit('setLoginStatus', 'Server error');
  403.                         }
  404.                     } else {
  405.                         commit('setLoginStatus', 'Communication error');
  406.                         console.error(error);
  407.                     }
  408.                 });
  409.         },
  410.         tryAutoLogin({ commit, state, dispatch }) {
  411.             const payload = getPayload();
  412.             if (!payload) {
  413.                 return;
  414.             }
  415.  
  416.             axios
  417.                 .get('/api/verify')
  418.                 .then(res => {
  419.                     // console.log(res);
  420.                     if (res.status === 200) {
  421.                         console.log(`Auto cookie login successful!`);
  422.                         commit('setAuthData', payload);
  423.                         dispatch('getAvailableCriteria');
  424.                         dispatch('setLogoutTimer');
  425.                         if (state.toRoute) {
  426.                             // console.log(state.toRoute);
  427.                             // console.log(router.currentRoute);
  428.                             // This option is for cases where a token-bearing user tries to navigate directly to a subpath rather than using the UI links to get there
  429.                             // This additional if check is needed to prevent router errors when trying to replace the curr path with the same path on auto login success
  430.                             if (state.toRoute.path !== router.currentRoute.path) {
  431.                                 router.replace({ path: state.toRoute.path, query: state.toRoute.query });
  432.                             }
  433.                         } else {
  434.                             router.replace('/dashboard');
  435.                         }
  436.                     } else {
  437.                         console.warn(`Auto login failed! Response status: ${res.status}`);
  438.                     }
  439.                 })
  440.                 .catch(error => {
  441.                     console.warn(`Auto login failed! ${error}`);
  442.                 });
  443.         },
  444.         clearAuthData({ commit }) {
  445.             console.log(`Clearing hpayloadCookie`);
  446.             document.cookie = 'hpayloadCookie= ; expires = Thu, 01 Jan 1970 00:00:00 GMT';
  447.             commit('clearAuthData');
  448.         },
  449.         logout({ state, dispatch }) {
  450.             dispatch('clearAuthData');
  451.             if (state.logoutHandler) {
  452.                 // console.log(`Clearing logout timeout handler`);
  453.                 clearTimeout(state.logoutHandler);
  454.             }
  455.             router.replace('/login');
  456.         }
  457.     }
  458. });
  459.  
  460.  
  461.  
  462. // Example in random Vue component, say you want to get the userid from the token or check if authenticated
  463.     computed: {
  464.         isAuthenticated() {
  465.             return this.$store.getters.isAuthenticated;
  466.         },
  467.         userid() {
  468.             return this.$store.state.tokenPayload.userid;
  469.         }
  470.     }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement