marco.bonaaa
marco.bonaaa

Reputation: 3

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.

Upvotes: 0

Views: 131

Answers (2)

kal_stackO
kal_stackO

Reputation: 77

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.

Upvotes: 0

Snake_py
Snake_py

Reputation: 582

First of all 30 second seems to me very short. Maybe consider to extend that time.

I guess what you want here is setting a setTimeout or a setInterval. Both are global functions available by js.

Basically you want to do soemthing like this:

Interval (prefered solution)


const delay = 30 * 1000; // time in ms


// Now setting the interval your submit will run every 30 seconds
setInterval(handleSubmit, delay);

To this solution you can also add some sort of checking if the access token is really outdated, if it is not you can wait for it (e.g. using setTimeout).

Timeout (just here as an alternative)

Some people would probably consider this as a valid set up. But I would not reccomend it! But to be complete here would be soutlion using setTimeout.



const delay = 30 * 1000; // time in ms

const submitWrapper = async () => {
 const res = await handleSubmit();
 setTimeout(submitHandler, delay);
} 


setTimeout(submitHandler, delay);

Note I am only showing how to call the intervall or the timeout. How this fits into your initial call or into your app is something else to be discussed. Feel free to ask a little more specific questions for further support.

Upvotes: 1

Related Questions