Authentication vs Authorization

Authentication
Checking that a username and password are correct.
Authorization
Making sure that the user that sent a request to my server is the same one that logged in through my authentication process previously.

JWT Online Playground

JWT Content

  • header - encoding algorithm
  • payload - all info you want to store inside
  • signature

JWT Signature

It is made from the header + payload and a secret key.

It allow us to check if the client tampered with the token content.

The client can’t change the content and create a matching signature because he doesn’t have the secret key.

jwt makes it easy for developers to allow a client to login only once for a system that comprise of multiple servers.

NPM Package

npm i jsonwebtoken

let’s generate tokens

first we need to import from the jsonwebtoken package

const jwt = require('jsonwebtoken');

secret key: “123”

const token = jwt.sign({ nisim: 42 }, '123');
console.log(token);

output

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuaXNpbSI6NDIsImlhdCI6MTY3NzQyMzk3MH0.RtB6DnZfiQ6kALGTtqqbmVsw-AdkmP7S9lL7t4iff5U

same secret used to generate

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' +
	'eyJuaXNpbSI6NDIsImlhdCI6MTY3NzQyMzk3MH0.' +
	'RtB6DnZfiQ6kALGTtqqbmVsw-AdkmP7S9lL7t4iff5U';

try {
	const data = jwt.verify(token, '123');
	console.log('success! data:', JSON.stringify(data));
}
catch (error) {
	console.error('failed!');
}

output

success! data: { nisim: 42, iat: 1677423970 }

in case verify method receive different key

const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' +
	'eyJuaXNpbSI6NDIsImlhdCI6MTY3NzQyMzk3MH0.' +
	'RtB6DnZfiQ6kALGTtqqbmVsw-AdkmP7S9lL7t4iff5U';

try {
	const data = jwt.verify(token, '123');
	console.log('success! data:', JSON.stringify(data)});
}
catch (error) {
	console.error('failed!');
}

output

failed!

having secrets in the code base is a bad practice

const jwt = require('jsonwebtoken');
const secret = '123';
const result = jwt.sign({ nisim: 42 }, secret);

The solution of using secrets without encoding them plainly in your js source files is to use environment variables.

click here to read my guide for using environment variables in a node program.

If you know how already then you should know we have to do the following:

npm i dotenv

create .env file

.env

SECRET=123
require('dotenv').config();
const jwt = require('jsonwebtoken');
const result = jwt.sign({ nisim: 42 }, process.env.SECRET);

what should be our secret?

You can generate once in a node repl session.

execute the node command without arguments

node

run the following command in the node session:

require('crypto').randomBytes(64).toString('hex');

The crypto package is builtin so there is no need to install it.

output

7df15f6d1ad6dc74824957198d31f1ff884236b9940305eff153918c13566131e84449fccd580d99e0ca26e76ac26b42ad2ed73a61312ec4cb8e7c3d59604c32

so we can copy the output and paste it as the value of the SECRET variable so .env should look like this:

.env

SECRET=7df15f6d1ad6dc74824957198d31f1ff884236b9940305eff153918c13566131e84449fccd580d99e0ca26e76ac26b42ad2ed73a61312ec4cb8e7c3d59604c32

Login End Point

authenticated user receive an access token

app.post('/login', (req, res) => {
	// authenticate user

	// in case of successfully authentication
	const accessToken = jwt.sign({ name: req.body.username },
		process.env.SECRET);

	res.json({ accessToken });
});

How should the client send his token?

Let’s say the client want to add a new singer.

POST /singers endpoint

request body:

{
	"fname": "avi",
	"lname": "biter"
}

headers:

Content-Type: 'application/json'
Authorization: 'Bearer 7df15f6...'

The token should be assigned to the Authorization header after the prefix “Bearer "

There is a single space between the word Bearer and the token.

It’s a good practice to authorize your endpoints via a middleware.

for the sake of simplicity let’s authorize GET /sayHi end point

app.get('/sayHi', auth, (req, res) => {
	res.send(`hi ${req.user.name}!`);
});

Let’s implement the auth middleware.

This is a structure of a middleware function:

function auth(req, res, next) {
	// our implementation
}

The following function will extract the token from a request object:

function extractToken(req) {
	const authHeader = req.headers.authorization;
	return authHeader && authHeader.split(' ')[1];
}

Now we can use it to implement the auth middleware:

function auth(req, res, next) {
	const token = extractToken(req);

	if (!token) return res.sendStatus(401);

	try {
		const user = jwt.verify(token, process.env.SECRET);
		req.user = user;
		next();
	}
	catch (error) {
		res.sendStatus(403);
	}
}