Per ulteriori dettagli, puoi esaminare il codice della demo del provider di email simulato e consultare i passaggi per l'emittente nelle proposte relative all'API Email Verification e al protocollo Email Verification.
In qualità di emittente, non devi registrarti alla prova dell'origine né fornire un token perché il sito della relying party attiva il comportamento del browser. Assicurati che i tuoi endpoint siano configurati per rispondere a queste richieste.
Configura il rilevamento dell'emittente
Per consentire ai browser di rilevare automaticamente gli endpoint di verifica quando viene selezionato un indirizzo email appartenente al tuo dominio, esponi la configurazione utilizzando DNS e un endpoint HTTP .well-known.
Configura il record di delega DNS
Configura un record TXT DNS sul tuo dominio email che delega l'autorità di verifica all'identificatore dell'emittente. Questi identificatori possono utilizzare lo stesso dominio a seconda dell'infrastruttura.
Formato record: _email-verification.<email-domain>
File di zona di esempio:
_email-verification.example.com IN TXT "iss=accounts.issuer.example"
Ospitare un endpoint .well-known/email-verification
Ospita un file di metadati JSON sul tuo dominio emittente nel percorso /.well-known/.
Questo file descrive le tue funzionalità di emissione e gli algoritmi di firma crittografica supportati dalla tua infrastruttura.
Endpoint: https://<issuer-domain>/.well-known/email-verification
Esempio di risposta:
{
"issuance_endpoint": "https://accounts.issuer.example/email-verification/issuance",
"jwks_uri": "https://accounts.issuer.example/.well-known/vc-public-jwks",
"signing_alg_values_supported": ["EdDSA", "ES256"]
}
Ospitare un endpoint .well-known/web-identity
Potresti aver già implementato una risorsa JSON .well-known aggiuntiva nell'ambito dell'API Federated Credentials (FedCM). Questa risorsa fornisce
link all'endpoint e all'URL di accesso dei tuoi account.
Endpoint: https://<domain>/.well-known/web-identity
Esempio di risposta:
{
"accounts_endpoint": "https://accounts.issuer.example/accounts",
"login_url": "https://accounts.issuer.example/login"
}
Utilizzare un endpoint degli account
L'endpoint accounts dell'API FedCM fornisce un elenco degli account a cui è stato eseguito l'accesso al momento. L'esempio seguente mostra una risposta minima. Per maggiori dettagli, consulta la guida all'implementazione del provider di identità.
Endpoint: come specificato in .well-known/web-identity
Di seguito è riportato un esempio di risposta:
{
"accounts": [
{
"id": "demo-example",
"name": "Demo User",
"email": "demo@example.com",
"given_name": "Demo"
}
]
}
Integrare l'API Login Status
L'utente deve avere una sessione attiva con il fornitore e devi segnalarlo al browser utilizzando l'API Login Status.
Quando un utente esegue l'accesso o la disconnessione, pubblica l'intestazione della risposta HTTP corrispondente:
Set-Login: logged-in
Set-Login: logged-out
In alternativa, aggiorna lo stato utilizzando JavaScript nel contesto dell'applicazione web:
navigator.login.setStatus("logged-in");
navigator.login.setStatus("logged-out");
Gestire le richieste di emissione
Il tuo issuance_endpoint riceve una richiesta application/json POST che
contiene la chiave email e le intestazioni HTTP Message
Signatures per Signature,
Signature-Input e Signature-Key.
Utilizza una libreria che supporti le intestazioni strutturate e le firme dei messaggi HTTP per il tuo ambiente. In Node.js puoi utilizzare
structured-headers e
http-message-sig.
Formato della richiesta completa:
POST /email-verification/issuance HTTP/1.1
Host: provider.example
Accept: application/json
Content-Digest: sha-256=:aBc123aBc123aBc123aBc123aBc123=:
Content-Type: application/json
Sec-Fetch-Dest: email-verification
Signature: sig=:+dEf567dEf567/dEf567dEf567dEf567/dEf567==:
Signature-Input: sig=("@method" "@authority" "@path" "content-digest" "signature-key");created=1786455840
Signature-Key: sig=hwk;crv="Ed25519";kty="OKP";x="gHi890_gHi890_gHi890"
{email: "demo@example.com"}
Analizza e convalida la richiesta:
- Autenticazione della sessione: convalida i cookie di sessione proprietari inviati con la richiesta. L'utente deve essere autenticato.
- Intestazione
Sec-Fetch-Dest: impostata suemail-verification. - Firme dei messaggi HTTP: verifica la firma della richiesta utilizzando la chiave pubblica effimera in
Signature-Keye convalidaContent-Digest. - Payload: il corpo JSON contiene la stringa
emailrichiesta per la verifica.
Risposta all'emissione
Una volta convalidati correttamente la sessione e il token di richiesta, genera un JWT di divulgazione selettiva (SD-JWT) firmato restituito come JSON utilizzando le librerie appropriate per la tua piattaforma. Ad esempio, per Node puoi utilizzare
@sd-jwt/core e
jose.
Il formato del payload non elaborato dovrebbe essere simile a questo:
{
"iss": "https://accounts.issuer.example",
"iat": 1780272000,
"exp": 1780272300,
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "pUbLiCKeY123pUbLiCKeY123pUbLiCKeY123",
"y": "pUbLiCKeY456pUbLiCKeY456pUbLiCKeY456"
}
},
"email": "demo@example.com",
"email_verified": true
}
Crea, firma e restituisci il token:
import { importJWK, CompactSign } from "jose";
import { SDJwtInstance } from "@sd-jwt/core";
import crypto from "node:crypto";
// Issuer private key from secure storage
const privateKey = await importJWK(PRIVATE_KEY_JWK, "EdDSA");
const origin = url.origin;
const currentTime = Math.floor(Date.now() / 1000);
const evtPayload = {
iss: origin,
iat: currentTime,
exp: currentTime + 300, // 5 minutes
cnf: {
jwk: browserJwk,
},
email: payload.email, // exactly as received in payload
email_verified: true,
};
const sdJwt = new SDJwtInstance({
signer: async (data) => {
const [headerB64, payloadB64] = data.split(".");
const header = JSON.parse(Buffer.from(headerB64, "base64url").toString());
const payload = Buffer.from(payloadB64, "base64url");
const signed = await new CompactSign(payload).setProtectedHeader(header).sign(privateKey);
return signed.split(".").pop()!;
},
signAlg: "EdDSA",
hasher: async (data, alg) => {
const nodeAlg = alg.replace("-", "");
return new Uint8Array(crypto.createHash(nodeAlg).update(data).digest());
},
hashAlg: "sha-256",
saltGenerator: async () => crypto.randomBytes(16).toString("base64url"),
});
const issuanceToken = await sdJwt.issue(evtPayload, undefined, {
header: {
alg: "EdDSA",
kid: PRIVATE_KEY_JWK.kid,
typ: "evt+jwt",
},
});
return sendResponse({ issuance_token: issuanceToken, }, 200);
Il corpo della risposta risultante è simile al seguente:
{
"issuance_token": "tOkEn123tOkEn123tOkEn123...~"
}
Dopo aver creato e firmato il token di verifica email, il browser lo passa al sito di verifica per la convalida.
Risoluzione dei problemi
Se il browser non contatta gli endpoint o rifiuta i token emessi, controlla i seguenti problemi comuni:
Il browser non chiama mai accounts_endpoint o issuance_endpoint
- Stato accesso non impostato: Chrome esegue query sugli endpoint solo se sa che l'utente ha eseguito l'accesso. Assicurati che il flusso di accesso imposti l'intestazione HTTP
Set-Login: logged-ino chiaminavigator.login.setStatus("logged-in"). - Cookie di sessione bloccati (
SameSite=None): il browser recupera i tuoiaccounts_endpointeissuance_endpointda una relying party su un sito diverso. Poiché si tratta di richieste cross-site, il cookie di sessione deve includereSameSite=None; Secure. Un cookieSameSite=Laxfunziona durante il test su un verificatore dello stesso sito, ma viene omesso nelle richieste cross-site. - Rilevamento o mancata corrispondenza dell'account: verifica che
_email-verification.<email-domain>restituisca un singolo record TXT (iss=<issuer-domain>, senzahttps://), che entrambi gli endpoint.well-knownrestituiscanoContent-Type: application/jsone che la rispostaaccounts_endpointincluda un account il cuiemailcorrisponda all'indirizzo inserito.
La convalida della richiesta di emissione non riesce
- Ortografia dell'intestazione
Sec-Fetch-Dest: Chrome 154 e versioni successive invianoSec-Fetch-Dest: email-verification(con un trattino), mentre Chrome 153 inviavaemailverification(senza trattino). Accetta entrambi i valori durante l'implementazione. - Mancata corrispondenza della firma del messaggio HTTP (
@authority) dietro un proxy: durante la verifica della firma RFC 9421, il componente@authorityriflette l'host pubblico. Se il server si trova dietro un proxy inverso o un bilanciatore del carico, ricostruisci l'URL di verifica utilizzandoX-Forwarded-Host(o l'origine pubblica) anziché il nome host interno e calcolaContent-Digestsui byte del corpo della richiesta non elaborati prima dell'analisi JSON.
Il browser rifiuta il issuance_token restituito
- Rivendicazione
emailmodificata o canonica: a partire da Chrome 156, il browser verifica che la rivendicazioneemailEVT corrisponda alemailrichiesto byte per byte. Se il backend normalizza l'indirizzo in un formato di account canonico (ad esempio restituendoFirst.Last@example.comquando è stato richiestofirst.LAST@example.com), Chrome elimina il token. Corrispondi alla richiesta con l'account dell'utente, ma restituisci la stringaemailesatta ricevuta nel corpo della richiesta. - Tilde finale mancante (
~): anche con zero divulgazioni, ilissuance_tokendeve essere un SD-JWT valido che termina con una tilde finale (<Issuer-signed-JWT>~) in modo che il browser possa aggiungere<KB-JWT>. issocnf.jwknon corrispondenti: assicurati che l'attestazione EVTisssia l'origine HTTPS esatta (https://<issuer-domain>, senza barra finale) corrispondente ai metadati.well-known/email-verificatione checnf.jwkincorpori la chiave pubblica effimera del browser dall'intestazioneSignature-Key.