Summary
In the lab we’re going to build today, we’ll talk about JWTs and how they can affect the security of your website. This is a lab that’s been requested, and I could not be happier that the community is actually suggesting things for me to build. It’s a privilege to do this.
JWT As Security Measure
JSON Web Tokens are used a lot by developers. They provide a way to send ‘data’ with them (e.g., a role from a user), to check for authorization. A JWT consists of 3 parts, separated by a .
.
Let’s take this JWT as an example.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30
The first part eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
is called ‘the header’. It holds information on the type of the JWT. The second part eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
is the payload, which holds the claims. This is where that ‘data’ lives. If you decode this with Base64url
you’ll learn that this token belongs to John Doe and that he is an administrator. The last part is the important part. This is the signature. Each JWT should be signed with a strong password. Ideally that password is saved in a .env
file on the server and not in the code.
So now you might wonder, well if it’s signed, nothing can go wrong right? Well, if that were true, I wouldn’t spend my evenings writing these kinds of blog posts now would I 😅.
Let’s Build The Lab Already
You’re right to think this by now. Enough theory, let’s do some hands on keyboard. As always in this series, I’ll provide you with the lab structure and some code to get us started.
Lab Tree
/mediumLabs
└── /JWT
├── server.js
├── login.html
├── index.html
├── admin.html
├── /node_modules
├── /package-lock.json
└── /package.json
Code
server.js
const express = require(‘express’);
const jwt = require(‘jsonwebtoken’);
const path = require(‘path’);
const Database = require(‘better-sqlite3’);
const app = express();
const JWT_SECRET = ‘secret’;
const db = new Database(‘:memory:’);
db.exec(CREATE TABLE users ( id INTEGER PRIMARY KEY, username TEXT, password TEXT, role TEXT ));
db.prepare(“INSERT INTO users (username, password, role) VALUES (‘user’, ‘password’, ‘user’)“).run();
app.use(express.urlencoded({ extended: false }));
app.get(’/’, (req, res) ⇒ res.sendFile(path.join(__dirname, ‘login.html’)));
function getToken(req) {
const match = (req.headers.cookie || ”).match(/token=([^;]+)/);
return match ? match[1] : null;
}
app.post(‘/login’, (req, res) ⇒ {
const { username, password } = req.body;
const user = db.prepare(‘SELECT * FROM users WHERE username = ? AND password = ?‘).get(username, password);
if (!user) return res.redirect(’/?error=Invalid+credentials’);
const token = jwt.sign({ username: user.username, role: user.role }, JWT_SECRET);
res.setHeader(‘Set-Cookie’, token=${token}; Path=/);
res.redirect(‘/index.html’);
});
app.get(‘/index.html’, (req, res) ⇒ {
try {
res.sendFile(path.join(__dirname, ‘index.html’));
} catch {
res.redirect(’/’);
}
});
app.get(‘/admin’, (req, res) ⇒ {
});
app.listen(3000, () ⇒ console.log(‘Listening on http://localhost:3000’));
login.html
Login
index.htmlWelcome!
You are logged in as .
admin.html
Admin Panel
Welcome, admin. Here is the secret flag:
FLAG{JWT}
Back to Home Let’s Add Some Vulnerabilities Ok, so now we have our base code, and we need to implement the functionality that will make sure only people with the role admin can navigate to /admin . First off, run npm run dev to see your code actually start. In a very, very wrong way we already tried implementing security in admin.html . When you look inside the