Il problema
Hai bisogno di autenticare un token o verificare che un dato non sia stato manomesso,
ma non vuoi aggiungere dipendenze come crypto-js o jsonwebtoken.
La Web Crypto API Γ¨ giΓ nel browser β basta usarla.
The problem
You need to authenticate a token or verify data integrity,
but don't want to add dependencies like crypto-js or jsonwebtoken.
The Web Crypto API is already in the browser β just use it.
Firma un payload (sign)
Sign a payload
/**
* Firma un payload con HMAC-SHA256.
* @param {string} secret β chiave segreta condivisa
* @param {string} payload β stringa da firmare (es. JSON.stringify di un oggetto)
* @returns {Promise<string>} firma in hex
*/
async function hmacSign(secret, payload) {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
enc.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false, // non estraibile
['sign']
);
const sig = await crypto.subtle.sign('HMAC', key, enc.encode(payload));
// Converte ArrayBuffer β stringa hex
return [...new Uint8Array(sig)]
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
Verifica la firma (verify)
Verify the signature
/**
* Verifica che tokenHex corrisponda a HMAC-SHA256(secret, payload).
* Usa una comparazione a tempo costante per prevenire timing attacks.
* @returns {Promise<boolean>}
*/
async function hmacVerify(secret, payload, tokenHex) {
const expected = await hmacSign(secret, payload);
// Confronto a lunghezza fissa β evita timing attacks
if (expected.length !== tokenHex.length) return false;
let diff = 0;
for (let i = 0; i < expected.length; i++) {
diff |= expected.charCodeAt(i) ^ tokenHex.charCodeAt(i);
}
return diff === 0;
}
Esempio completo: URL firmato con scadenza
Full example: signed URL with expiry
const SECRET = 'chiave-segreta-condivisa';
// ββ MITTENTE: crea token ββββββββββββββββββββββββββββββ
async function buildAuthUrl(userId, targetUrl) {
const payload = JSON.stringify({
uid: userId,
exp: Date.now() + 5 * 60 * 1000 // scade in 5 minuti
});
const b64 = btoa(payload);
const sig = await hmacSign(SECRET, b64);
const token = `${b64}.${sig}`;
return `${targetUrl}?auth=${encodeURIComponent(token)}`;
}
// ββ DESTINATARIO: legge e verifica token ββββββββββββββ
async function parseAuthToken(token) {
const [b64, sig] = token.split('.');
if (!b64 || !sig) throw new Error('Token malformato');
const valid = await hmacVerify(SECRET, b64, sig);
if (!valid) throw new Error('Firma non valida');
const data = JSON.parse(atob(b64));
if (Date.now() > data.exp) throw new Error('Token scaduto');
return data; // { uid: '...', exp: ... }
}
Quando usarlo: autenticazione cross-app, validazione webhook, URL firmati a scadenza, verifica integritΓ dati tra client e server.
When to use it: cross-app authentication, webhook validation, expiring signed URLs, data integrity verification between client and server.
Attenzione: il segreto nel codice client Γ¨ visibile a chi ispeziona il bundle. Per applicazioni pubbliche usa un backend che firma server-side (es. Firebase Cloud Functions o un endpoint Node.js).
Warning: the secret in client-side code is visible to anyone who inspects the bundle. For public apps, sign server-side using a backend (e.g. Firebase Cloud Functions or a Node.js endpoint).
PerchΓ© usare crypto.subtle e non btoa()?
btoa() Γ¨ solo una codifica Base64 β non firma nulla, non garantisce integritΓ .
crypto.subtle.sign() usa primitivi crittografici reali: la chiave viene importata
in forma non estraibile, le operazioni avvengono in un contesto protetto del motore JS.
- HMAC = Hash-based Message Authentication Code
- SHA-256 = funzione di hash a 256 bit
- Il risultato Γ¨ deterministico: stesso secret + stesso payload = stessa firma
- Senza il secret Γ¨ computazionalmente impossibile forgiare una firma valida
Why crypto.subtle instead of btoa()?
btoa() is just Base64 encoding β it signs nothing and guarantees no integrity.
crypto.subtle.sign() uses real cryptographic primitives: the key is imported
in a non-extractable form, operations happen in a protected context of the JS engine.
- HMAC = Hash-based Message Authentication Code
- SHA-256 = 256-bit hash function
- The result is deterministic: same secret + same payload = same signature
- Without the secret it is computationally infeasible to forge a valid signature
Articolo correlato
Token HMAC tra due PWA su Firebase diversi β caso reale completo
Related article
HMAC token between two separate Firebase PWAs β full real-world case
β