⚠️ Early Stage Project: This is an implementation of the WICG Email Verification Protocol, which is currently in incubation. The protocol specification may change, and browser support is not yet available.
The Email Verification Protocol enables web applications to obtain cryptographically verified email addresses without sending verification emails. Instead of the traditional "click the link in your email" flow, EVP allows instant verification when the user is already logged into their email provider.
Traditional Flow:
User → Enter email → Wait for email → Click link → Verified ❌ (slow, prone to drop-off)
EVP Flow:
User → Select email → Instant verification ✅ (seamless, no context switch)
| Package | Description | npm |
|---|---|---|
@aspect-evp/core |
Shared types, constants, and utilities | |
@aspect-evp/issuer |
EVP issuer for email providers | |
@aspect-evp/verifier |
EVP verifier for web apps (RPs) | |
@aspect-evp/cli |
CLI for testing and debugging |
# Core libraries
npm install @aspect-evp/core @aspect-evp/issuer @aspect-evp/verifier
# CLI (optional, for testing)
npm install -g @aspect-evp/cliimport { EmailVerificationIssuer } from '@aspect-evp/issuer';
const issuer = new EmailVerificationIssuer({
issuer: 'mail.example.com',
privateKey: yourPrivateKeyJWK,
kid: '2024-01-key',
algorithm: 'EdDSA'
});
// Serve /.well-known/email-verification
app.get('/.well-known/email-verification', (req, res) => {
res.json(issuer.getMetadata('https://mail.example.com'));
});
// Serve JWKS
app.get('/email-verification/jwks', (req, res) => {
res.json(issuer.getJWKS());
});import { EmailVerificationVerifier } from '@aspect-evp/verifier';
const verifier = new EmailVerificationVerifier({
rpOrigin: 'https://myapp.example.com'
});
async function handleEmailVerification(sdJwtKb: string, sessionNonce: string) {
const result = await verifier.verify(sdJwtKb, sessionNonce);
console.log('Verified email:', result.email);
console.log('Issuer:', result.issuer);
}# Generate a keypair
evp keygen -o keys.json
# Run a complete test flow
evp test -e user@example.com -i issuer.example.com
# Inspect a token
evp inspect <token>
# Check DNS records for a domain
evp dns gmail.comSince browsers don't yet support EVP, use the test utilities:
import { createTestFlow } from '@aspect-evp/core/testing';
import { EmailVerificationVerifier } from '@aspect-evp/verifier';
const testFlow = await createTestFlow({
issuer: 'issuer.example.com',
rpOrigin: 'https://myapp.example.com',
});
const verifier = new EmailVerificationVerifier({
rpOrigin: 'https://myapp.example.com',
dnsResolver: testFlow.dnsResolver,
fetch: testFlow.fetch,
});
const token = await testFlow.createToken('user@example.com', 'session-nonce');
const result = await verifier.verify(token, 'session-nonce');sequenceDiagram
participant Browser as Browser (Native)
participant DNS
participant Issuer as Issuer (@aspect-evp/issuer)
participant RP as RP (@aspect-evp/verifier)
Browser->>DNS: TXT _email-verification.domain.com
DNS-->>Browser: iss=issuer.example.com
Browser->>Issuer: POST /issuance (+ session cookies)
Issuer-->>Browser: SD-JWT (signed)
Browser->>Browser: Create KB-JWT
Browser->>RP: emailverified event (SD-JWT+KB)
RP->>Issuer: Fetch JWKS
RP->>RP: Verify signatures
RP-->>Browser: Verification result
git clone https://github.com/aspect-evp/evp-js.git
cd evp-js
pnpm install
pnpm build
pnpm test
pnpm test:coverage| Command | Description |
|---|---|
pnpm build |
Build all packages |
pnpm test |
Run tests |
pnpm test:coverage |
Run tests with coverage |
pnpm lint |
Lint code |
pnpm typecheck |
Type check |
pnpm changeset |
Create a changeset for release |
| Component | Status |
|---|---|
| WICG Specification | 📝 Draft |
| Chrome Implementation | 🧪 Intent to Prototype |
@aspect-evp/* |
✅ Published |
- Node.js >= 20.0.0
- WICG Email Verification Protocol Spec
- Chrome Intent to Prototype
- SD-JWT Specification (IETF RFC 9901)
MIT