JWT da zero: a cosa serve, come proteggerlo e implementarlo con l’IA
Identità, firme e permessi attraverso un caso B2B. Codice completo JavaScript, Python e PHP, test negativi e prompt inglesi.

A cosa mi serve?
Immagina un’app mobile per eventi che chiama un’API di fatturazione. Dopo l’accesso deve fare molte richieste senza inviare ogni volta la password. Un token breve porta una dichiarazione firmata sull’utente autenticato; l’API la verifica prima di gestire la richiesta. Serve per client mobili o diversi servizi che verificano accesso separatamente.
JWT è un formato, non un sistema di autenticazione completo. Per un sito tradizionale, una sessione server con cookie opaco può essere più semplice. Entrambi richiedono permessi. Alla fine saprai spiegare il flusso, eseguire tre implementazioni equivalenti, rifiutare token invalidi e chiedere modifiche verificabili all’IA. È un laboratorio locale senza endpoint di login reale.
1. Dall’accesso alla fattura
Il sistema affidabile autentica l’utente prima di emettere il token. Il client lo invia via HTTPS, generalmente in Authorization: Bearer. L’API verifica firma e dichiarazioni, poi il diritto di leggere quella specifica fattura.
L’utente fittizio user-7 appartiene ad Acme. Un suo token valido non autorizza fatture di un’altra azienda e, nella nostra politica, nemmeno quelle di un altro utente Acme. Autenticazione significa chi sei; autorizzazione cosa puoi fare. Nascondere pulsanti o rendere gli ID imprevedibili non applica questi permessi.
2. Struttura e contratto di sicurezza
Il JWT firmato contiene intestazione, contenuto e firma in base64url. Il contenuto è leggibile: niente password, pagamenti o documenti privati. La firma rileva modifiche, non cifra.
Il profilo impone HS256 e almeno 32 byte casuali di chiave server. iss indica l’emittente; aud l’API destinataria; sub l’utente; tenant l’organizzazione; iat/exp sono secondi Unix interi. Durata massima cinque minuti; typ nf-access+jwt distingue questo utilizzo. Sono requisiti dell’applicazione, non di ogni JWT.
Con HS256, ogni verificatore conosce anche il segreto per emettere token. Con più servizi considera firme asimmetriche e chiavi pubbliche/JWKS fidate. Fissa algoritmo e origine delle chiavi: un’intestazione ricevuta non può scegliere URL o file arbitrari.
3. Implementare nel tuo linguaggio
Il modulo usa jose. issue() presuppone identità e organizzazione già risolte dal server; verify() controlla firma e profilo; canReadInvoice() applica proprietà e organizzazione. Conserva la chiave stabile in un gestore di segreti, senza ricrearla a ogni richiesta. I test usano chiavi temporanee.
import {SignJWT,jwtVerify} from 'jose';
const issuer='https://auth.example.test', audience='billing-api';
function checkKey(key){if(!(key instanceof Uint8Array)||key.length<32)throw new Error('Use at least 32 random bytes');}
export async function issue(subject,tenant,key){
checkKey(key);
if(!subject||!tenant)throw new Error('Missing identity');
return new SignJWT({tenant}).setProtectedHeader({alg:'HS256',typ:'nf-access+jwt'})
.setSubject(subject).setIssuer(issuer).setAudience(audience)
.setIssuedAt().setExpirationTime('5m').sign(key);
}
export async function verify(token,key){
checkKey(key);
const {payload}=await jwtVerify(token,key,{
algorithms:['HS256'],typ:'nf-access+jwt',issuer,audience,
requiredClaims:['exp','iat','sub','tenant'],maxTokenAge:'5m',clockTolerance:0,
});
const now=Math.floor(Date.now()/1000);
if(typeof payload.sub!=='string'||!payload.sub||typeof payload.tenant!=='string'||!payload.tenant||
!Number.isInteger(payload.exp)||!Number.isInteger(payload.iat)||payload.iat>now||
payload.exp<=payload.iat||payload.exp-payload.iat>300)throw new Error('Invalid access profile');
return payload;
}
export function canReadInvoice(actor,invoice){
return !!actor?.sub&&!!actor?.tenant&&actor.tenant===invoice?.tenant&&actor.sub===invoice?.owner;
}"""Local HS256 access-token profile. Caller authenticates before issue()."""
import secrets, time, jwt
ISSUER, AUDIENCE = 'https://auth.example.test', 'billing-api'
def check_key(key):
if not isinstance(key, bytes) or len(key)<32: raise ValueError('Use 32 random bytes')
def issue(subject,tenant,key):
check_key(key)
if not subject or not tenant: raise ValueError('Missing identity')
now=int(time.time())
return jwt.encode({'sub':subject,'tenant':tenant,'iss':ISSUER,'aud':AUDIENCE,
'iat':now,'exp':now+300},key,algorithm='HS256',headers={'typ':'nf-access+jwt'})
def verify(token,key):
check_key(key)
data=jwt.decode_complete(token,key,algorithms=['HS256'],issuer=ISSUER,audience=AUDIENCE,
options={'require':['exp','iat','sub','tenant','iss','aud']})
p=data['payload']; now=int(time.time())
if (data['header'].get('typ')!='nf-access+jwt' or not isinstance(p['sub'],str)
or not p['sub'] or not isinstance(p['tenant'],str) or not p['tenant']
or type(p['iat']) is not int or type(p['exp']) is not int
or p['iat']>now or not 0<p['exp']-p['iat']<=300): raise ValueError('Invalid access profile')
return p
def can_read_invoice(actor,invoice):
return bool(actor.get('sub') and actor.get('tenant') and actor['sub']==invoice.get('owner') and actor['tenant']==invoice.get('tenant'))
if __name__=='__main__':
key=secrets.token_bytes(32);actor=verify(issue('user-7','acme',key),key)
print({'authenticated':True,'subject':actor['sub'],'sameTenantInvoice':can_read_invoice(actor,{'owner':'user-7','tenant':'acme'})})<?php
// Local HS256 profile. Issue tokens only after trusted server authentication.
require __DIR__.'/vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
function checkKey(string $key): void {
if(strlen($key)<32) throw new RuntimeException('Use 32 random bytes');
}
function issue(string $subject,string $tenant,string $key): string {
checkKey($key);
if($subject===''||$tenant==='') throw new RuntimeException('Missing identity');
$now=time();
return JWT::encode(['sub'=>$subject,'tenant'=>$tenant,'iss'=>'https://auth.example.test',
'aud'=>'billing-api','iat'=>$now,'exp'=>$now+300],$key,'HS256',null,['typ'=>'nf-access+jwt']);
}
function verify(string $token,string $key): object {
checkKey($key); $headers=new stdClass();
$p=JWT::decode($token,new Key($key,'HS256'),$headers); $now=time();
if(($headers->typ??null)!=='nf-access+jwt'||($p->iss??null)!=='https://auth.example.test'
||($p->aud??null)!=='billing-api'||!is_string($p->sub??null)||$p->sub===''
||!is_string($p->tenant??null)||$p->tenant===''
||!is_int($p->iat??null)||!is_int($p->exp??null)||$p->iat>$now||$p->exp<=$now
||$p->exp<=$p->iat||$p->exp-$p->iat>300) throw new RuntimeException('Invalid access profile');
return $p;
}
function canReadInvoice(object $actor,object $invoice): bool {
return !empty($actor->sub)&&!empty($actor->tenant)&&$actor->sub===($invoice->owner??null)&&$actor->tenant===($invoice->tenant??null);
}
if(realpath($_SERVER['SCRIPT_FILENAME']??'')===__FILE__){
$key=random_bytes(32);$actor=verify(issue('user-7','acme',$key),$key);
echo json_encode(['authenticated'=>true,'subject'=>$actor->sub,'sameTenantInvoice'=>canReadInvoice($actor,(object)['owner'=>'user-7','tenant'=>'acme'])]).PHP_EOL;
}4. Python con PyJWT
PyJWT verifica firma e dichiarazioni standard quando configurato. Richiedere un campo non controlla il suo significato: verifichiamo anche identità non vuote, tempi interi, tipo e durata. Decodificare senza verifica non produce un utente autenticato.
5. PHP e controlli espliciti
PHP-JWT controlla firma e date; l’applicazione verifica emittente, destinatario e profilo. Qui aud è esattamente la stringa billing-api. Altri fornitori usano array: adatta il contratto consapevolmente, senza togliere controlli per accettare un formato diverso.
6. Protezione nell’applicazione reale
Un bearer rubato può essere riutilizzato fino a scadenza o revoca. Usa HTTPS, oscura Authorization nei log ed evita token negli URL. Un cookie HttpOnly e Secure ne impedisce la lettura JavaScript; scegli SameSite e difendi da CSRF le richieste che modificano dati. Non corregge XSS. localStorage è esposto agli script iniettati; un’app nativa deve usare l’archivio protetto del sistema.
La scadenza breve non implementa il logout. L’invalidazione immediata richiede una verifica server di sessione/versione o revoca. I refresh token richiedono archiviazione, rotazione, rilevamento del riuso e revoca dedicati. Ruota le chiavi con sovrapposizione controllata. I permessi possono cambiare prima di exp: verifica quelli correnti sul server per operazioni sensibili. Questi meccanismi non fanno parte del laboratorio.
7. Esecuzione e risultato
Scegli un linguaggio o confrontali tutti. Lo ZIP contiene moduli, test e lockfile JavaScript/PHP. Servono runtime e rete per i registri dei pacchetti, ma nessuna chiave API o credenziale reale.
I test accettano il token valido e rifiutano firma sbagliata, scadenza, campi mancanti, emittente/destinatario/tipo errati, emissione futura, durata eccessiva e chiave corta. Controllano anche proprietà nella stessa azienda. JavaScript e PHP stampano 16 asserzioni riuscite; Python esegue un metodo con casi equivalenti. Non sono test HTTP.
# Choose ONE language; extract the ZIP into a new folder first.
# JavaScript / Node.js 22+
npm ci
npm test
# Python 3.9+ (macOS/Linux; Windows: .venv\Scripts\activate)
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
python -m unittest test_python.py
python jwt_example.py
# PHP 8.1+ and Composer
composer install --no-plugins --no-scripts
php test_php.php
php jwt_example.php8. Implementazione con IA e prove
Il prompt chiede di esaminare il framework prima di cambiare autenticazione. Adatta emittente e destinatario senza condividere segreti. Chiedi una modifica piccola, il comando di test e il risultato reale.
Act as a senior application engineer. First inspect the existing authentication
framework and explain whether an opaque server session or a JWT is appropriate.
Do not replace a working authentication system just to introduce JWTs.
If JWT access tokens are justified, implement the provided local access profile:
pinned HS256, at least 32 random key bytes, issuer https://auth.example.test,
audience billing-api, typ nf-access+jwt, non-empty sub and tenant, required
integer iat and exp, maximum lifetime 300 seconds and no future issued-at.
Use jose, PyJWT or PHP-JWT according to the actual project language.
Issue tokens only after trusted authentication. Enforce object ownership and
tenant membership in server authorization on every relevant request.
Never derive a signing key, accepted algorithm or tenant from request JSON.
Propose browser storage, CSRF protection, logout/revocation and key rotation
for this application before coding. Do not log tokens or secrets.
Add negative tests for bad signatures, expiration, missing claims, wrong issuer,
audience and token type, another tenant and a non-owner. Report real test output.
Stop before deployment.9. Esempio di risposta utile
È un esempio del formato atteso, non un audit della tua applicazione. Dire che JWT è sicuro non dimostra nulla. Una risposta utile collega requisito, codice, test negativo e dubbio di integrazione. Poi usa il prompt di revisione indipendente.
Requirement: reject a token intended for another API.
Evidence: verify() supplies audience="billing-api".
Negative test: a correctly signed token with aud="other-api" is rejected.
Remaining gap: verify the real HTTP middleware invokes verify() on every route.
This example is an expected review format, not a certification.10. Risultato e prossimo passo
Hai un profilo riproducibile in tre linguaggi e separi identità e permessi. Collegalo a login e middleware HTTP, verificando errori, permessi attuali, cookie/CSRF, rinnovo, logout e rotazione. Inizia da un endpoint che accetta il proprietario e rifiuta accessi tra aziende.
Review this local JWT implementation independently. Produce a table with:
requirement, exact code location, positive test, negative test and remaining gap.
Explain the difference between decoding, signature verification and authorization.
Check key entropy, algorithm pinning, required claims, issuer/audience/type,
short lifetime, server-derived identity, browser storage and token revocation.
Report confirmed defects separately from unverified integration assumptions.
Use only synthetic tokens. Never ask me to paste production credentials.Riferimenti ufficiali e approfondimenti
- IETF · JWT security practices
- jose · jwtVerify
- PyJWT · Usage
- PHP-JWT · Official repository
- OWASP · Session management
Portalo nel tuo progetto
Scarica i file, conserva la guida o consegna al tuo assistente istruzioni pronte.
Prepara con l’IA
Copia questo prompt inglese nel tuo assistente. Include articolo, file disponibili e passaggi di preparazione.
Mostra il prompt di preparazione
Read this tutorial as reference material:
https://nethanelfitoussi.com/article/jwt-tokens-security-ai?lang=en
Goal: apply the documented workflow to my development project.
Read the existing project instructions, inspect the relevant files and explain the smallest useful setup.
Available starter files:
https://nethanelfitoussi.com/api/media/aba77d81-4cfd-4614-bca5-53015d625dff.zip
Download and inspect these files in a temporary folder before using them. Do not overwrite existing project instructions.
If I have not supplied a project folder, ask which development project to use. Do not invent a repository URL.
Additional project setup instructions:
Extract the supplied ZIP into a NEW folder. Read README.md before installing or running code.
Act as a senior application engineer. First inspect the existing authentication
framework and explain whether an opaque server session or a JWT is appropriate.
Do not replace a working authentication system just to introduce JWTs.
If JWT access tokens are justified, implement the provided local access profile:
pinned HS256, at least 32 random key bytes, issuer https://auth.example.test,
audience billing-api, typ nf-access+jwt, non-empty sub and tenant, required
integer iat and exp, maximum lifetime 300 seconds and no future issued-at.
Use jose, PyJWT or PHP-JWT according to the actual project language.
Issue tokens only after trusted authentication. Enforce object ownership and
tenant membership in server authorization on every relevant request.
Never derive a signing key, accepted algorithm or tenant from request JSON.
Propose browser storage, CSRF protection, logout/revocation and key rotation
for this application before coding. Do not log tokens or secrets.
Add negative tests for bad signatures, expiration, missing claims, wrong issuer,
audience and token type, another tenant and a non-owner. Report real test output.
Stop before deployment.
Review this local JWT implementation independently. Produce a table with:
requirement, exact code location, positive test, negative test and remaining gap.
Explain the difference between decoding, signature verification and authorization.
Check key entropy, algorithm pinning, required claims, issuer/audience/type,
short lifetime, server-derived identity, browser storage and token revocation.
Report confirmed defects separately from unverified integration assumptions.
Use only synthetic tokens. Never ask me to paste production credentials.
Adapt example paths, commands and acceptance criteria to this repository rather than assuming they match.
Give me a short plan, then make the agreed local changes and run the relevant checks. Report actual results and anything not verified. Stop before credentials, paid services, production changes, push, merge or deployment.

