Autenticazione

PASETO Tokens

Autenticazione utenti con token PASETO (access e refresh tokens)

L'API Italianonprofit utilizza PASETO (Platform-Agnostic Security Tokens) per l'autenticazione degli utenti. PASETO è un'alternativa moderna ai JWT con migliori garanzie di sicurezza.

Sistema a Doppio Token

Il sistema implementa un meccanismo di autenticazione con due tipi di token:

  • Access Token: Token a breve durata (default: 1 ora) usato per autenticare le richieste API
  • Refresh Token: Token a lunga durata (default: 7 giorni) usato solo per ottenere nuovi access token

Questa separazione migliora la sicurezza limitando la validità dei token di accesso e riducendo i rischi in caso di compromissione.

Configurazione Tempi di Scadenza

I tempi di scadenza dei token possono essere configurati tramite variabili d'ambiente:

  • ACCESS_TOKEN_EXPIRY: Durata dell'access token in secondi (default: 3600 = 1 ora)
  • REFRESH_TOKEN_EXPIRY: Durata del refresh token in secondi (default: 604800 = 7 giorni)

Esempio per impostare un access token valido per 30 minuti:

ACCESS_TOKEN_EXPIRY=1800

Endpoint di Autenticazione

Login

Ottieni una coppia di token (access + refresh) tramite login:

POST /api/v1/auth/login

Body della richiesta:

{
  "email": "utente@example.com",
  "password": "password123"
}

Risposta:

{
  "status": "success",
  "data": {
    "accessToken": "v4.public.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ...",
    "refreshToken": "v4.public.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ...",
    "expiresIn": 3600,
    "user": {
      "id": 123,
      "email": "utente@example.com",
      "name": "Nome Utente",
      "role": "user"
    }
  }
}

Verifica Token

Verifica la validità di un token:

POST /api/v1/auth/verify

Body della richiesta:

{
  "token": "v4.public.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ..."
}

Risposta:

{
  "status": "success",
  "data": {
    "valid": true,
    "payload": {
      "sub": "123",
      "name": "Nome Utente",
      "email": "utente@example.com",
      "role": "user",
      "type": "access",
      "iat": 1516239022,
      "exp": 1516242622
    }
  }
}

Refresh Token

Rinnova un access token usando il refresh token:

POST /api/v1/auth/refresh

Body della richiesta:

{
  "token": "v4.public.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ..."
}

Nota: Il token deve essere un refresh token, non un access token.

Risposta:

{
  "status": "success",
  "data": {
    "accessToken": "v4.public.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ...",
    "expiresIn": 3600
  }
}

Utilizzo dei Token

Per accedere alle route protette, includi l'access token nell'header Authorization:

Authorization: Bearer v4.public.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ...

Esempio con curl

curl -X GET https://nitro.italianonprofit.it/api/v1/users/me \
  -H "Authorization: Bearer v4.public.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ..."

Payload del Token

Il payload dell'utente autenticato è disponibile nell'oggetto event.context.user in tutti gli handler delle route protette. Questo è impostato automaticamente dal middleware di autenticazione globale.

Struttura del Payload

{
  sub: string; // ID utente
  name: string; // Nome utente
  email: string; // Email utente
  role: string; // Ruolo (user, admin)
  type: "access" | "refresh"; // Tipo di token
  iat: number; // Issued at (timestamp)
  exp: number; // Expiration (timestamp)
}

Accesso al Payload (Lato Server)

export default defineEventHandler((event) => {
  // Accedi direttamente ai dati dell'utente
  const user = event.context.user;

  return {
    message: `Benvenuto, ${user.name}!`,
    userId: user.sub,
    role: user.role,
  };
});

Tipi di Token

I token PASETO contengono un campo type che ne indica l'utilizzo:

  • type: "access" - Token utilizzabile per autenticare le richieste
  • type: "refresh" - Token utilizzabile solo per ottenere nuovi access token

Il sistema verifica automaticamente che:

  • I refresh token non vengano utilizzati per l'autenticazione
  • Gli access token non vengano usati come refresh token

Gestione del Ciclo di Vita

Flusso Completo

1. Login → Ottieni accessToken + refreshToken
2. Usa accessToken per richieste API
3. Quando accessToken scade → Usa refreshToken per ottenere nuovo accessToken
4. Continua con nuovo accessToken
5. Quando refreshToken scade → Riloggare

Implementazione Client-Side

class AuthManager {
  private accessToken: string | null = null;
  private refreshToken: string | null = null;

  async login(email: string, password: string) {
    const response = await fetch("/api/v1/auth/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });

    const data = await response.json();
    this.accessToken = data.data.accessToken;
    this.refreshToken = data.data.refreshToken;

    // Salva in localStorage o secure storage
    localStorage.setItem("accessToken", this.accessToken);
    localStorage.setItem("refreshToken", this.refreshToken);
  }

  async refreshAccessToken() {
    if (!this.refreshToken) {
      throw new Error("No refresh token available");
    }

    const response = await fetch("/api/v1/auth/refresh", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ token: this.refreshToken }),
    });

    const data = await response.json();
    this.accessToken = data.data.accessToken;
    localStorage.setItem("accessToken", this.accessToken);

    return this.accessToken;
  }

  async makeAuthenticatedRequest(url: string, options: RequestInit = {}) {
    if (!this.accessToken) {
      throw new Error("Not authenticated");
    }

    const response = await fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        Authorization: `Bearer ${this.accessToken}`,
      },
    });

    // Se token scaduto, prova a rinnovare
    if (response.status === 401) {
      await this.refreshAccessToken();
      // Riprova la richiesta
      return fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          Authorization: `Bearer ${this.accessToken}`,
        },
      });
    }

    return response;
  }
}

Sicurezza

Best Practices

  1. Mai esporre token in URL o log
  2. Usa HTTPS sempre in produzione
  3. Salva token in secure storage (non localStorage per produzione)
  4. Gestisci scadenze implementando refresh automatico
  5. Revoca token quando l'utente fa logout

Logout

Per invalidare i token, semplicemente rimuovili dal client. I token non possono essere revocati lato server fino alla loro scadenza naturale.

logout() {
  this.accessToken = null;
  this.refreshToken = null;
  localStorage.removeItem('accessToken');
  localStorage.removeItem('refreshToken');
}

Prossimi Passi