Skip to content

Firebase Auth errorFirebase: “The action code is invalid” (auth/invalid-action-code)

A password reset, email verification or email change link fails with auth/invalid-action-code or auth/expired-action-code. The link carries a one-time code called oobCode. Once it has been used, or after it expires, Firebase rejects it. Custom handler pages also break it by reading the wrong parameter or applying the code twice.

Short answer

Treat the code as single use. On your handler page, read oobCode from the URL, check it once with verifyPasswordResetCode or checkActionCode, and only then apply it. If the check fails, show a button that sends a new email instead of a raw error.

Why Firebase rejects the code, and the fix

  1. 01

    The link was already used

    “Thrown if the action code is invalid. This can happen if the code is malformed or has already been used.” Firebase, firebase.auth.Auth (applyActionCode)

    The person clicked the link twice, or an earlier tab already applied it. Your own page can also apply it twice: an effect that runs twice in development, or a handler that calls applyActionCode on every render. Some email security scanners open links before the person does, so a handler that applies the code on page load can use it up; let the person press a button first. Apply the code once, then send the person on.

  2. 02

    The link expired

    “Thrown if the action code has expired.” Firebase, firebase.auth.Auth (applyActionCode)

    Reset and verification links do not last forever. An old email, or a link opened days later, fails with auth/expired-action-code. Offer to send a new link from the error screen.

  3. 03

    A custom handler reads the wrong value

    “A one-time code, used to identify and verify a request” Firebase, Create custom email action handlers

    With a custom email action handler, the code arrives in the oobCode query parameter, next to mode and continueUrl. A handler that reads a different name, loses the query string in a redirect, or trims the code passes Firebase something it never issued.

    const params = new URLSearchParams(window.location.search);
    const code = params.get('oobCode');
    if (!code) return; // no code in the link
    try {
      const email = await verifyPasswordResetCode(auth, code);
      // show the new-password form for email
    } catch (e) {
      // invalid or expired: offer to send a new link
    }
  4. 04

    The handler uses a different Firebase project

    A code from the production project fails on a page set up with the staging config, and the other way round. Check the handler page initialises the same project that sent the email.

  5. 05

    Reading the message in your app

    “The action code is invalid. This can happen if the code is malformed, expired, or has already been used.” GitHub, firebase-js-sdk auth error messages

    The web SDK’s message lists all three reasons. Show the person a clear next step instead.

Checked against the vendor’s docs on : Firebase reference: firebase.auth.Auth error codes; Firebase docs: Create custom email action handlers; firebase-js-sdk: auth error messages.

Rather hand it over?

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

Related: Firebase auth/weak-password · Firebase auth/unauthorized-domain · Supabase email link is invalid or has expired · Firebase auth/invalid-email

Questions

More on this error

What is the difference between invalid-action-code and expired-action-code?

expired-action-code means the code is too old. invalid-action-code means it is malformed or already used. Both need a new email.

Why does the reset fail after the person typed a new password?

confirmPasswordReset can also fail with auth/weak-password if the new password is too short. That is a different error from invalid-action-code, so check the length before you submit.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.