nextauthjs/next-auth · warning · Verification

Verification

Error message

Verification

What it means

A Verification error thrown when processing an email sign-in (magic link) callback: the verification token has no matching invite (hasInvite false), has expired, or the identifier in the callback URL does not match the token's identifier. The error carries { hasInvite, expired } so the client can distinguish the causes.

Source

Thrown at packages/core/src/lib/actions/callback/index.ts:234

      }

      const secret = provider.secret ?? options.secret
      // @ts-expect-error -- Verified in `assertConfig`.
      const invite = await adapter.useVerificationToken({
        // @ts-expect-error User-land adapters might decide to omit the identifier during lookup
        identifier: paramIdentifier, // TODO: Drop this requirement for lookup in official adapters too
        token: await createHash(`${paramToken}${secret}`),
      })

      const hasInvite = !!invite
      const expired = hasInvite && invite.expires.valueOf() < Date.now()
      const invalidInvite =
        !hasInvite ||
        expired ||
        // The user might have configured the link to not contain the identifier
        // so we only compare if it exists
        (paramIdentifier && invite.identifier !== paramIdentifier)
      if (invalidInvite) throw new Verification({ hasInvite, expired })

      const { identifier } = invite
      const user = (await adapter!.getUserByEmail(identifier)) ?? {
        id: crypto.randomUUID(),
        email: identifier,
        emailVerified: null,
      }

      const account: Account = {
        providerAccountId: user.email,
        userId: user.id,
        type: "email" as const,
        provider: provider.id,
      }

      const redirect = await handleAuthorized({ user, account }, options)
      if (redirect) return { redirect, cookies }

View on GitHub (pinned to a1a16a5a77)

Solutions

  1. Request a new sign-in email and use the fresh link promptly (links are single-use).
  2. Increase the verification token maxAge: emailVerification: { sendVerificationRequest, maxAge: ... } or via adapter token settings.
  3. Verify both the app sending and consuming the link use the same AUTH_SECRET and database/adapter.
  4. Exclude /api/auth from link-scanning email security tools, or send links from a domain scanners ignore less aggressively.
  5. Show a friendly 'link expired, sign in again' page by handling the Verification error's hasInvite/expired properties.

Example fix

// before
EmailProvider({ server, from })
// after
EmailProvider({ server, from, maxAge: 60 * 60 }) // 1 hour instead of default 24h
Defensive patterns

Strategy: try-catch

Validate before calling

// Before trusting a token client-side, you can only check URL presence:
const hasToken = new URLSearchParams(window.location.search).has('token')
if (!hasToken) show('This link is incomplete — request a new sign-in email')

Try / catch

// Client-side: handle the error page Auth.js renders; server-side:
try {
  await callback(request)
} catch (e) {
  if (e instanceof Verification) {
    const { hasInvite, expired } = e
    // redirect to /auth/error?error=Verification with hasInvite/expired info
  }
}

Prevention

When it happens

Trigger: User clicks a magic link after the token expired (default 24h maxAge, one-time use); clicking the same link twice (token consumed on first use); an email/URL mangler (mail scanner, link preview bot) pre-consuming the link; the link was edited or the email param altered so it no longer matches the token's identifier.

Common situations: Corporate email security bots prefetching links; users forwarding magic-link emails; long delays between requesting and clicking the link; multiple Auth.js instances with different secrets/database so the token cannot be found.

Related errors


AI-assisted analysis of nextauthjs/next-auth@a1a16a5a77 (2026-08-28). Data as JSON: /api/errors/2aeb16bfc036aa17. Report an issue: GitHub.