Aby wdrożyć weryfikację adresu e-mail w swojej witrynie, zaktualizuj kod formularza, aby wysyłać żądania tokena, i dodaj weryfikację po stronie serwera dla przychodzących tokenów.
Rejestracja w testowaniu origin
Witryny, które mają zostać zweryfikowane, muszą mieć skonfigurowaną w swojej domenie wersję próbną origin.
- Rejestracja w testowaniu origin
- Udostępnianie tokena testowania origin
Od wersji 154 Chrome obsługuje testy pochodzenia innych firm z ważnym zastrzeżeniem: zarejestrowane pochodzenie testu musi być w tej samej witrynie co wydawca. Na przykład:
- Domena wystawcy:
issuer.example - Rejestrujący OT:
https://issuer.example - Źródło JavaScript:
https://issuer.example(lubhttps://app.issuer.examplez dopasowywaniem subdomen)
Konfigurowanie pól formularza
Dodaj do formularza przesyłania e-maili ukryte pole tokena:
<input
type="email"
name="email-address"
autocomplete="email">
<input
type="hidden"
name="token"
autocomplete="email-verification-token"
nonce="rAnD0m-VaLuE">
Wymagania dotyczące pola:
- Pole adresu e-mail: ustaw
type="email"iautocomplete="email", aby Chrome mógł autouzupełniać i rozpoznawać adres. - Atrybuty pola tokena:
- Ustaw
autocomplete="email-verification-token": Chrome identyfikuje to pole, aby wypełnić token podczas przesyłania. - Ustawienie
nonce="<VALUE>": witryna musi udostępniać niepowtarzalny, powiązany z sesją identyfikator nonce, aby zweryfikować przesłanie formularza.
- Ustaw
Sprawdź token potwierdzania adresu e-mail (EVT).
Gdy użytkownik prześle formularz, Twój serwer otrzyma adres e-mail i token z ukrytego pola. Puste pole tokena oznacza, że przeglądarka lub dostawca nie obsługuje weryfikacji EVP albo użytkownik pominął weryfikację. W takim przypadku wróć do dotychczasowego procesu weryfikacji, np. wysyłania hasła jednorazowego lub magicznego linku.
Jeśli token jest obecny, sprawdź go w ten sposób:
- Przeanalizuj token za pomocą biblioteki SD-JWT.
- Sprawdź oczekiwane wartości i deklaracje sesji.
- Sprawdź delegowanie DNS.
- Odkrywanie metadanych wydawcy i pobieranie JWKS.
- Weryfikacja podpisów kryptograficznych i powiązania kluczy.
1. Analizowanie tokena
Token jest zgodny z formatem RFC 9901: Selective Disclosure JWT (SD-JWT+KB). Użyj odpowiednich bibliotek dla swojej platformy, aby przeanalizować i zweryfikować token.
Na przykład w przypadku Node możesz użyć @sd-jwt/core i jose. W formie surowej wygląda to tak: JWT podpisany przez wystawcę, po którym następuje zero lub więcej ujawnień, a na końcu JWT powiązania klucza. Poszczególne komponenty są rozdzielone tyldą:
<Issuer-signed EVT>~<Disclosure 1>~...~<Disclosure N>~<Key Binding JWT>
W obecnej implementacji token nie zawiera żadnych informacji o ujawnieniu (<Issuer-signed EVT>~<Key Binding JWT>). Może się to jednak zmienić w przyszłości.
Zdekoduj token za pomocą biblioteki:
import { decodeSdJwtSync } from "@sd-jwt/core";
import { createHash } from "node:crypto";
const hasher = (data, alg) =>
createHash(alg === "sha-256" ? "sha256" : alg)
.update(data)
.digest();
const decoded = decodeSdJwtSync(rawToken, hasher);
const evtPayload = decoded.jwt.payload;
const kbPayload = decoded.kbJwt?.payload;
Jeśli verifier.example zweryfikuje demo@provider.example, zdekodowany token będzie wyglądać mniej więcej tak:
{
"evtJwtDecodedHeader": {
"typ": "evt+jwt",
"alg": "EdDSA",
"kid": "issuer-key-id"
},
"evtJwtDecodedPayload": {
"iss": "https://provider.example",
"iat": 12345678901,
"exp": 12345679901,
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "pUbLiCkEy123pUbLiCkEy123pUbLiCkEy123"
}
},
"email": "demo@provider.example",
"email_verified": true
},
"kbJwtDecodedHeader": {
"alg": "EdDSA",
"typ": "kb+jwt"
},
"kbJwtDecodedPayload": {
"aud": "https://verifier.example",
"iat": 12345678901,
"nonce": "rAnDoM123rAnDoM123rAnDoM123rAnDoM123",
"sd_hash": "hAsH456hAsH456hAsH456hAsH456hAsH456"
},
"disclosures": []
}
2. Weryfikowanie oczekiwanych wartości i roszczeń dotyczących sesji
Sprawdź, czy podstawowe wartości w ładunku odpowiadają podanym i oczekiwanym wartościom:
email_verified: musi mieć wartośćtrue.email: musi być zgodny z adresem e-mail podanym w formularzu.aud(odbiorcy): musi pasować do pochodzenia witryny.nonce: musi być zgodny z wartością nonce podaną w formularzu.iat(issued at) iexp(expiry): sprawdź, czy token jest w okresie ważności i nie wygasł.
3. Sprawdzanie delegowania DNS
Sprawdź _email-verification rekord DNS domeny adresu e-mail. Na przykład w przypadku demo@gmail.com wyślij zapytanie o rekord TXT _email-verification.gmail.com. W przypadku tego dostawcy zapytanie zwraca lokalizację dostawcy konta, czyli accounts.google.com.
$ dig +short TXT _email-verification.gmail.com
"iss=accounts.google.com"
Sprawdź, czy schemat wystawcy to https:// i czy https://<domain> pasuje do roszczenia iss w EVT.
4. Weryfikowanie podpisu EVT
Pobierz metadane wykrywania wydawcy z
https://<issuer>/.well-known/email-verification:
{
"issuance_endpoint": "https://accounts.google.com/gsi/email-verification/issue",
"jwks_uri": "https://verifiablecredentials-pa.googleapis.com/.well-known/vc-public-jwks",
"signing_alg_values_supported": ["EdDSA"]
}
Pobierz zestaw kluczy internetowych JSON z jwks_uri.
Użyj biblioteki SD-JWT, aby zweryfikować pakiet tokenów. Biblioteka koordynuje weryfikację:
- Weryfikacja podpisu wystawcy na dokumencie EVT na podstawie pobranych kluczy JWKS.
- Weryfikacja podpisu przeglądarki w KB-JWT za pomocą efemerycznego klucza publicznego w
cnf.jwk. - Weryfikacja powiązania klucza (
aud,noncei wartość skrótusd_hash).
Przykładowa logika weryfikacji w Node.js:
import { SDJwtInstance } from "@sd-jwt/core";
import { importJWK, compactVerify } from "jose";
import { createHash } from "node:crypto";
const hasher = (data, alg) =>
createHash(alg === "sha-256" ? "sha256" : alg)
.update(data)
.digest();
const sdJwt = new SDJwtInstance({ hasher });
sdJwt.config({
hasher,
// Verifier for the Issuer-signed EVT
verifier: async (data, sig) => {
const token = `${data}.${sig}`;
const header = decoded.jwt.header;
const headerAlg = header.alg || "ES256";
// Match by kid if present, or iterate across matching algorithm keys
const keysToTry = header.kid
? jwksData.keys.filter(k => k.kid === header.kid)
: jwksData.keys;
for (const jwk of keysToTry) {
try {
const pubKey = await importJWK(jwk, jwk.alg || headerAlg);
await compactVerify(token, pubKey);
return true;
} catch {
// Try next candidate key
}
}
return false;
},
// Verifier for the Key Binding JWT (KB-JWT)
kbVerifier: async (data, sig) => {
try {
const browserJwkKey = evtPayload.cnf?.jwk;
if (!browserJwkKey) return false;
const pubKey = await importJWK(browserJwkKey, decoded.kbJwt.header.alg || "ES256");
await compactVerify(`${data}.${sig}`, pubKey);
return true;
} catch {
return false;
}
},
});
// The library automatically verifies EVT signature, KB-JWT signature, audience, nonce, and sd_hash
const result = await sdJwt.verify(rawToken, {
kb: {
expectedNonce: sessionNonce,
expectedAudience: "https://example.com",
required: true,
},
});
const verifiedPayload = result.payload;
Jeśli wszystkie kroki zostaną wykonane prawidłowo, adres e-mail zostanie zweryfikowany u dostawcy. Jeśli weryfikacja się nie powiedzie, wróć do wysyłania e-maila z potwierdzeniem do użytkownika za pomocą normalnego przepływu.
Rozwiązywanie problemów
Jeśli weryfikacja się nie powiedzie lub przeglądarka nie dostarczy tokena, sprawdź te typowe problemy:
Pole tokena jest puste podczas przesyłania
- Rejestracja w programie testów origin: sprawdź, czy na stronie jest wyświetlany nagłówek
Origin-Triallub tag<meta>. W przypadku testów origin innych firm (Chrome 154 i nowsze) zarejestrowany origin testu musi być w tej samej witrynie co wydawca (https://<issuer-domain>). Konfigurację testu origin w witrynie możesz sprawdzić w Narzędziach deweloperskich w sekcji Aplikacja > Ramki > (wybierz odpowiednią ramkę) > Testy origin. - Znaczniki formularza: zarówno
<input type="email" autocomplete="email">, jak i<input type="hidden" autocomplete="email-verification-token" nonce="...">muszą znajdować się w tym samym elemencie<form>(nie mogą być odseparowane przez granice Shadow DOM), anoncenie może być pusty. - Wcześniejsze przesłanie lub ponowne użycie strony: przeglądarka pobiera token w tle po wpisaniu lub automatycznym wypełnieniu adresu e-mail. Przesłanie przed zakończeniem żądania spowoduje, że token będzie pusty. Może się tak zdarzyć, jeśli użytkownik po wpisaniu adresu e-mail naciśnie klawisz Enter, aby przesłać formularz.
- Wymagania dotyczące przeglądarki i dostawcy: użytkownik musi być zalogowany u dostawcy biorącego udział w programie w tym samym profilu przeglądarki i mieć włączoną zweryfikowaną pocztę e-mail w ustawieniach Chrome (
chrome://settings/contactInfo).
Nie udało się zweryfikować podpisu wydawcy
- Brak nagłówka
kid: roszczeniekid(identyfikator klucza) w nagłówku EVT i JWKS jest opcjonalne (np. Gmail pomijakid). Jeślikidnie występuje, przeiteruj wszystkie klucze kandydujące wjwks_uriwystawcy, zamiast kończyć działanie z powodu niepowodzenia wyszukiwania identyfikatora klucza. - Identyfikatory algorytmów (EdDSA i Ed25519): wystawcy i biblioteki mogą określać
EdDSAlubEd25519(wraz zES256). Upewnij się, że logika importowania i weryfikacji JWK akceptuje oba identyfikatory. - Format pochodzenia wystawcy (
iss): rekord TXT DNS (_email-verification.<domain>) zawiera samą nazwę hosta (iss=accounts.issuer.example), a deklaracja EVTissto pełne pochodzenie HTTPS (https://accounts.issuer.examplebez ukośnika na końcu). Przed porównaniem dodaj prefikshttps://do wartości rekordu DNS.
Nie udało się zweryfikować powiązania klucza (KB-JWT)
- Niezgodny lub wygasły nonce: upewnij się, że wartość
noncewyrenderowana w<input>jest zgodna z nonce aktywnej sesji na serwerze i nie została zastąpiona przez inną kartę ani wykorzystana przez poprzednie żądanie. - Niezgodność odbiorców (
aud): roszczenieaudto pochodzenie HTTPS weryfikatora (https://verifier.example, bez ścieżki ani ukośnika na końcu).
Porównanie deklaracji e-maila (email) nie powiodło się
- Wielkość liter i kanonizacja: Chrome w wersji 156 lub nowszej zwraca deklarację
emailw postaci bajt po bajcie, tak jak została wpisana w formularzu, ale starsze wersje przeglądarki lub dostawcy mogą zwracać adres w postaci kanonicznej (np.First.Last@example.comzamiastfirst.last@example.com). Podczas dopasowywania deklaracjiemailtokena do wartości przesłanej w formularzu używaj porównania bez uwzględniania wielkości liter.