Learning about authentication on Nodejs server I made this project to learn and explore.
Check the Github repository to install and run:
auth-server
Libraries that a use:
- bcryptjs
- cors
- dotenv
- express
- jsonwebtoken
- lowdb
End points:
- /auth -> The auth endpoint that creates a new user record or logs a user based on an existing record
- /verify -> The verify endpoint that checks if a given JWT token is valid
- /check-account -> An endpoint to see if there's an existing account for a given email address
Explain the app.js
ES6 stand imports.
We have the imports to use:
import express from "express"
import bcrypt from 'bcryptjs'
import cors from 'cors'
import jwt from "jsonwebtoken"
import { JSONFilePreset } from 'lowdb/node'
import 'dotenv/config'
Initialization of the lowdb, it's basically a JSON file:
const defaultData = { users: [] }
let db
async function startLowdb() {
db = await JSONFilePreset('datatable.json', defaultData)
}
startLowdb()
Initialize Express app and Define a JWT secret key using .env file:
const app = express()
const jwtSecretKey = process.env.JWT_SECRET_KEY
A little about CORS:
Cross-origin resource sharing (CORS) is a mechanism that allows a web page to access restricted resources from a server on a domain different than the domain that served the web page.
Cors Wikipedia
Middleware:
Middleware is a type of computer software program that provides services to software applications beyond those available from the operating system. It can be described as "software glue".
Middleware cors
Set up CORS and JSON middlewares:
app.use(cors())
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
Basic home route for the API:
app.get("/", (_req, res) => {
res.send("Auth API.\nPlease use POST /auth & POST /verify for authentication");
});
Here things get a little more complex.
The auth endpoint that creates a new user record or logs a user based on an existing record.
Don't forget to pass email and password on the post auth.
If authentication success will return a token.
app.post("/auth", (req, res) => {
const { email, password } = req.body
// Look up the user entry in the database
const { users } = db.data
const user = users.filter(user => email === user.email)
// If found, compare the hashed passwords and generate the JWT token for the user
if (user.length === 1) {
bcrypt.compare(password, user[0].password, function (_err, result) {
console.log('_err', _err)
console.log('compare result', result)
if (!result) {
console.log('invalid password')
return res.status(401).json({ message: "Invalid password" });
} else {
let loginData = {
email,
signInTime: Date.now(),
}
const token = jwt.sign(loginData, jwtSecretKey)
res.status(200).json({ message: "authentication success", token })
}
})
// If no user is found, hash the given password and create a new entry in the auth db with the email and hashed password
} else if (user.length === 0) {
bcrypt.hash(password, 10, async function (_err, hash) {
console.log('email e password', { email, password: hash })
const { users } = db.data
// db.get("users").push({ email, password: hash }).write()
db.data.users.push({ email, password: hash })
await db.write()
console.log('autorizado com sucesso')
let loginData = {
email,
signInTime: Date.now(),
}
const token = jwt.sign(loginData, jwtSecretKey)
res.status(200).json({ message: "success", token })
})
}
})
When using auth endpoint, you can get a token to be verified.
The verify endpoint that checks if a given JWT token is valid:
app.post('/verify', (req, res) => {
const authToken = req.headers.tokenheaderkey;
console.log('req', req.headers)
try {
const verified = jwt.verify(authToken, jwtSecretKey)
return verified ? res.status(200).json({ status: "logged in", message: "verify with success" }) : res.status(401).json({ status: "invalid auth", message: "error" });
} catch (error) {
// Access Denied
return res.status(401).json({ status: "invalid auth", message: "error" })
}
})
An endpoint to see if there's an existing account for a given email address:
app.post('/check-account', (req, res) => {
const { email } = req.body
console.log(req.body)
const { users } = db.data
const user = users.filter(user => email === user.email)
console.log(user)
res.status(200).json({
status: user.length === 1 ? "User exists" : "User does not exist", userExists: user.length === 1
})
})
An endpoint to delete a user.
Lowdb don't give a delete/remove function.
Using filer, if the user exists and then update the Users' database.json object with new object:
app.post('/remove-user', async (req, res) => {
const { email } = req.body
const { users } = db.data;
const user = users.filter(user => email === user.email);
if (user) {
const newObj = {};
newObj.users = [];
const newUsers = users.filter(userDB => email == userDB.email ? null : userDB);
newObj.users = newUsers;
db.data.users = newUsers;
db.write();
}
res.status(200).json({
status: user.length === 1 ? "User removed" : "User not founded", userExists: user.length === 1
});
})
Some application use process.env to set up the port.
You can use what ever port that is free:
app.listen(3080, function () {
console.log('listen to port 3080')
})
It's a simple way to understand how to create a simple backend server for authentication and to control users flow on your app. You can check if the user that is logging and if he still has a valid token.
Of course that more features are needed in a real application.
Some examples to implementation:
connect to another DB, create others validation to user creation, update and delete.
Put a timer on the token to expire, and you can validate if the user's request has a token that is still valid.
Maybe in the future I will some add more features.
Feel free to clone, fork, issue or a pull request.
Any suggestion are welcome.
Top comments (1)
Amazing tutorial! Thank you for sharing, I need to learn JWT once and for all and you made it look easy, will implement it in my next React project.