npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

better-auth-evp

v1.0.8

Published

Email Verification Protocol (Chrome origin trial) plugin for Better Auth, with automatic fallback to any other sign-in method

Readme

better-auth-evp

npm version Better Auth License: MIT TypeScript

Email Verification Protocol (EVP) plugin for Better Auth, with automatic fallback to whatever other sign-in method you already have.

Live preview → - a minimal sign-in screen wired up with this plugin, source in evp-demo repo.

What is EVP?

EVP is an experimental, Chrome-only browser capability (currently gated behind an origin trial) that lets a user prove they own an email address without typing an OTP or clicking a magic link. The browser talks to the user's mailbox provider directly and, if the user is signed in there, hands your form a signed token proving ownership - all triggered by the normal act of filling in and submitting an email field.

Because this depends on: the user running an origin-trial build of Chrome, their mailbox provider having implemented the issuer side of the protocol, and the user being signed into that mailbox in the browser - it will not work for most users today. This plugin is pure progressive enhancement: wire it up, try it first, and fall back to your existing sign-in method (OTP, magic link, password, ...) whenever it doesn't pan out.

Installation

npm install better-auth-evp

Uses email-verification-api (does the actual SD-JWT/DNS/issuer verification, built by the folks at Resend) as a dependency.

Peer Dependencies

  • better-auth ^1.5.0
  • zod ^3.0.0 || ^4.0.0

Server Setup

import { betterAuth } from "better-auth";
import { emailVerificationProtocol } from "better-auth-evp";

export const auth = betterAuth({
  // ...
  account: {
    accountLinking: {
      enabled: true,
      // Add "email-verification-protocol" alongside your other trusted
      // providers (e.g. "email-otp", "magic-link") so a user who already
      // has an account can also sign into it via EVP, instead of getting
      // a separate, unlinked account for the same email.
      trustedProviders: ["email-otp", "email-verification-protocol"],
    },
  },
  plugins: [
    emailVerificationProtocol({
      // Must match the origin your Chrome origin-trial token, DNS
      // `_email-verification` record, etc. were issued for.
      origin: "https://example.com",
      allowedEmailDomains: ["example.com"],
      disableSignUp: false,
      userFields: (verified) => ({
        // any additional fields for a newly created user
      }),
    }),
    // Keep your existing sign-in plugin(s) around for the fallback path,
    // e.g. emailOTP(), magicLink(), ...
  ],
});

Client Setup

import { createAuthClient } from "better-auth/react";
import { emailVerificationProtocolClient } from "better-auth-evp/client";

export const authClient = createAuthClient({
  plugins: [emailVerificationProtocolClient()],
});

Origin Trial Token

As a participating site you must register for the origin trial and serve the token on any page that renders the email form.

To get your token:

  1. Go to the EVP origin trial registration page and sign in.
  2. Enter your web origin (and check the box if you want to match subdomains too).
  3. Accept the terms.
  4. Copy the generated token.

Then serve it as a meta tag in the <head> of any page that renders the email form (we don't use the Origin-Trial HTTP header):

<meta http-equiv="origin-trial" content="YOUR_TOKEN" />

This plugin does not manage that token for you - it's static per-origin configuration, not something to fetch from an API.

Form Markup

<input name="email" type="email" autocomplete="email" />
<input type="hidden" name="token" nonce="{nonce}" autocomplete="email-verification-token" />

{nonce} comes from authClient.evp.getNonce() and must be re-fetched for every attempt.

Usage Example (progressive enhancement)

import { emailVerificationProtocolClient } from "better-auth-evp/client";

async function handleEmailSubmit(email: string, form: HTMLFormElement) {
  const tokenInput = form.elements.namedItem("token") as HTMLInputElement;

  if (tokenInput.value) {
    const { nonce } = await authClient.evp.getNonce();
    const result = await authClient.evp.verify({
      email,
      token: tokenInput.value,
      nonce,
    });

    if (result.data?.verified) {
      // User is signed in already - redirect and stop here.
      return;
    }
  }

  // EVP unsupported/unavailable/failed - fall back to your normal flow.
  await authClient.emailOtp.sendVerificationOtp({ email, type: "sign-in" });
}

API

Server (auth.api)

  • evpGetNonce() - GET /evp/get-nonce - issues a single-use nonce, valid for nonceExpiresIn seconds (default 120).
  • evpVerify({ email, token, nonce }) - POST /evp/verify - verifies the token and, on success, creates a session (and a user, unless disableSignUp is set). Returns { verified: false, reason } instead of throwing on any expected failure (invalid/expired nonce, disallowed email domain, verification failure, email mismatch, sign-up disabled).

Client (authClient.evp)

  • getNonce()
  • verify({ email, token, nonce })

Options

| Option | Type | Default | Description | | ---------------- | ------------------------------------------------ | ---------- | --------------------------------------------------------- | | origin | string | (required) | This relying party's absolute origin, used as audience. | | nonceExpiresIn | number | 120 | Seconds a nonce stays valid. | | allowedEmailDomains | string[] | optional, unrestricted if omitted | Restricts which email domains /evp/verify will even attempt to verify. The email field in your own form is client-side validation only and can be bypassed by calling the API directly - without this option, a caller can make the server perform a DNS lookup + issuer JWKS fetch against any domain they choose (SSRF/abuse surface). Strongly recommended whenever your app only expects a fixed set of domains. | | disableSignUp | boolean | false | Reject verified emails with no existing account. | | userFields | (verified) => T | - | Extra fields for a newly created user. | | onVerified | (verified & { userId }) => void \| Promise<void> | - | Side-effect hook after a session is created. | | verify | custom verification function | verifyEmailToken from email-verification-api | Override for testing. |

License

MIT