Node.js - express jwt (json web token)

Express jwt

JWT (Json Web Token) is een manier om gebruikers na het inloggen voor een bepaalde tijd toegang te geven tot bepaalde bronnen van jouw web applicatie. Op deze pagina wordt beschreven hoe je jouw routes binnen Express kunt laten werken met een jwt token. Meer achtergrondinformatie over jwt kun je hier vinden. JSON Web Token

JWT en het inlog proces

Nadat een gebruiker is ingelogd wordt er een JWT token gemaakt waarmee de gebruiker voor een bepaalde tijd toegang heeft.

  • De gebruiker vult zijn gebruikersnaam (bijvoorbeeld email) en wachtwoord in.
  • De server ontvangt de gebruikersnaam en het wachtwoord.
  • De server stelt vast dat gebruikersnaam en wachtwoord correct zijn, de gebruiker is ingelogd.
  • De server genereert een jwt token voor de gebruiker op basis van de gebruikersgegevens (bijvoorbeeld de email).
  • De server geeft een httpOnly cookie terug aan de gebruiker met daarin het token.
  • Wanneer de gebruiker daarna een opgvolgend verzoek indient naar de server om gegevens op te halen worden de gegevens uit het httpOnly cookie meegestuurd.
  • De server checkt de binnengekomen token en valideert deze (is het een geldig token, is de geldigheidsduur niet verstreken).
  • Is er geen token? De server reageert met een 401 code (niet ingelogd).
  • Is er een token maar is deze niet geldig? De server reageert met een 403 code (toegang geweigerd).
  • Is er een token en deze is geldig? De server geeft gehoor aan het verzoek en geeft de verwachte data terug.

Waarom een httpOnly cookie?

De client moet zorgen dat de token niet door derden kan worden gebruikt. Dan zouden anderen mogelijk kwaadwillenden toegang krijgen tot jouw website en jouw account. De token opslaan in localStorage is geen goed idee want localStorage kan door elke website worden uitgelezen. Een httpOnly cookie kan alleen worden uitgelezen door de betreffende website waarvoor het cookie is geïnstalleerd en is niet benaderbaar door JavaScript code in de browser. Een httpOnly cookie wordt meegestuurd met elk verzoek naar het domein waarvoor de cookie bedoeld is. Dit zorgt ervoor dat het zo veilig mogelijk is en het moeilijk is om de gegevens te stelen. Als iemand echter toegang heeft tot jouw cookies kan de token nog steeds worden gestolen. Het is dus ook belangrijk de geldigheidsduur niet te hoog te zetten.

jwt gebruiken in NodeJS voor Express

Het maken en controleren van jwt tokens hoeven we niet helemaal zelf te programmeren. Hier kunnen we de NodeJS jsonwebtoken module voor gebruiken. Run hiervoor het commando npm install jsonwebtoken

Om ervoor te zorgen dat je het binnenkomende cookie kunt uitlezen in Express heb je ook de cookieparser nodig. Run hiervoor het commando npm install cookie-parser

Geheime sleutel

Om jwt tokens te genereren heb je een geheime sleutel nodig. Het liefst een zo lang mogelijke random string. Deze kun je zelf genereren met behulp van de Node.js interne module 'crypto'.

            
import crypto from 'node:crypto';

console.log(crypto.randomBytes(64).toString('hex'));
            
        

Deze code levert bijvoorbeeld de output e45ca3952f403fd0476b357677f287b748e059942e6eacda376eed7499317eb09c204dbcd3415b7182baf09ec5de54889a6a4c03f3e890f55a2d614e660ff4e6 op.

Plak deze geheime sleutel in je .env bestand zodat je deze later kunt gebruiken bij het generen van je jwt tokens.

                
TOKEN_SECRET=e45ca3952f403fd0476b357677f287b748e059942e6eacda376eed7499317eb09c204dbcd3415b7182baf09ec5de54889a6a4c03f3e890f55a2d614e660ff4e6
                
            

Gebruik maken van jwt in Express

Hieronder vind je een code voorbeeld hoe je de jsonwebtoken module kunt gebruiken in Express. De standaard Express code is deels weggelaten uit dit voorbeeld.

            
//vereiste imports
import jwt from 'jsonwebtoken';
import cookies from 'cookie-parser';

//we laten express weten dat we cookies willen kunnen uitlezen
app.use(cookies());

//login route
app.post('/login', (req, res) => {
    //uitlezen van email en wachtwoord
    const email = req.body.email;
    const password = req.body.password;

    /*de loginUser() functie handelt het inloggen af
    (verbinding met database en check of de gegevens juist zijn).
     de sendLoginResponse() functie handelt het antwoord aan de client af.*/
    loginUser(email, password).
        then(loggedIn => sendLoginResponse(res, email, loggedIn));
});

/*route om te testen of we zijn ingelogd. De authenticateToken middleware functie
checkt of het token er is en geldig is.*/
app.get('/hello', authenticateToken, (req, res) => {
    res.send('Hello ' + req.user.email);
});

/*functie die het antwoord richting de client verzorgt.
Heeft het response object nodig, de email en een loggedIn boolean.*/
function sendLoginResponse(res, email, loggedIn) {
    //was het inloggen gelukt? (ofwel waren gebruikersnaam en wachtwoord correct)
    if (loggedIn) {
        //er wordt een access token gegenereerd met de generateAccessToken() functie.
        const token = generateAccessToken(email);
        //de server geeft een httpOnly cookie terug met daarin het token.
        res.status(200).cookie('token', token,
        {
            secure: true,
            httpOnly: true,
            sameSite: 'none'
        }).send({ loggedIn: true });
    } else {
        //het inloggen was niet gelukt dus dit laten we de client weten.
        res.send({ loggedIn: false });
    }
}

//funcie om een jwt access token te genereren
function generateAccessToken(email) {
    /*acess token wordt gegenereerd met de jwt sign() functie aan hand van de email,
     de geheime sleutel en een geldigheidsduur (1800 seconden is 30 minuten)*/
    return jwt.sign({ email: email }, process.env.TOKEN_SECRET, { expiresIn: 1800 });
}

//middleware functie die je kunt toevoegen aan een route die je beveiligd wilt hebben achter de login.
function authenticateToken(req, res, next) {
    //token wordt uitgelezen uit de cookie die wordt meegestuurd door de client
    const token = req.cookies.token;
    //in het geval er geen token is dan sturen we een 401 niet ingelogd terug.
    if (token == null){
        return res.sendStatus(401);
    }

    //is het token er wel dan gaan we het token controleren met de verify() functie van jwt
    jwt.verify(token, process.env.TOKEN_SECRET, (err, user) => {
        /*als er een fout is dan is het token niet geldig of is de geldigheidsduur verstreken.
        We sturen een 403 geen toegang. */
        if (err){
             return res.sendStatus(403);
        }
        /*we zetten de user op het request zodat we de beschikking hebben
         over het user object in de route.*/
        req.user = user;
        //next() functie zorgt ervoor dat we verder gaan waar we waren in de route
        next();
    });
}