Questa guida ti aiuta a gestire correttamente il ciclo di vita dei token PASETO e delle API Keys, inclusi refresh, scadenza e gestione errori.
I token PASETO hanno un ciclo di vita a due livelli:
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
Quando un access token scade, usa il refresh token per ottenerne uno nuovo:
curl -X POST https://nitro.italianonprofit.it/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"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
}
}
Ecco un esempio completo di gestione automatica del refresh:
class TokenManager {
private accessToken: string | null = null;
private refreshToken: string | null = null;
private refreshPromise: Promise<string> | null = null;
constructor() {
this.loadTokens();
}
private loadTokens() {
this.accessToken = localStorage.getItem("accessToken");
this.refreshToken = localStorage.getItem("refreshToken");
}
private saveTokens(accessToken: string, refreshToken?: string) {
this.accessToken = accessToken;
localStorage.setItem("accessToken", accessToken);
if (refreshToken) {
this.refreshToken = refreshToken;
localStorage.setItem("refreshToken", refreshToken);
}
}
async refreshAccessToken(): Promise<string> {
// Evita chiamate multiple simultanee
if (this.refreshPromise) {
return this.refreshPromise;
}
if (!this.refreshToken) {
throw new Error("No refresh token available");
}
this.refreshPromise = (async () => {
try {
const response = await fetch("https://nitro.italianonprofit.it/api/v1/auth/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: this.refreshToken }),
});
const data = await response.json();
if (data.status === "error") {
// Refresh token scaduto o non valido
this.clearTokens();
throw new Error("Refresh token expired");
}
const newAccessToken = data.data.accessToken;
this.saveTokens(newAccessToken);
return newAccessToken;
} finally {
this.refreshPromise = null;
}
})();
return this.refreshPromise;
}
async makeRequest(url: string, options: RequestInit = {}): Promise<Response> {
let accessToken = await this.getAccessToken();
const makeRequestWithToken = (token: string) => {
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
};
let response = await makeRequestWithToken(accessToken);
// Se token scaduto, prova a rinnovare
if (response.status === 401) {
try {
accessToken = await this.refreshAccessToken();
response = await makeRequestWithToken(accessToken);
} catch (error) {
// Refresh fallito, redirect a login
this.clearTokens();
throw error;
}
}
return response;
}
async getAccessToken(): Promise<string> {
if (!this.accessToken) {
throw new Error("Not authenticated");
}
return this.accessToken;
}
private clearTokens() {
this.accessToken = null;
this.refreshToken = null;
localStorage.removeItem("accessToken");
localStorage.removeItem("refreshToken");
}
logout() {
this.clearTokens();
}
}
Puoi rinnovare il token prima che scada per evitare interruzioni:
class ProactiveTokenManager extends TokenManager {
private refreshTimer: NodeJS.Timeout | null = null;
startProactiveRefresh() {
// Rinnova il token quando mancano 5 minuti alla scadenza
const refreshInterval = (3600 - 300) * 1000; // 55 minuti
this.refreshTimer = setInterval(async () => {
try {
await this.refreshAccessToken();
console.log("Token refreshed proactively");
} catch (error) {
console.error("Failed to refresh token:", error);
this.stopProactiveRefresh();
}
}, refreshInterval);
}
stopProactiveRefresh() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
}
}
{
"status": "error",
"error": {
"code": "REFRESH_TOKEN_INVALID",
"message": "Refresh token non valido o scaduto"
}
}
Soluzione: L'utente deve rifare login:
if (error.code === "REFRESH_TOKEN_INVALID") {
tokenManager.logout();
// Redirect a login
window.location.href = "/login";
}
{
"status": "error",
"error": {
"code": "INVALID_TOKEN",
"message": "Token non valido"
}
}
Soluzione: Rimuovi i token salvati e richiedi nuovo login.
Puoi vedere tutte le tue API Keys (senza il valore completo per sicurezza):
curl -X GET https://nitro.italianonprofit.it/api/v1/api-keys \
-H "Authorization: Bearer <tuo-access-token>"
Risposta:
{
"status": "success",
"data": [
{
"id": "uuid-1",
"name": "Chiave Produzione",
"prefix": "npf_prefisso1",
"scopes": ["organizations:read"],
"expiresAt": "2024-12-31T23:59:59.999Z",
"createdAt": "2024-01-01T00:00:00.000Z",
"active": true
}
]
}
Se un'API Key è stata compromessa o non è più necessaria:
curl -X DELETE https://nitro.italianonprofit.it/api/v1/api-keys/:id \
-H "Authorization: Bearer <tuo-access-token>"
Dove :id è l'ID dell'API Key da revocare.
Puoi disattivare temporaneamente un'API Key senza eliminarla:
curl -X POST https://nitro.italianonprofit.it/api/v1/api-keys/:id/deactivate \
-H "Authorization: Bearer <tuo-access-token>"
Questo è utile quando vuoi sospendere un'integrazione senza perdere completamente la chiave.
È buona pratica ruotare regolarmente le API Keys:
async function rotateApiKey(oldApiKeyId: string, newApiKeyName: string) {
// 1. Crea nuova API Key
const newApiKey = await createApiKey(newApiKeyName, ["organizations:read"]);
// 2. Aggiorna il codice per usare la nuova chiave
// (qui dovresti aggiornare la configurazione della tua applicazione)
// 3. Attendi qualche giorno per verificare che tutto funzioni
// 4. Revoca la vecchia API Key
await revokeApiKey(oldApiKeyId);
}
{
"status": "error",
"error": {
"code": "TOKEN_EXPIRED",
"message": "Token non valido o scaduto"
}
}
Gestione automatica:
async function makeRequestWithAutoRefresh(url: string, options: RequestInit) {
const tokenManager = new TokenManager();
try {
return await tokenManager.makeRequest(url, options);
} catch (error) {
if (error.message === "Refresh token expired") {
// Redirect a login
window.location.href = "/login";
}
throw error;
}
}
{
"status": "error",
"error": {
"code": "AUTHENTICATION_REQUIRED",
"message": "Autenticazione richiesta"
}
}
Soluzione: Verifica che il token o l'API Key siano presenti nell'header Authorization.
{
"status": "error",
"error": {
"code": "FORBIDDEN",
"message": "Accesso negato"
}
}
Soluzione: Verifica che l'API Key abbia gli scope necessari o che l'utente abbia i permessi richiesti.
import axios from "axios";
const tokenManager = new TokenManager();
axios.interceptors.request.use(async (config) => {
const token = await tokenManager.getAccessToken();
config.headers.Authorization = `Bearer ${token}`;
return config;
});
axios.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// Se errore 401 e non abbiamo già provato a refreshare
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newToken = await tokenManager.refreshAccessToken();
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return axios(originalRequest);
} catch (refreshError) {
// Refresh fallito, redirect a login
tokenManager.logout();
window.location.href = "/login";
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
import requests
import os
from datetime import datetime, timedelta
class ApiKeyManager:
def __init__(self):
self.api_key = os.environ.get('ITALIANONPROFIT_API_KEY')
self.created_at = os.environ.get('ITALIANONPROFIT_API_KEY_CREATED_AT')
def is_expiring_soon(self, days_threshold=30):
if not self.created_at:
return False
created = datetime.fromisoformat(self.created_at)
expires_at = created + timedelta(days=365)
days_until_expiry = (expires_at - datetime.now()).days
return days_until_expiry < days_threshold
def rotate_if_needed(self, access_token, key_name):
if self.is_expiring_soon():
print(f"API Key scade tra {self.days_until_expiry()} giorni. Rotazione consigliata.")
# Implementa logica di rotazione
# ...
def make_request(self, url, method='POST', **kwargs):
headers = kwargs.get('headers', {})
headers['Authorization'] = f'Bearer {self.api_key}'
kwargs['headers'] = headers
response = requests.request(method, url, **kwargs)
if response.status_code == 401:
raise Exception('API Key non valida o scaduta')
return response