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) => {