Autenticazione
JWT di sessione, chiavi API e firma HMAC servizio-a-servizio: formati, scadenze, rotazione e revoca.
Blu Pantheon supporta tre metodi di autenticazione, pensati per contesti diversi. Tutte le richieste devono viaggiare su HTTPS: le chiamate in chiaro vengono rifiutate a livello di edge.
| Metodo | Quando usarlo | Scadenza | Revoca | Dove conservarlo |
|---|---|---|---|---|
| Bearer JWT | Applicazioni con login utente, Console, app mobile | 15 minuti (access), 30 giorni (refresh) | Logout o revoca sessione | Memoria del processo o cookie HttpOnly |
| Chiave API | Integrazioni server-to-server, script, ETL | Nessuna, opzionalmente a termine | Revoca immediata dalla Console | Secret manager o variabile ambiente del backend |
| HMAC-SHA256 | Comunicazione tra servizi interni e partner certificati | Firma valida 5 minuti | Rotazione del segreto condiviso | Secret manager, mai nel codice |
Regola non negoziabile
Nessuna credenziale deve finire in codice frontend, bundle JavaScript, repository Git, log applicativi o
URL. Una chiave bp_live_... committata in Git va considerata compromessa e revocata immediatamente,
anche se il commit viene rimosso dalla history.
1. Bearer JWT (sessioni utente)
Usa questo metodo quando l'azione è compiuta per conto di un utente e devi ereditarne ruoli e permessi.
Login
/identity/v1/auth/loginPubblicoAutentica un utente con email e password e restituisce la coppia access token / refresh token.
{
"email": "mario.rossi@acme.it",
"password": "•••••••••••",
"tenantSlug": "acme"
}{
"type": "success",
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfNGE3YjFlOTAi...",
"refreshToken": "rt_8c1d4f7a2b9e0563a1d8c47f2e9b0355",
"tokenType": "Bearer",
"expiresIn": 900,
"user": {
"id": "usr_4a7b1e90",
"email": "mario.rossi@acme.it",
"roles": ["tenant_admin"]
}
}Il campo tenantSlug è obbligatorio solo se l'utente appartiene a più tenant. In sua assenza, quando
esistono più appartenenze, la risposta è 409 con codice TENANT_SELECTION_REQUIRED e l'elenco dei
tenant disponibili in details.
Uso dell'access token
curl https://api.blupantheon.com/console/v1/sessions \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."Claim contenuti nel token
L'access token è un JWT firmato con RSA (RS256). La chiave pubblica è pubblicata su
https://api.blupantheon.com/identity/v1/.well-known/jwks.json e va usata per la verifica lato tuo.
Claim principali
| Parametro | Tipo | Obblig. | Default | Descrizione |
|---|---|---|---|---|
sub | string | Sì | — | Identificativo utente, ad esempio usr_4a7b1e90. |
tid | string | Sì | — | Identificativo del tenant attivo per la sessione. |
tslug | string | Sì | — | Slug leggibile del tenant, utile per il routing multi-tenant. |
roles | string[] | Sì | — | Ruoli assegnati all’utente nel tenant attivo. |
scope | string | No | — | Permessi effettivi separati da spazio, nel formato servizio:risorsa:azione. |
exp | number | Sì | — | Timestamp Unix di scadenza. Access token: 900 secondi dall’emissione. |
iat | number | Sì | — | Timestamp Unix di emissione. |
jti | string | Sì | — | Identificativo univoco del token, usato per la revoca puntuale. |
iss | string | Sì | — | Emittente, sempre https://api.blupantheon.com/identity. |
Payload decodificato di esempio:
{
"sub": "usr_4a7b1e90",
"tid": "tnt_2b8e4f11",
"tslug": "acme",
"roles": ["tenant_admin"],
"scope": "identity:users:read identity:users:write console:sessions:read",
"iss": "https://api.blupantheon.com/identity",
"iat": 1788515535,
"exp": 1788516435,
"jti": "jti_5d0a9b3c"
}Verifica sempre firma e scadenza
Non usare i claim senza validare la firma con le chiavi JWKS e senza controllare exp e iss.
Le chiavi ruotano periodicamente: memorizza il JWKS in cache per un massimo di 10 minuti e
rileggilo quando incontri un kid sconosciuto.
Refresh
/identity/v1/auth/refreshPubblicoScambia un refresh token valido con una nuova coppia di token. Il refresh token usato viene invalidato (rotazione).
{
"refreshToken": "rt_8c1d4f7a2b9e0563a1d8c47f2e9b0355"
}{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "rt_1f6b2e94d7c3a08516ba9e2f7c4d1088",
"tokenType": "Bearer",
"expiresIn": 900
}Il refresh token è a uso singolo. Se lo stesso refresh token viene presentato due volte, l'intera
famiglia di token viene revocata e la risposta è 401 con codice REFRESH_TOKEN_REUSED: è il segnale
di un possibile furto di credenziali e richiede un nuovo login.
Logout
/identity/v1/auth/logoutBearer JWTRevoca la sessione corrente. Con allSessions a true revoca tutte le sessioni attive dell'utente.
{
"refreshToken": "rt_1f6b2e94d7c3a08516ba9e2f7c4d1088",
"allSessions": false
}{}Flusso MFA
Se l'utente ha l'autenticazione a più fattori attiva, il login non restituisce token ma una sfida.
Login iniziale
POST /identity/v1/auth/login risponde 200 con un payload di tipo mfa_required:
{
"type": "mfa_required",
"mfaToken": "mfa_3e7c9a15b2d84f06",
"methods": ["totp", "email"],
"expiresIn": 300
}Invio del codice
Per i metodi che richiedono l'invio di un codice (email, SMS) chiama
POST /identity/v1/auth/mfa/challenge con mfaToken e method. Per TOTP questo passaggio non serve.
{
"mfaToken": "mfa_3e7c9a15b2d84f06",
"method": "email"
}Completamento
POST /identity/v1/auth/mfa/verify scambia il codice con i token definitivi.
{
"mfaToken": "mfa_3e7c9a15b2d84f06",
"code": "418206"
}{
"type": "success",
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "rt_9b0e4d7a1c62f358",
"tokenType": "Bearer",
"expiresIn": 900
}Il mfaToken scade dopo 5 minuti (MFA_TOKEN_EXPIRED). Dopo 5 codici errati l'account viene bloccato
temporaneamente e la risposta è 423 con codice ACCOUNT_LOCKED. I codici di recupero monouso possono
essere usati al posto del codice, sullo stesso endpoint, nel campo code.
Per la configurazione lato prodotto vedi Autenticazione e MFA.
2. Chiavi API
Le chiavi API identificano un'applicazione, non una persona. Non scadono per impostazione predefinita e non richiedono refresh: sono la scelta corretta per job schedulati, integrazioni ERP e servizi backend.
Creazione
Definisci nome e ambiente
Usa un nome che identifichi il consumatore, non la persona che la crea: erp-sync-produzione è utile,
chiave-mario no.
Seleziona i permessi
Assegna solo gli scope necessari, nel formato servizio:risorsa:azione. Una chiave che deve solo
esportare conversazioni richiede console:sessions:read e nulla di più.
Copia il segreto
Il valore completo viene mostrato una sola volta. Salvalo subito nel secret manager.
Chiave API
bp_live_••••••••••••••••Visibile solo alla creazione. Non recuperabile in seguito.
Formato e prefissi
| Prefisso | Ambiente | Note |
|---|---|---|
bp_live_ | Produzione | Opera su dati reali |
bp_stg_ | Staging | Dati anonimizzati |
bp_test_ | Sandbox | Dati sintetici, nessun effetto reale |
La chiave è composta da prefisso e da un segreto casuale. Nel database Blu Pantheon conserva solo l'hash SHA-256 del segreto e i primi caratteri in chiaro, usati per identificare la chiave nella Console e nei log di audit. Per questo motivo il valore completo non è recuperabile dopo la creazione.
Uso
curl https://api.blupantheon.com/console/v1/clients?limit=50 \
-H "Authorization: Bearer bp_live_xxxxxxxxxxxxxxxx"Mai in query string
Non passare la chiave come parametro di query (?apiKey=...). Le query string finiscono nei log dei
proxy, nella cronologia del browser e negli header Referer. L'unico canale supportato è l'header
Authorization.
Scope e permessi
Una chiave non può avere più permessi dell'utente che l'ha creata al momento della creazione. Se i permessi dell'utente vengono ridotti in seguito, la chiave mantiene gli scope assegnati fino alla revisione esplicita: rivedi le chiavi attive a ogni cambio di ruolo rilevante.
Quando una chiave chiama un endpoint fuori dai propri scope la risposta è 403 con codice
INSUFFICIENT_PERMISSIONS e l'elenco degli scope mancanti in details.
Rotazione
Crea la nuova chiave
Genera una seconda chiave con gli stessi scope, con un nome che indichi la data (erp-sync-2026-09).
Distribuisci e verifica
Aggiorna il secret manager e riavvia i consumatori. Verifica nella Console che la nuova chiave registri traffico e che le chiamate risultino andate a buon fine.
Revoca la vecchia
Solo dopo aver confermato che la chiave precedente non registra più utilizzi da almeno 24 ore, revocala. La revoca è immediata e non reversibile.
Consigliamo una rotazione almeno semestrale e una rotazione immediata dopo ogni sospetto di esposizione o dopo l'uscita di una persona con accesso al secret manager.
Revoca
/identity/v1/api-keys/{keyId}Bearer JWTRevoca immediatamente una chiave API. Le richieste in corso non vengono interrotte, le successive falliscono con 401.
{
"id": "key_6d3f0a82",
"name": "erp-sync-produzione",
"status": "revoked",
"revokedAt": "2026-09-04T10:32:15.482Z"
}Dopo la revoca ogni chiamata con quella chiave riceve 401 con codice API_KEY_REVOKED.
3. HMAC servizio-a-servizio
Usato per la comunicazione tra servizi interni e per i partner che richiedono garanzie di integrità
oltre alla riservatezza del canale TLS. Ogni consumatore riceve un keyId pubblico e un segreto condiviso.
Header richiesti
Header di firma
| Parametro | Tipo | Obblig. | Default | Descrizione |
|---|---|---|---|---|
X-BP-Key-Id | string | Sì | — | Identificativo pubblico della coppia di chiavi, ad esempio svc_billing_01. |
X-BP-Timestamp | number | Sì | — | Timestamp Unix in secondi al momento della firma. Tolleranza massima 300 secondi. |
X-BP-Nonce | string | Sì | — | Valore casuale univoco per richiesta, usato per prevenire replay. |
X-BP-Signature | string | Sì | — | Firma nel formato t=<unix>,v1=<hex>, dove la parte v1 è l’HMAC-SHA256 in esadecimale minuscolo. |
Stringa da firmare
Gli elementi vengono concatenati con il carattere di ritorno a capo \n, nell'ordine seguente:
<METODO>\n
<PATH_CON_QUERY>\n
<X-BP-TIMESTAMP>\n
<X-BP-NONCE>\n
<SHA256_HEX_DEL_BODY>Esempio concreto per POST /billing/v1/usage con body {"tenantId":"tnt_2b8e4f11","units":42}:
POST
/billing/v1/usage
1788515535
n_7c2a94f0e13b
3f8a1c9e2b7d0546a83f1c9b0e7d2a45f8c31b9e0d7a24f6c8b135e0a97d4f26Implementazione in Node.js:
import { createHash, createHmac, randomBytes } from 'node:crypto';
export function signRequest(
method: string,
pathWithQuery: string,
body: string,
keyId: string,
secret: string,
): Record<string, string> {
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = `n_${randomBytes(6).toString('hex')}`;
const bodyHash = createHash('sha256').update(body, 'utf8').digest('hex');
const canonical = [method.toUpperCase(), pathWithQuery, timestamp, nonce, bodyHash].join('\n');
const signature = createHmac('sha256', secret).update(canonical, 'utf8').digest('hex');
return {
'X-BP-Key-Id': keyId,
'X-BP-Timestamp': timestamp,
'X-BP-Nonce': nonce,
'X-BP-Signature': `t=${timestamp},v1=${signature}`,
'Content-Type': 'application/json',
};
}Il body va firmato byte per byte esattamente come viene trasmesso: non riserializzare il JSON dopo la firma, perché un diverso ordinamento delle chiavi o una spaziatura differente invalidano l'hash.
Lo stesso schema di firma è usato in direzione opposta per i webhook in uscita: vedi Riferimento webhook e Firma HMAC.
Errori di autenticazione più frequenti
L'header Authorization è assente o vuoto. Verifica che il tuo client HTTP non lo stia rimuovendo dopo
un redirect: molte librerie eliminano gli header di autenticazione quando cambia l'host.
Il valore non rispetta il formato Bearer <token>. Errore tipico: doppio prefisso (Bearer Bearer ...)
o spazi aggiuntivi introdotti dalla lettura di un file di configurazione. Applica sempre un trim.
L'access token è scaduto. Esegui il refresh e riprova la chiamata una sola volta. Se accade sistematicamente, controlla che l'orologio del server sia sincronizzato via NTP.
La credenziale è valida ma non ha lo scope necessario. Il campo details elenca i permessi richiesti:
aggiungili al ruolo o alla chiave API e riprova.
La firma HMAC non corrisponde. Nella quasi totalità dei casi la causa è il body riserializzato dopo la firma oppure il path canonico privo della query string. Registra la stringa canonica in ambiente di sviluppo e confrontala con quella attesa.
Il catalogo completo è in Errori.