Skip to main content

LAB: how to do auth in your app?

Let's learn about authentication.

Authentication is the one of the most common and widely use in almost all web applications.

Authentication will be 2 methods mainly

  1. Register (Sign up)
  2. Login

Let's start:

Download this zip file and extract it in to a folder
https://drive.google.com/file/d/1IRCFkS_xvYXZ90SgXQeYt7a4QRHZP-5I/view?usp=sharing

We are working in the backend directory

create a directory name auth in /src/controller/

Let's create a required services first

create jwt.js file in /controller/auth/ :

import jwt from 'jsonwebtoken';

const JWTSecretKey = 'wuuuuuuuuuuuw';
//move this value to .env and import it here instead
// also Change it to your student ID

export const generateToken = (id) => {
	const token = jwt.sign(
		{
			exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24,
			userId: id,
		},
		JWTSecretKey
	);
	return token;
};

export const validateToken = (token) => {
	const id = jwt.verify(token, JWTSecretKey);
	return id.userId;
};

export const checkauth = (req, res, next) => {
	try {
		const token = req.header('authorization');
		if (token) {
			if (!jwt.verify(token.split(' ')[1], key)) {
				throw err;
			}
		} else {
			throw err;
		}
		next();
	} catch (err) {
		console.error(err);
		return res
			.status(401)
			.json({ success: false, message: 'Invalid token' });
	}
};


We create these function to handle the authentication methods

Now we can deny access to user only routes by using checkauth():

Screenshot 2024-05-06 at 23.07.28.png

import and use checkauth() from /src/controller/auth/jwt.js and use it above all of the paths we want to restrict

Next let's start the real authentication functions start with register method

create new register.js file in /src/controller/auth/

import db from '../../db/connect.js';
import { v4 as uuidv4 } from 'uuid';
import bcrypt from 'bcrypt';

export const register = async (req, res, next) => {
	try {
		const username = req.body.username;
		const salt = 10; // Move this to .env and import it here instead
		const password = bcrypt.hashSync(req.body.password, salt);
		const firstname = req.body.firstname;
		const lastname = req.body.lastname;
		const email = req.body.email;
		const existingUser = await db
			.promise()
			.query('SELECT * FROM `users` WHERE username = ?', [username]);
		// If the username exists, throw an error
		if (existingUser[0].length > 0) {
			return res
				.status(400)
				.json({ success: false, payload: 'Username already exists' });
		}

		const credit_ID = uuidv4();
		const data = [
			[username, password, firstname, lastname, email, credit_ID],
		];
		await db
			.promise()
			.query(
				'INSERT INTO `users` (`username`, `password`, `firstname`, `lastname`, `email`,`credit_ID`) VALUES  ?',
				[data]
			);

		return res
			.status(200)
			.json({ success: true, payload: 'register successful' });
	} catch (error) {
		return res.status(400).json({ success: false, payload: error.message });
	}
};

Here as you can see the password has been hashed with bcrypt (node dependency for encoding data) bcrypt.hash() is a function to encode a data with salt. We are hashing the password because the possibility that the database can be leaked. This also ensure user that even the developer will not know users password.

Now we register next let's go to login
create another file in the same directory as register.js name login.js

this file will handle the login function

import db from '../../db/connect.js';
import bcrypt from 'bcrypt';
import { generateToken } from './jwt.js';

export const login = (req, res, next) => {
	const username = req.body.username;
	const password = req.body.password;
	console.log(username);
	try {
		db.query(
			'SELECT id ,password FROM `users` WHERE username = ?',
			[username],
			async (err, user) => {
				if (err) {
					throw err;
				} else {
					if (user.length == 0) {
						return res.status(400).send('username not found');
					} else if (
						bcrypt.compare(password, user[0].password) == false
					) {
						return res.status(400).send('password not correct');
					} else {
						const id = user[0].id;
						const token = generateToken(id);
						res.setHeader('token', token);
						return res.status(200).json({ token: token });
					}
				}
			}
		);
	} catch (error) {
		next();
		return res.status(400).json({
			payload: error,
		});
	}
};

as you see here we also use bcrypt here but now we use the compare() function. this function is use to compare normal string and hashed string that is it matched without using salt value.



now all of the function are all created let's create the router for these functions
create a file name auth.js in /router/ directory.

import express from 'express';
import { login } from '../controller/auth/login.js';
import { register } from '../controller/auth/register.js';

const authRouter = express();
authRouter.post('/login', login);
authRouter.post('/register', register);

export default authRouter;


then add the router in index.js file above the checkauth() call to bypass the checkauth() function

app.use('/auth', authRouter);

if you call this under checkauth it will use checkauth middleware before using the endpoint. and will not be able to access some of the endpoint because we don't have the token yet.

now let's move to other functionality

1. deposit function (/controller/bank/deposits.js)
before this class we used to define a userId as a static integer but if we have multiple users this will not work as expected anymore or if we send it by body there would be possibility of the request to leaked so we need to change it to get the userId from user token

import db from '../../db/connect.js';
import { validateToken } from '../auth/jwt.js';
import timeStamp from '../../utill/timeStamp.js';
export const getDeposit = (req, res) => {
	const token = req.header('authorization').split(' ')[1];
	const userId = validateToken(token);
	// const userId = req.body.user_id;
	db.query(
		'SELECT * FROM `banks` WHERE (owner = ? && type = ?) ORDER BY date DESC',
		[userId, 'disposit'],
		(err, re) => {
			if (err) {
				res.status(400).json({
					success: false,
					data: null,
					error: err.message,
				});
			} else {
				return res.json({
					success: true,
					data: re,
					error: null,
				});
			}
		}
	);
};
export const deposit = async (req, res, next) => {
	try {
		// const user_id = req.body.user_id;
		const token = req.header('authorization').split(' ')[1];
		const userId = validateToken(token);
		const receiver = null;
		const note = req.body?.note;
		const amount = req.body.amount;
		const bank = req.body.bank;
		// eslint-disable-next-line no-undef
		const user = await new Promise((resolve, reject) => {
			db.query(
				'SELECT balance, firstname FROM `users` WHERE id = ?',
				[userId],
				(err, result) => {
					if (err) {
						reject(err);
					} else {
						resolve(result[0]);
					}
				}
			);
		});

		const data = [
			[
				userId,
				user.firstname,
				receiver,
				note,
				amount,
				bank,
				'deposit',
				timeStamp(),
			],
		];
		db.query(
			'INSERT INTO banks (`owner`, `sender`, `receiver`,`note`, `amount`, `bank`, `type`, `date`) VALUES ?',
			[data],
			(err, result) => {
				if (err) {
					console.log(err);
					throw err;
				} else {
					db.query('UPDATE users SET balance = ? WHERE id = ?', [
						user.balance + amount,
						userId,
						(err) => {
							if (err) {
								console.log(err);
								throw err;
							}
						},
					]);
					return res.json({
						success: true,
						data: 'deposit success',
					});
				}
			}
		);
	} catch (error) {
		res.status(400).json({ error });
		return next();
	}
};

Also change this userId variables in all of the files (/controller/bank/deposits.js, transfer.js, withdraw.js and /controller/user/getUser.js, getBalance.js)

After you are done. Please write in the docs.md to explain each of these function functionality in about 50 - 150 words each

  1. /controller/auth/jwt.js -> generateToken(), validateToken() and checkauth()
  2. /controller/auth/login.js -> login()
  3. /controller/auth/register.js -> register()
  4. what is req.header() and what will be the result of req.header('authorization').split(' ')[1];



Reminder: there will be extra point to student who move the sensitive data to .env