mastra-ai/mastra · error

auth callback rejected (invalid state/token); 400 HTML failu

Error message

auth callback rejected (invalid state/token); 400 HTML failure page returned

What it means

During the interactive `mastra auth login` OAuth-style flow, the local HTTP callback validates the returned state parameter and required fields (token, user, org). If the state does not match the CSRF value the CLI generated, or token/user/org are missing, it responds with a 400 HTML failure page and throws this error, aborting login to prevent CSRF/token-injection attacks.

Source

Thrown at packages/cli/src/commands/auth/credentials.ts:304

      handleAbort();
      return;
    }
    if (options.skipOnInput) {
      stopListeningForSkip = listenForSkipInput(() => finish(() => reject(new LoginCancelledError())));
    }

    server.on('request', (req, res) => {
      const url = new URL(req.url!, `http://localhost:${port}`);

      if (url.pathname === '/callback') {
        const callbackState = url.searchParams.get('state');
        const token = url.searchParams.get('token');
        const refreshToken = url.searchParams.get('refresh_token');
        const userParam = url.searchParams.get('user');
        const orgId = url.searchParams.get('org');

        if (callbackState !== state || !token || !userParam || !orgId) {
          res.writeHead(400, { 'Content-Type': 'text/html' });
          res.end(callbackPage({ success: false }));
          return;
        }

        const user = JSON.parse(decodeURIComponent(userParam));

        res.writeHead(200, { 'Content-Type': 'text/html', Connection: 'close' });
        res.end(callbackPage({ success: true }));

        finish(() => resolve({ token, refreshToken, user, organizationId: orgId }));
      }
    });
  });

  const creds: Credentials = {
    token: result.token,
    ...(result.refreshToken ? { refreshToken: result.refreshToken } : {}),
    user: result.user,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Abort the failed browser tab and restart cleanly with `mastra auth login`, completing the flow in the newly opened tab without reusing old URLs.
  2. Ensure only one `mastra auth login` runs at a time.
  3. Paste the full callback URL including all query parameters if completing manually.
  4. Disable browser extensions that strip query strings, or try another browser.

Example fix

// before (stale tab reused)
http://localhost:PORT/callback?state=OLD_STATE...  # 400 failure page
// after
mastra auth login  # fresh state, use the newly opened tab only
Defensive patterns

Strategy: try-catch

Type guard

function isLoginStateError(err: unknown): boolean {
  return err instanceof Error &&
    err.message.includes('auth callback rejected');
}

Try / catch

try {
  await login(signal, options);
} catch (err) {
  if (isLoginStateError(err)) {
    console.error('Login callback invalid — restart the flow: `mastra auth login`, and use only the newly opened browser tab.');
    return login(signal, options); // single clean retry
  }
  throw err;
}

Prevention

When it happens

Trigger: The browser callback URL hit the local server with state != the generated state, or with missing token / user / org query params — e.g. user pasted a truncated URL, bookmarked a stale callback URL, the login page was opened twice, or a redirect dropped parameters.

Common situations: Re-opening an old login tab after the CLI restarted (state mismatch); copying only part of the callback URL; browser extensions stripping query params; concurrent `mastra auth login` runs clobbering each other's state; firewall/alternate port sending the callback to the wrong CLI instance.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/3b7e1131c5a37e66. Report an issue: GitHub.