Copyright Derek O'Reilly, Dundalk Institute of Technology (DkIT), Dundalk, Co. Louth, Ireland.
Server-side sessions are needed if a user needs to be logged in to access a database collection or any other resource on the server-side.
Session IDs tie the client-side and server-side together. A session ID is generated on the server-side. It is sent as a cookie from the server-side to the client-side when a user opens a website. It can be sent the other way (from client-side to server-side) as part of the header data in an axios function call.
In order to use sessions, we must create a private session key. This should be stored in the server/config/.env file, as shown below:
... # Keys
SESSION_PRIVATE_KEY = yourRandomKey ...
We must tie the SESSION_PRIVATE_KEY to an express-session in the server/server.js file, as shown below:
... app.use(require(`express-session`)({
secret: process.env.SESSION_PRIVATE_KEY,
resave: false,
cookie: {secure: false, maxAge: 60000},
saveUninitialized: true
})) ...
In order to use sessions, we must include the express-session package. Use npm to install the express-session package.
On the server-side, session data is stored in the req.session property that is passed in the header of client-side axios function calls.
We can add our own additional properties to req.session. In the code below, we add a user property (req.session.user). We should set the req.session.user property whenever a user successfully logs in.
In the code below, the user property is initialised to contain an email and accessLevel property. This means that the properties req.session.user.email and req.session.user.accessLevel will be available on the server-side for any logged in user.
router.post(`/users/login/:email/:password`, (req,res) =>
{ ... req.session.user = {email:data.email, accessLevel:data.accessLevel}
res.json({name:data.name, accessLevel:data.accessLevel}) ... }
When a user logs out, the session should be destroyed on the server-side, as shown below:
router.post(`/users/logout`, (req,res) =>
{
req.session.destroy()
res.json({})
})
All router code that accesses restricted resources, such as database collections, should do server-side validation to ensure that the user is allowed to access the resource.
The req.session.user property that is created in the login code above can be used anywhere on the server-side to check if a user is logged in. The code below can be used to check that the req.session.user property exists. If req.session.user does not exist, then the user is not logged in.
if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) }
The code below checks on the server-side that a user is logged in before allowing the user to add a new car to the 'cars' collection.
In the login code described above, an accessLevel property is added to the req.session.user property. We can check this property against the required access level that is needed to access a resource. The code below checks that the logged-in user is an administrator before letting them add a new car to the 'cars' collection.
// Add new record router.post(`/cars`, (req, res) => { if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) } else { if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN) { carsModel.create(req.body, (error, data) => { res.json(data) }) } else { res.json({errorMessage:`User is not an administrator, so they cannot add new records`}) } } })
Session IDs can be sent from the client-side to the server-side during the next axios function call. This allows the server-side to tie the client-side to a given session. In order to do this, we only have to include the line of code that is shown below before we make each axios call.
axios.defaults.withCredentials = true
Open the client_side_session_storage project from the previous section in these notes. Change the code so that it uses sessions to validate that users are logged in at the correct access level before allowing them to interact with the 'cars' collection.
The full project code for the "Cars" Worked Example that is described below can be downloaded from this link.
In this example, we implement server-side sessions and restrict server-side access to user's who have the appropriate access level.
Other than the inclusion of axios.defaults.withCredentials = true before each axios() method, the client-side code does not change.
import React, {Component} from "react"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"
import Button from "../components/Button"
import {SERVER_HOST} from "../config/global_constants"
export default class Register extends Component
{
constructor(props)
{
super(props)
this.state = {
name:"",
email:"",
password:"",
confirmPassword:"",
isRegistered:false
}
}
handleChange = (e) =>
{
this.setState({[e.target.name]: e.target.value})
}
handleSubmit = (e) =>
{
e.preventDefault()
axios.defaults.withCredentials = true // needed for sessions to work
axios.post(`${SERVER_HOST}/users/register/${this.state.name}/${this.state.email}/${this.state.password}`)
.then(res =>
{
if(res.data)
{
if (res.data.errorMessage)
{
console.log(res.data.errorMessage)
}
else // user successfully registered
{
console.log("User registered and logged in")
sessionStorage.name = res.data.name
sessionStorage.accessLevel = res.data.accessLevel
this.setState({isRegistered:true})
}
}
else
{
console.log("Registration failed")
}
})
}
render()
{
return (
<form className="form-container" noValidate = {true} id = "loginOrRegistrationForm">
{this.state.isRegistered ? <Redirect to="/DisplayAllCars"/> : null}
<h2>New User Registration</h2>
<input
name = "name"
type = "text"
placeholder = "Name"
autoComplete="name"
value = {this.state.name}
onChange = {this.handleChange}
ref = {(input) => { this.inputToFocus = input }}
/><br/>
<input
name = "email"
type = "email"
placeholder = "Email"
autoComplete="email"
value = {this.state.email}
onChange = {this.handleChange}
/><br/>
<input
name = "password"
type = "password"
placeholder = "Password"
autoComplete="password"
title = "Password must be at least ten-digits long and contains at least one lowercase letter, one uppercase letter, one digit and one of the following characters (£!#€$%^&*)"
value = {this.state.password}
onChange = {this.handleChange}
/><br/>
<input
name = "confirmPassword"
type = "password"
placeholder = "Confirm password"
autoComplete="confirmPassword"
value = {this.state.confirmPassword}
onChange = {this.handleChange}
/><br/><br/>
<Button value="Register New User" className="green-button" onClick={this.handleSubmit} />
<Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
</form>
)
}
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {Component} from "react"
import {Redirect, Link} from "react-router-dom"
import axios from "axios"
import Button from "../components/Button"
import {SERVER_HOST} from "../config/global_constants"
export default class Login extends Component
{
constructor(props)
{
super(props)
this.state = {
email:"",
password:"",
isLoggedIn:false
}
}
handleChange = (e) =>
{
this.setState({[e.target.name]: e.target.value})
}
handleSubmit = (e) =>
{
axios.defaults.withCredentials = true // needed for sessions to work
axios.post(`${SERVER_HOST}/users/login/${this.state.email}/${this.state.password}`)
.then(res =>
{
if(res.data)
{
if (res.data.errorMessage)
{
console.log(res.data.errorMessage)
}
else // user successfully logged in
{
console.log("User logged in")
sessionStorage.name = res.data.name
sessionStorage.accessLevel = res.data.accessLevel
this.setState({isLoggedIn:true})
}
}
else
{
console.log("Login failed")
}
})
}
render()
{
return (
<form className="form-container" noValidate = {true} id = "loginOrRegistrationForm">
<h2>Login</h2>
{this.state.isLoggedIn ? <Redirect to="/DisplayAllCars"/> : null}
<input
type = "email"
name = "email"
placeholder = "Email"
autoComplete="email"
value={this.state.email}
onChange={this.handleChange}
/><br/>
<input
type = "password"
name = "password"
placeholder = "Password"
autoComplete="password"
value={this.state.password}
onChange={this.handleChange}
/><br/><br/>
<Button value="Login" className="green-button" onClick={this.handleSubmit}/>
<Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
</form>
)
}
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {Component} from "react"
import {Redirect} from "react-router-dom"
import axios from "axios"
import Button from "../components/Button"
import {ACCESS_LEVEL_GUEST, SERVER_HOST} from "../config/global_constants"
export default class Logout extends Component
{
constructor(props)
{
super(props)
this.state = {
isLoggedIn:true
}
}
handleSubmit = (e) =>
{
e.preventDefault()
axios.defaults.withCredentials = true // needed for sessions to work
axios.post(`${SERVER_HOST}/users/logout`)
.then(res =>
{
if(res.data)
{
if (res.data.errorMessage)
{
console.log(res.data.errorMessage)
}
else
{
console.log("User logged out")
sessionStorage.clear()
sessionStorage.name = "GUEST"
sessionStorage.accessLevel = ACCESS_LEVEL_GUEST
this.setState({isLoggedIn:false})
}
}
else
{
console.log("Logout failed")
}
})
}
render()
{
return (
<div>
{!this.state.isLoggedIn ? <Redirect to="/DisplayAllCars"/> : null}
<Button value="Log out" className="red-button" onClick={this.handleSubmit}/>
</div>
)
}
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {Component} from "react"
import {Link} from "react-router-dom"
import axios from "axios"
import CarTable from "./CarTable"
import Logout from "./Logout"
import {ACCESS_LEVEL_GUEST, ACCESS_LEVEL_ADMIN, SERVER_HOST} from "../config/global_constants"
export default class DisplayAllCars extends Component
{
constructor(props)
{
super(props)
this.state = {
cars:[]
}
}
componentDidMount()
{
axios.defaults.withCredentials = true // needed for sessions to work
axios.get(`${SERVER_HOST}/cars`)
.then(res =>
{
if(res.data)
{
if (res.data.errorMessage)
{
console.log(res.data.errorMessage)
}
else
{
console.log("Records read")
this.setState({cars: res.data})
}
}
else
{
console.log("Record not found")
}
})
}
render()
{
return (
<div className="form-container">
{sessionStorage.accessLevel > ACCESS_LEVEL_GUEST ?
<div className="logout">
<Logout/>
</div>
:
<div>
<Link className="green-button" to={"/Login"}>Login</Link>
<Link className="blue-button" to={"/Register"}>Register</Link>
<Link className="red-button" to={"/ResetDatabase"}>Reset Database</Link> <br/><br/><br/></div>
}
<div className="table-container">
<CarTable cars={this.state.cars} />
{sessionStorage.accessLevel >= ACCESS_LEVEL_ADMIN ?
<div className="add-new-car">
<Link className="blue-button" to={"/AddCar"}>Add New Car</Link>
</div>
:
null
}
</div>
</div>
)
}
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {Component} from "react"
import {Redirect, Link} from "react-router-dom"
import Form from "react-bootstrap/Form"
import axios from "axios"
import Button from "../components/Button"
import {ACCESS_LEVEL_ADMIN, SERVER_HOST} from "../config/global_constants"
export default class AddCar extends Component
{
constructor(props)
{
super(props)
this.state = {
model:"",
colour:"",
year:"",
price:"",
redirectToDisplayAllCars:sessionStorage.accessLevel < ACCESS_LEVEL_ADMIN
}
}
componentDidMount()
{
this.inputToFocus.focus()
}
handleChange = (e) =>
{
this.setState({[e.target.name]: e.target.value})
}
handleSubmit = (e) =>
{
e.preventDefault()
const carObject = {
model: this.state.model,
colour: this.state.colour,
year: this.state.year,
price: this.state.price
}
axios.defaults.withCredentials = true // needed for sessions to work
axios.post(`${SERVER_HOST}/cars`, carObject)
.then(res =>
{
if(res.data)
{
if (res.data.errorMessage)
{
console.log(res.data.errorMessage)
}
else
{
console.log("Record added")
this.setState({redirectToDisplayAllCars:true})
}
}
else
{
console.log("Record not added")
}
})
}
render()
{
return (
<div className="form-container">
{this.state.redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}
<Form>
<Form.Group controlId="model">
<Form.Label>Model</Form.Label>
<Form.Control ref = {(input) => { this.inputToFocus = input }} type="text" name="model" value={this.state.model} onChange={this.handleChange} />
</Form.Group>
<Form.Group controlId="colour">
<Form.Label>Colour</Form.Label>
<Form.Control type="text" name="colour" value={this.state.colour} onChange={this.handleChange} />
</Form.Group>
<Form.Group controlId="year">
<Form.Label>Year</Form.Label>
<Form.Control type="text" name="year" value={this.state.year} onChange={this.handleChange} />
</Form.Group>
<Form.Group controlId="price">
<Form.Label>Price</Form.Label>
<Form.Control type="text" name="price" value={this.state.price} onChange={this.handleChange} />
</Form.Group>
<Button value="Add" className="green-button" onClick={this.handleSubmit}/>
<Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link>
</Form>
</div>
)
}
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {Component} from "react" import Form from "react-bootstrap/Form" import {Redirect, Link} from "react-router-dom" import axios from "axios" import Button from "../components/Button" import {ACCESS_LEVEL_NORMAL_USER, SERVER_HOST} from "../config/global_constants" export default class EditCar extends Component { constructor(props) { super(props) this.state = { model: ``, colour: ``, year: ``, price: ``, redirectToDisplayAllCars:sessionStorage.accessLevel < ACCESS_LEVEL_NORMAL_USER } } componentDidMount() { this.inputToFocus.focus() axios.defaults.withCredentials = true // needed for sessions to work axios.get(`${SERVER_HOST}/cars/${this.props.match.params.id}`) .then(res => { if(res.data) { if (res.data.errorMessage) { console.log(res.data.errorMessage) } else { this.setState({ model: res.data.model, colour: res.data.colour, year: res.data.year, price: res.data.price }) } } else { console.log(`Record not found`) } }) } handleChange = (e) => { this.setState({[e.target.name]: e.target.value}) } handleSubmit = (e) => { e.preventDefault() const carObject = { model: this.state.model, colour: this.state.colour, year: this.state.year, price: this.state.price } axios.defaults.withCredentials = true // needed for sessions to work axios.put(`${SERVER_HOST}/cars/${this.props.match.params.id}`, carObject) .then(res => { if(res.data) { if (res.data.errorMessage) { console.log(res.data.errorMessage) } else { console.log(`Record updated`) this.setState({redirectToDisplayAllCars:true}) } } else { console.log(`Record not updated`) } }) } render() { return ( <div className="form-container"> {this.state.redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null} <Form> <Form.Group controlId="model"> <Form.Label>Model</Form.Label> <Form.Control ref = {(input) => { this.inputToFocus = input }} type="text" name="model" value={this.state.model} onChange={this.handleChange} /> </Form.Group> <Form.Group controlId="colour"> <Form.Label>Colour</Form.Label> <Form.Control type="text" name="colour" value={this.state.colour} onChange={this.handleChange} /> </Form.Group> <Form.Group controlId="year"> <Form.Label>Year</Form.Label> <Form.Control type="text" name="year" value={this.state.year} onChange={this.handleChange} /> </Form.Group> <Form.Group controlId="price"> <Form.Label>Price</Form.Label> <Form.Control type="text" name="price" value={this.state.price} onChange={this.handleChange} /> </Form.Group> <Button value="Update" className="green-button" onClick={this.handleSubmit}/> <Link className="red-button" to={"/DisplayAllCars"}>Cancel</Link> </Form> </div> ) } }
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
import React, {Component} from "react"
import {Redirect} from "react-router-dom"
import axios from "axios"
import {SERVER_HOST} from "../config/global_constants"
export default class DeleteCar extends Component
{
constructor(props)
{
super(props)
this.state = {
redirectToDisplayAllCars:false
}
}
componentDidMount()
{
axios.defaults.withCredentials = true // needed for sessions to work
axios.delete(`${SERVER_HOST}/cars/${this.props.match.params.id}`)
.then(res =>
{
if(res.data)
{
if (res.data.errorMessage)
{
console.log(res.data.errorMessage)
}
else // success
{
console.log("Record deleted")
}
this.setState({redirectToDisplayAllCars:true})
}
else
{
console.log("Record not deleted")
}
})
}
render()
{
return (
<div>
{this.state.redirectToDisplayAllCars ? <Redirect to="/DisplayAllCars"/> : null}
</div>
)
}
}
All axios() methods must be preceeded by axios.defaults.withCredentials = true
axios.defaults.withCredentials = true // needed for sessions to work
A session's ID (which is shared on the client-side via a cookie) and its server-side details are accessible via the req.session object, which is one of the parameters of the various router methods. A session property is created and becomes available for subsequent use as soon as it is first assigned a value. In the "Cars" example, a session property called user has been assigned a sub-property called accessLevel.
# This file holds global constants that are visible on the Server-side # Database DB_NAME = D01234567 # Access Levels ACCESS_LEVEL_GUEST = 0 ACCESS_LEVEL_NORMAL_USER = 1 ACCESS_LEVEL_ADMIN = 2 # Keys SESSION_PRIVATE_KEY = yourRandomKey # Salt length of encryption of user passwords # The salt length should be 16 or higher for commercially released code # It has been set to 3 here, so that the password will be generated faster PASSWORD_HASH_SALT_ROUNDS = 3 # Port SERVER_PORT = 4000 # Local Host LOCAL_HOST = http://localhost:3000
We create a private SESSION_PRIVATE_KEY for our session.
// Server-side global variables require(`dotenv`).config({path:`./config/.env`}) // Database require(`./config/db`) // Express const express = require(`express`) const app = express() app.use(require(`express-session`)({ secret: process.env.SESSION_PRIVATE_KEY, resave: false, cookie: {secure: false, maxAge: 60000}, saveUninitialized: true })) app.use(require(`body-parser`).json()) app.use(require(`cors`)({credentials: true, origin: process.env.LOCAL_HOST})) // Routers app.use(require(`./routes/cars`)) app.use(require(`./routes/users`)) // Port app.listen(process.env.SERVER_PORT, () => { console.log(`Connected to port ` + process.env.SERVER_PORT) }) // Error 404 app.use((req, res, next) => {next(createError(404))}) // Other errors app.use(function (err, req, res, next) { console.error(err.message) if (!err.statusCode) { err.statusCode = 500 } res.status(err.statusCode).send(err.message) })
We use the express-session package.
app.use(require(`express-session`)({ secret: process.env.SESSION_PRIVATE_KEY, resave: false, cookie: {secure: false, maxAge: 60000}, saveUninitialized: true }))
Install the express-session package.
const router = require(`express`).Router() const usersModel = require(`../models/users`) const bcrypt = require('bcryptjs'); // needed for password encryption // IMPORTANT // Obviously, in a production release, you should never have the code below, as it allows a user to delete a database collection // The code below is for development testing purposes only router.post(`/users/reset_user_collection`, (req,res) => { usersModel.deleteMany({}, (error, data) => { if(data) { const adminPassword = `123!"£qweQWE` bcrypt.hash(adminPassword, parseInt(process.env.PASSWORD_HASH_SALT_ROUNDS), (err, hash) => { if(err){console.log("bad")} usersModel.create({name:"Administrator",email:"admin@admin.com",password:hash,accessLevel:parseInt(process.env.ACCESS_LEVEL_ADMIN)}, (createError, createData) => { if(createData) { res.json(createData) } else { res.json({errorMessage:`Failed to create Admin user for testing purposes`}) } }) }) } else { res.json({errorMessage:`User is not logged in`}) } }) }) router.post(`/users/register/:name/:email/:password`, (req,res) => { // If a user with this email does not already exist, then create new user usersModel.findOne({email:req.params.email}, (uniqueError, uniqueData) => { if(uniqueData) { res.json({errorMessage:`User already exists`}) } else { bcrypt.hash(req.params.password, parseInt(process.env.PASSWORD_HASH_SALT_ROUNDS), (err, hash) => { usersModel.create({name:req.params.name,email:req.params.email,password:hash}, (error, data) => { if(data) { req.session.user = {email: data.email, accessLevel:data.accessLevel} res.json({name: data.name, accessLevel:data.accessLevel}) } else { res.json({errorMessage:`User was not registered`}) } }) }) } }) }) router.post(`/users/login/:email/:password`, (req,res) => { usersModel.findOne({email:req.params.email}, (error, data) => { if(data) { bcrypt.compare(req.params.password, data.password, (err, result) => { if(result) { req.session.user = {email: data.email, accessLevel:data.accessLevel} res.json({name: data.name, accessLevel:data.accessLevel}) } else { res.json({errorMessage:`User is not logged in`}) } }) } else { console.log("not found in db") res.json({errorMessage:`User is not logged in`}) } }) }) router.post(`/users/logout`, (req,res) => { req.session.destroy() res.json({}) }) module.exports = router
When a user registers, their email and accessLevel are stored in the session. This session information will then be available on the server-side for other methods to access.
router.post(`/users/register/:name/:email/:password`, (req,res) => { // If a user with this email does not already exist, then create new user usersModel.findOne({email:req.params.email}, (uniqueError, uniqueData) => { if(uniqueData) { res.json({errorMessage:`User already exists`}) } else { bcrypt.hash(req.params.password, parseInt(process.env.PASSWORD_HASH_SALT_ROUNDS), (err, hash) => { usersModel.create({name:req.params.name,email:req.params.email,password:hash}, (error, data) => { if(data) { req.session.user = {email: data.email, accessLevel:data.accessLevel} res.json({name: data.name, accessLevel:data.accessLevel}) } else { res.json({errorMessage:`User was not registered`}) } }) }) } }) })
Likewise, when a user logs in, their email and accessLevel are stored in the session. This session information will then be available on the server-side for other methods to access.
router.post(`/users/login/:email/:password`, (req,res) => { ... req.session.user = {email: data.email, accessLevel:data.accessLevel} ... })
When a user logs out, the session is destroyed and it is no longer accessible on the server-side.
router.post(`/users/logout`, (req,res) => {
req.session.destroy()
res.json({})
})
const router = require(`express`).Router() const carsModel = require(`../models/cars`) // read all records router.get(`/cars`, (req, res) => { //user does not have to be logged in to see car details carsModel.find((error, data) => { res.json(data) }) }) // Read one record router.get(`/cars/:id`, (req, res) => { if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) } else { carsModel.findById(req.params.id, (error, data) => { res.json(data) }) } }) // Add new record router.post(`/cars`, (req, res) => { if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) } else { if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN) { carsModel.create(req.body, (error, data) => { res.json(data) }) } else { res.json({errorMessage:`User is not an administrator, so they cannot add new records`}) } } }) // Update one record router.put(`/cars/:id`, (req, res) => { if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) } else { carsModel.findByIdAndUpdate(req.params.id, {$set: req.body}, (error, data) => { res.json(data) }) } }) // Delete one record router.delete(`/cars/:id`, (req, res) => { if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) } else { if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN) { carsModel.findByIdAndRemove(req.params.id, (error, data) => { res.json(data) }) } else { res.json({errorMessage:`User is not an administrator, so they cannot delete records`}) } } }) module.exports = router
A session is only set up when a user successfully logs in.
We can test if a user is logged in by checking if(typeof req.session.user === `undefined`)
// Read one record
router.get(`/cars/:id`, (req, res) =>
{
if(typeof req.session.user === `undefined`)
{
res.json({errorMessage:`User is not logged in`})
}
else
{
carsModel.findById(req.params.id, (error, data) =>
{
res.json(data)
})
}
})
We can test if a user is logged in by checking if(typeof req.session.user === `undefined`)
We can test the accessLevel of a user by checking if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
// Add new record router.post(`/cars`, (req, res) => { if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) } else { if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN) { carsModel.create(req.body, (error, data) => { res.json(data) }) } else { res.json({errorMessage:`User is not an administrator, so they cannot add new records`}) } } })
We can test if a user is logged in by checking if(typeof req.session.user === `undefined`)
// Update one record
router.put(`/cars/:id`, (req, res) =>
{
if(typeof req.session.user === `undefined`)
{
res.json({errorMessage:`User is not logged in`})
}
else
{
carsModel.findByIdAndUpdate(req.params.id, {$set: req.body}, (error, data) =>
{
res.json(data)
})
}
})
We can test if a user is logged in by checking if(typeof req.session.user === `undefined`)
We can test the accessLevel of a user by checking if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN)
// Delete one record router.delete(`/cars/:id`, (req, res) => { if(typeof req.session.user === `undefined`) { res.json({errorMessage:`User is not logged in`}) } else { if(req.session.user.accessLevel >= process.env.ACCESS_LEVEL_ADMIN) { carsModel.findByIdAndRemove(req.params.id, (error, data) => { res.json(data) }) } else { res.json({errorMessage:`User is not an administrator, so they cannot delete records`}) } } })
Write code to incorporate the login (from here), registration (from here) and validation (from here), as shown here.
Add MongoDB field validation to the above code. You should provide appropriate client-side error messages.
Why should you validate at all three of the client-side, the server-side routes, and the mongoDB?
Copyright Derek O' Reilly, Dundalk Institute of Technology (DkIT), Dundalk, Co. Louth, Ireland.