mastra-ai/mastra · error

Invalid email or password

Error message

Invalid email or password

What it means

Thrown in signIn() when the Neon Auth sign-in API responds with a non-OK status. The library first tries to surface the server-provided message from the error body; if the body has no message (or the body isn't JSON), it falls back to this generic message. It deliberately hides the real cause to avoid leaking whether the account exists.

Source

Thrown at auth/neon/src/index.ts:297

    return true;
  }

  // ── ICredentialsProvider ──

  async signIn(email: string, password: string, request: Request): Promise<CredentialsResult<EEUser>> {
    const response = await fetch(`${this.baseUrl}/auth/sign-in/email`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...(request?.headers ? Object.fromEntries(request.headers.entries()) : {}),
      },
      body: JSON.stringify({ email, password }),
    });

    if (!response.ok) {
      const errorData = (await response.json().catch(() => ({}))) as { message?: string };
      throw new Error(errorData.message || 'Invalid email or password');
    }

    const result = (await response.json()) as { user?: NeonSessionResponse['user']; token?: string | null };

    if (!result?.user) {
      throw new Error('Invalid email or password');
    }

    const cookies = parseCookies(response);

    return {
      user: mapNeonUserToEEUser(result.user),
      token: result.token ?? undefined,
      cookies,
    };
  }

  async signUp(

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Double-check the email and password being passed
  2. Register the user first (sign-up) before calling signIn
  3. Log the underlying HTTP response (or add temporary logging) to see the server's actual status/body — a 502/503 indicates infrastructure, not bad credentials
  4. If a custom error message is expected, ensure the Neon Auth error responses include a 'message' field

Example fix

// before
await auth.signIn({ email: userInput.email, password: userInput.password });
// after
if (!userExists(email)) await auth.signUp({ email, password });
await auth.signIn({ email, password });
Defensive patterns

Strategy: try-catch

Validate before calling

function assertSignInInput(email: unknown, password: unknown): asserts email is string {
  if (typeof email !== 'string' || !email.includes('@') || email.length === 0) {
    throw new Error('A valid email is required');
  }
  if (typeof password !== 'string' || password.length === 0) {
    throw new Error('A password is required');
  }
}

Type guard

function isAuthError(err: unknown): err is Error & { message: string } {
  return err instanceof Error && /invalid email or password/i.test(err.message);
}

Try / catch

try {
  const session = await auth.signIn({ email, password });
} catch (err) {
  if (isAuthError(err)) {
    showUserFriendlyError('Those credentials do not match an account'); // don't leak which field failed
  } else {
    throw err; // infrastructure error — surface it
  }
}

Prevention

When it happens

Trigger: Calling auth.signIn({ email, password }) where the credentials are wrong, the user doesn't exist, the response body is not JSON, or the server returns an error without a 'message' field.

Common situations: Typos in email/password; user never signed up with Neon Auth; a reverse proxy returning an HTML 502 page (body parse fails, so the real cause is masked); Neon Auth API changed its error body shape.

Related errors


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