Authentication with Nodejs and JWTs - a simple example
When it comes to JWTs, most tutorials help you build a full implementation in Node. However, they rarely stop to show just the basics - just the esssential parts that need to be there for JWT authentication to work, and nothing more.
When it comes to JWTs, most tutorials help you build a full implementation in Node. However, they rarely stop to show just the basics - just the esssential parts that need to be there for JWT authentication to work, and nothing more.
This article aims to give a simple, straightforward overview of the steps needed to add JWT auth to a Node app.
We'll start from an unprotected API with a super secret resource and go towards securing it with JSON Web Tokens (JWTs).
What we'll build
Our API will only contain two endpoints:
/login
- will return the token/super-secret-resource
- the information that only logged in users should access
Step 1: Creating a Node API with Express
Let's start from the barebones - just an API that returns a very important resource, that should only be accessed by logged in users. This endpoint should return 401 Unauthorized
if the user is not logged in. Since we don't have the /login
endpoint yet, it will just always return 401 for now.
Create a new folder for the project, run npm init -y
to initialize the project and run npm install express
to install express
.
Next, create a server.js
file for the node app. In its simplest form, this is how the API would look:
// server.js
const express = require("express");
const app = express();
app.get("/super-secure-resource", (req, res) => {
return res
.status(401)
.json({ message: "You need to be logged in to access this resource" });
});
app.listen(3001, () => {
console.log("API running on localhost:3001");
});
To start the API, run node server.js
in the console.
To check that the endpoint cannot be accessed, run curl
:
curl -i localhost:3001/super-secure-resource
// Will output:
//
// HTTP/1.1 401 Unauthorized
// X-Powered-By: Express
// Content-Type: application/json; charset=utf-8
// Content-Length: 62
// ETag: W/"3e-eJQzoUv1nk6AKpsmnnXBmFvKrI4"
// Date: Sat, 22 Jan 2022 09:05:53 GMT
// Connection: keep-alive
// Keep-Alive: timeout=5
//
// {"message":"You need to be logged in to access this resource"}
The -i
flag tells curl
to also return the headers, so we can see the HTTP status code.
Notice the 401
HTTP status code - which stands for "Unauthorized" - and the message we returned from the endpoint.
Step 2: Return a JWT token on successful login
Next, we want to allow users to login and send back a verified token if their user and password are correct.
Since setting up a full database of users and password is out of the scope of this article, we'll just hardcode an admin/admin
user and check for that as an example.
The first step is to install the npm jsonwebtoken module. This package will help us sign and verify JWT tokens.
npm install jsonwebtoken
Then, the endpoint will look like this:
const express = require("express");
const jsonwebtoken = require("jsonwebtoken");
// The secret should be an unguessable long string (you can use a password generator for this!)
const JWT_SECRET =
"goK!pusp6ThEdURUtRenOwUhAsWUCLheBazl!uJLPlS8EbreWLdrupIwabRAsiBu";
const app = express();
app.use(express.json());
app.post("/login", (req, res) => {
const { username, password } = req.body;
console.log(`${username} is trying to login ..`);
if (username === "admin" && password === "admin") {
return res.json({
token: jsonwebtoken.sign({ user: "admin" }, JWT_SECRET),
});
}
return res
.status(401)
.json({ message: "The username and password your provided are invalid" });
});
Some things to note here:
- the
JWT_SECRET
should be an unguessable long string - the part that actually does the signing is:
jsonwebtoken.sign({ user: "admin" }, JWT_SECRET)
We can check a token is returned with curl:
curl -X POST http://localhost:3001/login --header "Content-Type: application/json" --data '{ "username": "admin", "password": "admin" }'
// {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2NDI4NDQ0NTR9.O_710gS-6t9nmqvW0_E1GlP7MdoLMFR85GXUUeskNi8"}
You can decode the token and see what it contains on jwt.io
Some things to note:
- The "Header" of the token contains the algorithm used - in our case, the algorithm for signing the token is HS256
- You can input your secret in the bottom right to have jwt.io verify the token signature (if the signature is verified, it means the token hasn't been tampered with)
- Besides the
{ user: admin }
which we encoded, there's also another property there -iat
, added by thejsonwebtoken
library; this stands for "Issued at" - i.e. when the server created the token (as Unix timestamp)
Step 3: Send the token to the /super-secure-resource and verify it to allow access
Now let's use that token to allow the logged in user to access the restricted resource!
In order to do that, we will need to send the token to the API (as an Authorization
header) and then verify the token on the server:
import jwt from "jsonwebtoken";
app.get("/super-secure-resource", (req, res) => {
if (!req.headers.authorization) {
return res.status(401).json({ error: "Not Authorized" });
}
// Bearer <token>>
const authHeader = req.headers.authorization;
const token = authHeader.split(" ")[1];
try {
// Verify the token is valid
const { user } = jwt.verify(token, process.env.JWT_SECRET);
return res.status(200).json({
message: `Congrats ${user}! You can now accesss the super secret resource`,
});
} catch (error) {
return res.status(401).json({ error: "Not Authorized" });
}
});
When doing curl
, the resource will now be accessible 💪:
curl -i localhost:3001/super-secure-resource --Header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2NDI4NDYzMzV9.gQj-qjoQwtvyU2XzER6yiT-T4DwYphjMft-kogW978c'
// Will return:
//
// HTTP/1.1 200 OK
// X-Powered-By: Express
// Content-Type: application/json; charset=utf-8
// Content-Length: 69
// ETag: W/"45-9rvFFDk8vkfruRd3Ok0vQNGIdMk"
// Date: Sat, 22 Jan 2022 10:15:03 GMT
// Connection: keep-alive
// Keep-Alive: timeout=5
//
// {"message":"Congrats! You can now accesss the super secret resource"}
Why this works:
- By signing the token, you ensure that the server can check that whatever token it receives, was created by itself
- If you try to tamper with the token, you'll invalidate it! The server will tell it's been tampered, so it will not authorise you!
- Try it out: take the token, change it in jwt.io and try again -> it will fail!
Make it yours!
I specifically kept the source code for this article very barebones. Go ahead and improve it!
- Add
nodemon
to automatically reload the server when you change the API - Add
nodeenv
and move the JWT_SECRET to as an environment variable - Setup an actual database with users and passwords
- Add more endpoints and move the code that verifies the token in an express middleware
Comments ()