reactjsnode.jsaxiosjwtrefresh-token

Reactjs and Nodejs access/refresh jwt token authentication


I have a problem with the refresh token, login API sends accessToken (expiration: 30s), refreshToken (expiration: 5min), and cookie 'refreshCookie' (expiration: 5min). This API works correctly.

login.js (backend)

const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { promisify } = require('util');
const conn = require('../db_connection');
require('dotenv').config();

const router = express.Router(); // Inizializzazione di un oggetto router
const query = promisify(conn.query).bind(conn); // Permette di far eseguire le query del database in modo asincrono

// Route per il login
router.post('/login', async (req, res) => {
    try {
        const { email, password } = req.body; // Ottieni i dati dal body della richiesta

        // Verifica se l'email è registrata
        const users = await query('SELECT * FROM users WHERE email = ?', [email]);

        // Email non registrata
        if (users.length === 0) {
            return res.status(401).json({ error: 'Utente non registrato' });
        }

        const user = users[0];
        const userId = user.user_id;

        // Verifica se la password è corretta
        const passwordMatch = await bcrypt.compare(password, user.password);

        // Password sbagliata
        if (!passwordMatch) {
            return res.status(401).json({ error: 'La password è sbagliata' });
        }

        // Creazione di un token jwt di accesso
        const accessToken = jwt.sign({ user_id: userId }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '30s' });

        // Creazione di un refresh token
        const refreshToken = jwt.sign({ user_id: userId }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '5m' }) 

        // Creazione di un cookie per l'autenticazione dell'utente
        res.cookie('refreshCookie', refreshToken, {
            httpOnly: true, // Accessibile solo da server web
            secure: false, // true = solo https, false = https e http
            sameSite: 'None', // Cross-site cookie (il frontend è su un server diverso)
            maxAge: 5 * 60 * 1000 // 5 min in ms
        });

        res.json({ accessToken, refreshToken });
    } catch (error) {
        console.log("errore login: ", error);
        return res.status(500).json({ error: "Errore durante il login" });
    }
});

module.exports = router;

This API is for the token refresh, when accessToken has expired, the client should send a request at endpoint/refresh-token to get a new access token if refreshToken has not expired.

refresh_token.js (backend)

const express = require('express');
const jwt = require('jsonwebtoken');
const { promisify } = require('util');
const conn = require('../db_connection');
require('dotenv').config();

const router = express.Router(); // Inizializzazione di un oggetto router
const query = promisify(conn.query).bind(conn); // Permette di far eseguire le query del database in modo asincrono

// Route per refreshare il token jwt
router.post('/refresh-token', async (req, res) => {
    const cookies = req.cookies;

    if (!cookies?.refreshCookie) {
        return res.status(401).json({ error: "Utente non autorizzato" });
    }

    const refreshToken = cookies.refreshCookie;

    try {
        const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);

        const user = await query("SELECT * FROM users WHERE user_id = ?", [decoded.user_id]); // Cerca l'utente nel database
        
        // Utente non trovato
        if (!user) {
            res.status(401).send({ error: "Unauthorized" });
        }

        // Creazione di un token jwt di accesso
        const accessToken = jwt.sign({ user_id: decoded.user_id }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '30s' });
        
        res.json({ accessToken });
    } catch (error) {
        console.log("\nErrore refresh: ", error);
        return res.status(403).send({ error: "Forbidden" });
    }
});

module.exports = router;

this is my frontend request, someone could help me to make the client do a request at endpoint/refresh-token when accessToken has expired to get a new access token? I want to use Axios and cookies to save tokens. I want the token to be refreshed only when the old token has expired.

login.js (frontend)

// Funzione che invia una richiesta al server e restituisce la risposta
      async function handleSubmit(e) {
        e.preventDefault();
        try {
            const response = await axios.post(`${config.API_BASE_URL}/api/login`, { formData }, {
                withCredentials: true
            });
            const { accessToken } = response.data;
            Cookies.set('accessToken', accessToken);
            navigate("/");
        } catch (error) {
            console.error("Errore login: ", error);
            setError(error.response.data.error); // Gestisci altri tipi di errore di login
        }
    }

I have tried with Axios interceptor but it doesn't work, I prefer to use this method.


Solution

  • You don't have to fetch in the background every second(s) just to check if access token is still valid. Let the user/client trigger the action for you.

    Scenario:

    1. the logged in user send a request (e.g. by refreshing the page, navigating to other route).
    2. server say recieved accessToken expired.
    3. client recieve the message that accessToken expired.
    4. therefore client request a new accessToken by sending refreshToken to your /refresh-token endpoint.
    5. server verify that sent refreshToken still valid.
    6. server generate a new accessToken and send it to client.