From 6739170e2e517451ac41243ef1b0215442f27527 Mon Sep 17 00:00:00 2001 From: Mikayla Dobson <93477693+innocuous-symmetry@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:33:58 -0600 Subject: [PATCH] jwt auth workflow --- client/package.json | 1 + client/src/App.jsx | 78 ++++++++++++++++++++------------ client/src/pages/Auth.jsx | 45 ++---------------- client/src/pages/Home.jsx | 13 ------ client/src/util/API.js | 12 +++-- client/src/util/axiosInstance.js | 10 ++-- server/loaders/passport.js | 28 +++++------- server/package.json | 2 + server/routes/auth.js | 31 +++++++++++-- server/routes/index.js | 9 ---- server/routes/item.js | 20 +++++--- 11 files changed, 121 insertions(+), 128 deletions(-) diff --git a/client/package.json b/client/package.json index b6f5742..5ef592a 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "axios": "^1.3.0", + "jwt-decode": "^3.1.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/client/src/App.jsx b/client/src/App.jsx index 5b44e06..de95d3c 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,51 +1,73 @@ import { useEffect, useState } from 'react'; -import Home from './pages/Home' +import jwt_decode from "jwt-decode"; import API from './util/API'; -import './App.css' import Auth from './pages/Auth'; +import './App.css' function App() { const [user, setUser] = useState(null); - const [contents, setContents] = useState(); + const [contents, setContents] = useState(<>); - useEffect(() => { - (async() => { - const res = await API.validate(); - console.log(res); + async function handleLogin(info) { + if (!info.email || !info.password) return; - if (res.user) { - setUser(res.user); - } else { - setUser(null); - } - })(); - }, []) + const response = await API.login(info); + console.log(response); - useEffect(() => { - setContents(user ? : ); - }, [user]); + const user = jwt_decode(response.token); + console.log(user); + + localStorage.setItem('user', JSON.stringify(user)); + localStorage.setItem('token', response.token); + } async function handleLogout() { await API.logout(); - setUser(null); + localStorage.removeItem("user"); + localStorage.removeItem("token"); } - async function handleLogin(info) { - const res = await API.login(info); - if (res.data) setUser(res.data); - return; + async function handleRegister(register) { + if (!register.username || !register.email || !register.password) return; + await API.register(register); } - async function handleRegister(info) { - const res = await API.register(info); - console.log(res); - return; - } + useEffect(() => { + let item = localStorage.getItem('user'); + if (item) { + item = JSON.parse(item); + setUser(item.user); + } + }, []) + + useEffect(() => { + let protectedData; + + if (user) { + (async() => { + protectedData = await API.getItems(); + console.log(protectedData); + })(); + } + + setContents( + user ? ( +
+

Welcome, {user.username}!

+
+ +
+ +
+ ) : ( + + ) + ) + }, [user]) return (

Auth Testing

- { contents }
) diff --git a/client/src/pages/Auth.jsx b/client/src/pages/Auth.jsx index ad8eedb..f8b5a5a 100644 --- a/client/src/pages/Auth.jsx +++ b/client/src/pages/Auth.jsx @@ -1,52 +1,17 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import { LoginForm, RegisterForm } from "../components/Form"; function Auth({ handleLogin, handleRegister }) { - const [info, setInfo] = useState({ username: "", email: "", password: "" }); - const [mode, setMode] = useState("login"); - const [form, setForm] = useState(); - const [button, setButton] = useState( - <> - - - - ); - - useEffect(() => { - console.log(mode); - }, [info]); - - function swapForm() { - if (mode === "register") { - setMode("login"); - setForm(); - setButton( - <> - - - - ) - } else { - setMode("register"); - setForm(); - setButton( - <> - - - - ); - } - } + const [info, setInfo] = useState({ email: "", password: "" }); + const [register, setRegister] = useState({ username: "", email: "", password: "" }) return (
- {/* */} - - - {/* */} + +
) } diff --git a/client/src/pages/Home.jsx b/client/src/pages/Home.jsx index e285367..45b31c0 100644 --- a/client/src/pages/Home.jsx +++ b/client/src/pages/Home.jsx @@ -5,19 +5,6 @@ function Home({ user, handleLogout }) { const [items, setItems] = useState(null); console.log(user); - useEffect(() => { - if (user) { - (async() => { - const myItems = await API.getItems(); - - setItems(myItems.map(each => <>{each.id})); - })(); - } else { - setItems(null); - } - - }, []) - async function getStatus() { const res = await API.validate(); console.log(res); diff --git a/client/src/util/API.js b/client/src/util/API.js index d4dbab2..30711c9 100644 --- a/client/src/util/API.js +++ b/client/src/util/API.js @@ -1,5 +1,4 @@ -import axiosInstance from './axiosInstance'; -const _api = axiosInstance(); +import { default as _api } from './axiosInstance'; export default class API { static async validate() { @@ -42,7 +41,14 @@ export default class API { } static async getItems() { - const response = await _api.get('/app/item'); + const token = localStorage.getItem("token"); + + const response = await _api.get('/app/item', { + headers: { + "Content-Type": "application/json", + "Authorization": ("Bearer " + token) + } + }); return Promise.resolve(response.data); } } \ No newline at end of file diff --git a/client/src/util/axiosInstance.js b/client/src/util/axiosInstance.js index eded926..55579a4 100644 --- a/client/src/util/axiosInstance.js +++ b/client/src/util/axiosInstance.js @@ -2,10 +2,6 @@ import axios from 'axios' const apiUrl = import.meta.env.VITE_APIURL; -export default function axiosInstance() { - if (!apiUrl) { - throw new Error("API URL not found"); - } - - return axios.create({ baseURL: apiUrl }); -} \ No newline at end of file +export default axios.create({ + baseURL: "http://localhost:8080" +}) \ No newline at end of file diff --git a/server/loaders/passport.js b/server/loaders/passport.js index 94b7b89..640ad40 100644 --- a/server/loaders/passport.js +++ b/server/loaders/passport.js @@ -1,6 +1,6 @@ const passport = require('passport'); -const { Strategy } = require('passport-local'); -const AuthController = require('../controllers/authController'); +const JwtStrategy = require('passport-jwt').Strategy; +const { ExtractJwt } = require('passport-jwt'); async function passportLoader(app) { app.use(passport.initialize()); @@ -18,24 +18,20 @@ async function passportLoader(app) { }) }) - passport.use(new Strategy({ usernameField: "email", passwordField: "password" }, async (email, password, done) => { - console.log('calling local strategy'); - console.log(email, password); + // config for jwt strategy + let opts = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: 'secret' + } + // jwt strategy + passport.use(new JwtStrategy(opts, async (token, done) => { try { - console.log('before response') - const response = await AuthController.login({ email: email, password: password }); - console.log(response); - - if (response && response.ok) { - return done(null, response.data.data); - } else { - return done(null, false); - } + return done(null, token.user); } catch (error) { - return done(error); + done(error); } - })) + })); return passport; } diff --git a/server/package.json b/server/package.json index 33760e1..0302e86 100644 --- a/server/package.json +++ b/server/package.json @@ -18,7 +18,9 @@ "dotenv": "^16.0.3", "express": "^4.18.2", "express-session": "^1.17.3", + "jsonwebtoken": "^9.0.0", "passport": "^0.6.0", + "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "pg": "^8.9.0" }, diff --git a/server/routes/auth.js b/server/routes/auth.js index 502dce7..a3285f4 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -1,6 +1,9 @@ const AuthController = require('../controllers/authController'); - +const jwt = require('jsonwebtoken'); const router = require('express').Router(); +require('dotenv').config(); + +const secret = process.env.SECRET; async function authRoute(app, passport) { router.post('/register', async (req, res) => { @@ -17,18 +20,36 @@ async function authRoute(app, passport) { router.post('/login', passport.authenticate('local'), async (req, res, next) => { try { const data = req.body; - const response = await AuthController.login(data); + let response = await AuthController.login(data); if (!response || !response.ok) { res.status(response.code || 400).send(response.data || "Something went wrong"); } else { - req.user = response.data; - req.session.user = response.data; + // flatten controller responses + while (response.data) { + response = response.data; + } + + req.user = response; + req.session.user = response; + + // exclude sensitive data from being stored client side + const safeUserData = { + id: response.id, + username: response.username, + email: response.email, + created: response.created, + modified: response.modified + } + + const token = jwt.sign({ user: safeUserData }, secret); + req.session.token = token; + req.session.save((err) => { return next(err); }) - res.send(response.data); + res.json({ token }); } } catch (error) { next(error); diff --git a/server/routes/index.js b/server/routes/index.js index 8dbf051..2866465 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -2,15 +2,6 @@ const authRoute = require("./auth"); const itemRoute = require("./item"); async function routesLoader(app, passport) { - app.use('/', (req, res, next) => { - console.log(req.user || "no user"); - next(); - }) - - app.get('/', (req, res) => { - res.send(req.session); - }) - app.use('/auth', await authRoute(app, passport)); app.use('/app', await itemRoute(app, passport)); } diff --git a/server/routes/item.js b/server/routes/item.js index ab57fda..dd07abf 100644 --- a/server/routes/item.js +++ b/server/routes/item.js @@ -1,14 +1,20 @@ +const jwt = require('jsonwebtoken'); +require('dotenv').config(); const router = require('express').Router(); const ItemController = require('../controllers/ItemController'); -function itemRoute(app, passport) { +async function itemRoute(app, passport) { router.use('/', (req, res, next) => { - if (req.user == null) { - res.status(403).send("Unauthorized"); - return; - } else { - next(); - } + console.log('check for jwt'); + const token = req.headers['authorization'].split(" ")[1]; + jwt.verify(token, process.env.SECRET, (err, data) => { + if (err) { + res.status(403).send(err); + } else { + req.user = data; + next(); + } + }) }) router.get('/item', async (req, res) => {