I have had success implementing the application in its entirety but the only problem I am having deals with authorization when reloading the page. I have set a "token" key in localStorage but it fails to retrieve this key or token (presumably) on reload. I can clearly see it is defined in Chrome's Inspect but when I try to console.log or use that localStorage variable in anyway after reloading it is reads undefined. I can still see the token visible in Chrome's Application Storage and I am not sure how this is possible. The app works properly in development but when deployed to heroku or when express is used to deliver the static file it also stops working and this behavior of returning undefined keeps happening always returning "res.status(403).json("Not Authorized (authorization catch)")" I have gone through many documents in express and jwt along with many SO solutions. It seems most people lose their token but that is not the case here. My server code looks like:
const express= require("express")
const app = express()
const cors = require("cors")
const path = require('path')
// middleware
app.use(express.json())
app.use(cors())
// ROUTES
// register and login
app.use("/auth", require("./routes/jwtAuth"))
app.use("/dashboard", require("./routes/dashboard"))
app.use("/", express.static(path.join(__dirname, 'client/build')))
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client/build/index.html"));
});
const PORT = process.env.PORT || 5000
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`)
})
And the page that I would like to reload includes:
import React, { Fragment, useState, useEffect } from 'react'
//components
import InputConnection from './connectionlist/InputConnection'
import ListConnections from './connectionlist/ListConnections'
import LogoutBtn from './LogoutBtn'
import ReportingLayout from './reporting/Layout/ReportingLayout'
const Dashboard = ({ setAuth }) => {
const [name, setName] = useState("")
const [allConnections, setAllConnections] = useState([])
const [connectionsChange, setConnectionsChange] = useState(false)
const auth = setAuth
const getName = async () => {
try {
const response = await fetch("/dashboard/", {
method:"GET",
headers:{ token: localStorage.token }
})
const parseData = await response.json()
// console.log(parseData)
if (parseData.admin === 'lead') {
setName("Lead School Counselor")
setAllConnections(parseData.results)
}else{
setName(parseData[0].user_name)
setAllConnections(parseData)
}
} catch (error) {
}
}
useEffect(() => {
getName()
setConnectionsChange(false)
}, [connectionsChange])
if(name === "Lead School Counselor" ){
return(
<div>
<ReportingLayout auth={auth} allConnections={ allConnections } />
</div>
)
}else{
return(
<Fragment>
<div className="container">
<div className='btn-group '>
<LogoutBtn setAuth = {setAuth}/>
</div>
<h1 className="d-flex rm-3" > Welcome {name}, </h1>
<InputConnection setConnectionsChange={setConnectionsChange}/>
<ListConnections allConnections={ allConnections } setConnectionsChange=
{setConnectionsChange}/>
</div>
</Fragment>
)
}
}
export default Dashboard;
This is where the code fails. It is a middleware that deals with the authorization and prinst the error message from step 1:
const jwt = require("jsonwebtoken")
require("dotenv").config()
module.exports = async (req, res, next) => {
try {
// step 1 destructure
const jwtToken = req.header("token")
if(!jwtToken){
return res.status(403).json("Not Authorized (authorization not jwt Token)")
}
// step 2 check if the token is valid
const payload = jwt.verify(jwtToken, process.env.jwtSecret)
// step 3 gives access as req.user
req.user = payload.user
next()
} catch (err) {
console.error(err.message)
return res.status(403).json("Not Authorized (authorization catch)")
}
}
App.js:
import React, { Fragment, useState, useEffect } from 'react';
import './App.css';
import {BrowserRouter as Router, Switch, Route, Redirect} from 'react-
router-dom'
// components
import Dashboard from './components/dashboard/Dashboard'
import Login from './components/Login'
import Register from './components/Register'
import Landing from './components/Landing'
//toastify
import "react-toastify/dist/ReactToastify.css";
import { toast } from "react-toastify";
toast.configure()
function App() {
const [isAuthenticated, setIsAuthenticated] = useState(false)
const setAuth = (boolean) => {
setIsAuthenticated(boolean)
}
async function isAuth(){
try {
const response = await fetch("/auth/is-verified", {
method:"GET",
headers:{token: localStorage.getItem("token") }
app.s })
const parseRes = await response.json()
console.log(`this message is ${parseRes}`)
parseRes === true ? setIsAuthenticated(true): setIsAuthenticated(false)
} catch (err) {
console.error(err.message)
}
}
useEffect(() => {
isAuth()
})
return (
<Fragment>
<Router>
<div>
<Switch>
<Route exact path="/landing" render={props => !isAuthenticated
? <Landing {...props} /> : <Redirect to='/dashboard'/>} />
<Route exact path="/register" render={props => !isAuthenticated ?
<Register {...props} setAuth ={setAuth} /> : <Redirect to='/login'/>} />
<Route exact path="/login" render={props => !isAuthenticated ?
<Login {...props} setAuth ={setAuth} auth={isAuthenticated}/> : <Redirect
to='/dashboard'/>} />
<Route exact path="/dashboard" render={props => isAuthenticated ?
<Dashboard {...props} setAuth ={setAuth} /> : <Redirect to='/login'/>} />
</Switch>
</div>
</Router>
</Fragment>
);
}
export default App;
here is the dashboard.js db and connections:
const router = require("express").Router()
const pool = require("../db")
const authorization = require("../middleware/authorization")
// all connections and name
router.get("/", authorization, async (req, res) => {
try {
res.json(req.user.name)
if(req.user.name === 'lead'){
const lead = await pool.query("SELECT * FROM connections LEFT JOIN
users ON users.user_id = connections.user_id")
... A bunch of sql queries here that work fine...
res.json({
admin: req.user.name,
results: lead.rows,
// aggregated queries
studentsEngaged: studentsEngaged.rows,
gender:gender.rows,
distinctStudents: distinctStudents.rows,
amountSep: amountSep.rows,
amountOct:amountOct.rows,
amountNov: amountNov.rows,
amountDec: amountDec.rows,
studentSessions:studentSessions.rows,
homeVisits: homeVisits.rows,
outsideAgencies: outsideAgencies.rows,
cpReferrals: cpReferrals.rows,
amountReferrals:amountReferrals.rows,
amountDischarges: amountDischarges.rows,
classroomPresentations: classroomPresentations.rows,
groupSessions: groupSessions.rows,
checkins: checkins.rows,
crisisInterventions: crisisInterventions.rows,
parentContacts: parentContacts.rows,
meetings : meetings.rows
})
}else{
const user = await pool.query("SELECT u.user_name, c.connection_id,
c.contact_type, c.contact_method, c.provision, c.connection_date,
c.student_id,
c.purpose, c.gender, c.yearGroup, c.school, c.referral_discharge,
c.cp_referral
FROM users AS u LEFT JOIN connections AS c ON u.user_id = c.user_id WHERE
u.user_id= $1", [req.user.id])
res.json(user.rows)
}
} catch (err) {
console.error(err.message)
res.status(500).json("Server Error (dashboard catch)")
}
})
// create connection
router.post("/connections", authorization, async (req, res) => {
try {
// console.log(req.body);
const { student_id, contact_type, yearGroup, school, contact_method,
gender, purpose, provision, connection_date, referral_discharge, cp_referral}
=
req.body;
const newConnection = await pool.query(
"INSERT INTO connections (user_id, student_id, user_name, contact_type,
yearGroup, school, contact_method, gender, purpose, provision,
connection_date,
referral_discharge, cp_referral) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9,
$10, $11, $12, $13) RETURNING *",
[req.user.id, student_id, req.user.name, contact_type, yearGroup, school,
contact_method, gender, purpose, provision, connection_date,
referral_discharge,
cp_referral]
);
res.json(newConnection.rows[0]);
// console.log(newConnection.rows[0])
} catch (err) {
console.error(err.message)
}
});
// update connection
router.put("/connections/:id", authorization, async (req, res) => {
try {
const { id } = req.params;
const { student_id, contact_type, yearGroup, school, contact_method,
gender, purpose, provision, connection_date, referral_discharge, cp_referral
} =
req.body;
const updateConneciton = await pool.query(
"UPDATE connections SET student_id=$1, contact_type=$2, yearGroup=$3,
school=$4, contact_method=$5, gender=$6, purpose=$7, provision=$8,
connection_date=$9, referral_discharge=$10, cp_referral=$11 WHERE
connection_id =
$12 AND user_id = $13 RETURNING *",
[student_id, contact_type, yearGroup, school, contact_method, gender,
purpose, provision, connection_date, referral_discharge, cp_referral, id,
req.user.id]
);
if (updateConneciton.rows.length === 0) {
return res.json("This connection is not yours");
}
res.json("Connection was updated");
} catch (err) {
console.error(err.message);
}
});
// delete connection
router.delete("/connections/:id", authorization, async (req, res) => {
try {
const { id } = req.params;
const deleteConnection = await pool.query(
"DELETE FROM connections WHERE connection_id = $1 AND user_id = $2
RETURNING *",
[id, req.user.id]
);
if (deleteConnection.rows.length === 0) {
return res.json("This connection is not yours");
}
res.json("Connection was deleted");
} catch (err) {
console.error(err.message);
}
}) ;
At input is appended to the headers:
import React, { Fragment, useState } from "react";
import { toast } from 'react-toastify'
const InputTodo = ({ setConnectionsChange }) => {
const [contact_type, setContactType] = useState("");
const [contact_method, setContactMethod] = useState("");
const [provision, setProvision] = useState("");
const [connection_date, setDate] = useState("");
const [student_id, setStudentID] = useState("");
const [purpose, setPurpose] = useState("");
const [gender, setGender] = useState("");
const [yearGroup, setYearGroup] = useState("");
const [school, setSchool] = useState("");
const [referral_discharge, setReferralDischarge] = useState("");
const [cp_referral, setCPReferral] = useState("");
const onSubmitForm = async e => {
e.preventDefault();
try {
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
myHeaders.append("token", localStorage.token);
const body = {
contact_type,
contact_method,
provision,
connection_date,
student_id,
purpose,
gender,
yearGroup,
school,
referral_discharge,
cp_referral
};
const response = await fetch("/dashboard/connections", {
method: "POST",
headers: myHeaders,
body: JSON.stringify(body)
});
const parseResponse = await response.json();
console.count(parseResponse);
setConnectionsChange(true);
setContactType("")
setContactMethod("")
setProvision("")
setDate("")
setStudentID("")
setPurpose("")
setGender("")
setYearGroup("")
setSchool("")
setReferralDischarge("")
setCPReferral("")
toast.success('Contact has been added', {
position: "top-center",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
});
} catch (error) {
console.error(error.message);
}
};
return (
<Fragment> A form
</Fragment>
)
Authorization check on back end:
const router = require("express").Router()
const pool = require("../db")
const bcrypt = require("bcrypt")
const jwtGenerator = require("../utils/jwtGenerator")
const validInfo = require("../middleware/validInfo")
const authorization = require("../middleware/authorization")
// registering
router.post("/register", validInfo, async(req, res) =>{
try {
// step1 destructure
const { name, email, password } = req.body
// step2 check if the user exists
const user = await pool.query("SELECT * FROM users WHERE
user_email=$1", [email])
if(user.rows.length >0){
return res.status(401).json("User already exists; email is
already registered with the database")
}
// step3 bcrypt the user password for db
const saltRound = 10;
const salt = await bcrypt.genSalt(saltRound)
const bcryptPassword = await bcrypt.hash(password, salt)
// step4 insert the info into the db
const newUser = await pool.query("INSERT INTO users (user_name,
user_email, user_password) VALUES ($1, $2, $3) RETURNING *", [name, email,
bcryptPassword])
// step5 generate a jwt token
const token = jwtGenerator(newUser.rows[0].user_id,
newUser.rows[0].user_name)
res.json({ token })
} catch (err) {
console.error(err.message)
res.status(500).json("Server Error (register)")
}
})
// login and logout
router.post("/login", validInfo, async (req, res) => {
try {
// step1 deconstruct req.body
const { email, password } = req.body
// step 2 check if user doesnt exist and if not throw and error
const user = await pool.query("SELECT * FROM users WHERE
user_email=$1", [email])
if(user.rows.length === 0){
return res.status(401).json("User email is incorrect or does not
exist.")
}
// step 3 check if incoming pword is the same as db password
const validPassword = await bcrypt.compare(password,
user.rows[0].user_password)
if(!validPassword){
return res.status(401).json("Password is incorrect.")
}
// step4 give them a jwt token
const token = jwtGenerator(user.rows[0].user_id, user.rows[0].user_name)
res.json({ token })
} catch (err) {
console.log(err.Message)
res.statusMessage(500).json("Server Error (login)")
}
})
router.get("/is-verified", authorization, (req, res) => {
try {
res.json(true)
} catch (error) {
console.log(err.Message)
res.statusMessage(500).json("Server Error (is-verified)")
}
})
module.exports = router;
Instead of reloading, I will type in "/login" into the url and it will then redirect me (using the routes set up) to dashboard no problem. Could I just direct the user to log in on reloading and if so what is an optimal way to implement it?