Incorrect error thrown wrong password - UNAUTHORIZED #1830

Closed
opened 2026-03-13 09:06:23 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @austinm911 on GitHub (Sep 3, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

When using a wrong password this will throw

Two issues

2025-09-03T06:30:11.880Z ERROR [Better Auth]: Invalid password
[browser] [1ccd7cd3] LOG: 🚀 ~ error: {"status":401,"statusText":"UNAUTHORIZED"} (http://localhost:3000/src/features/auth/login.tsx:110:17)
    at Object.onSubmit (http://localhost:3000/src/features/auth/login.tsx:110:17)
export type AuthError = keyof Auth['$ERROR_CODES'] | 'UNAUTHORIZED' // Auth is typed auth 

const form = useAppForm({
		defaultValues: {
			email: '',
			password: '',
		} as typeof loginSchema.Type,
		validators: {
			onChange: Schema.standardSchemaV1(loginSchema),
		},
		onSubmit: async ({ value }) => {
			const { error } = await authClient.signIn.email(
				{
					email: value.email,
					password: value.password,
					callbackURL: router.routesByPath['/app'].fullPath,
				},
				{
					onSuccess: () => {
						router.navigate({ to: '/' })
					},
					onError: (error) => {
                        // have to manually add this unauthorized code  
						switch (error.error.statusText as AuthError) { 
							case 'UNAUTHORIZED': 
								toast.error('Invalid email or password')
								break
							default:
								toast.error('Something went wrong. Please try again.')
								break
						}
					},
				},
			)
			if (error) {
				console.log('🚀 ~ error:', error)
				toast.error(error.message)
			}
		},
	})

Current vs. Expected behavior

Error would be INVALID_PASSWORD

What version of Better Auth are you using?

1.3.7

System info

System:
    OS: macOS 15.6.1
    CPU: (10) arm64 Apple M1 Pro
    Memory: 130.77 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Browsers:
    Brave Browser: 139.1.81.136
    Chrome: 139.0.7258.155
    Safari: 18.6

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

No response

Originally created by @austinm911 on GitHub (Sep 3, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce When using a wrong password this will throw Two issues - 1) It throws "statusText": "UNAUTHORIZED" which is not present in the Auth error codes (see switch statement) - 2) It should actually throw correct Error Code/statusText seen here ? https://github.com/better-auth/better-auth/blob/c84b37faaf2924119ce33fb49b3f2e8aca2e8ef1/packages/better-auth/src/error/codes.ts#L7 ```ts 2025-09-03T06:30:11.880Z ERROR [Better Auth]: Invalid password [browser] [1ccd7cd3] LOG: 🚀 ~ error: {"status":401,"statusText":"UNAUTHORIZED"} (http://localhost:3000/src/features/auth/login.tsx:110:17) at Object.onSubmit (http://localhost:3000/src/features/auth/login.tsx:110:17) ``` ```ts export type AuthError = keyof Auth['$ERROR_CODES'] | 'UNAUTHORIZED' // Auth is typed auth const form = useAppForm({ defaultValues: { email: '', password: '', } as typeof loginSchema.Type, validators: { onChange: Schema.standardSchemaV1(loginSchema), }, onSubmit: async ({ value }) => { const { error } = await authClient.signIn.email( { email: value.email, password: value.password, callbackURL: router.routesByPath['/app'].fullPath, }, { onSuccess: () => { router.navigate({ to: '/' }) }, onError: (error) => { // have to manually add this unauthorized code switch (error.error.statusText as AuthError) { case 'UNAUTHORIZED': toast.error('Invalid email or password') break default: toast.error('Something went wrong. Please try again.') break } }, }, ) if (error) { console.log('🚀 ~ error:', error) toast.error(error.message) } }, }) ``` ### Current vs. Expected behavior Error would be `INVALID_PASSWORD` ### What version of Better Auth are you using? 1.3.7 ### System info ```bash System: OS: macOS 15.6.1 CPU: (10) arm64 Apple M1 Pro Memory: 130.77 MB / 16.00 GB Shell: 5.9 - /bin/zsh Browsers: Brave Browser: 139.1.81.136 Chrome: 139.0.7258.155 Safari: 18.6 ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 09:06:23 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 3, 2025):

You're correct—INVALID_PASSWORD is the expected error code for wrong password attempts, as defined in the Better Auth error codes file, and UNAUTHORIZED is not a documented auth error code in this context. The backend appears to be returning a generic UNAUTHORIZED status (likely HTTP 401) instead of the more specific INVALID_PASSWORD code, which leads to the mismatch you're seeing in the frontend. This is likely a gap in the backend error mapping logic, not an intentional design. To ensure consistent error handling, the backend should return INVALID_PASSWORD for wrong password scenarios so your frontend can handle it directly without workarounds. You can reference the canonical error codes here: codes.ts.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Sep 3, 2025): <!-- Answer --> You're correct—`INVALID_PASSWORD` is the expected error code for wrong password attempts, as defined in the Better Auth error codes file, and `UNAUTHORIZED` is not a documented auth error code in this context. The backend appears to be returning a generic `UNAUTHORIZED` status (likely HTTP 401) instead of the more specific `INVALID_PASSWORD` code, which leads to the mismatch you're seeing in the frontend. This is likely a gap in the backend error mapping logic, not an intentional design. To ensure consistent error handling, the backend should return `INVALID_PASSWORD` for wrong password scenarios so your frontend can handle it directly without workarounds. You can reference the canonical error codes here: [codes.ts](https://github.com/better-auth/better-auth/blob/23daf79a427ea987a4d94d0431e0fb306d26d464/packages/better-auth/src/error/codes.ts). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/1a3bba78-6066-48a7-aed9-afb25f0dd460?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/1a3bba78-6066-48a7-aed9-afb25f0dd460?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/1a3bba78-6066-48a7-aed9-afb25f0dd460?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/1a3bba78-6066-48a7-aed9-afb25f0dd460?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/1a3bba78-6066-48a7-aed9-afb25f0dd460?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/1a3bba78-6066-48a7-aed9-afb25f0dd460?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/1a3bba78-6066-48a7-aed9-afb25f0dd460?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4379)
Author
Owner

@erickweil commented on GitHub (Sep 3, 2025):

The way I see this, maybe returning a opaque UNAUTHORIZED error regardless of the underlying problem is the intended behaviour, one shouldn't adquire any info from the login errors, like if the email exists or not vs just the password that mismatched.

This is yet more evident when you look at the source code and you'll found they do a hash of the password even if the user isn't found (email incorrect) so that by measuring response times this info isn't revealed

see below code from /sign-in/email

                if (!user) {
                        // Hash password to prevent timing attacks from revealing valid email addresses
                        // By hashing passwords for invalid emails, we ensure consistent response times
                        await ctx.context.password.hash(password);
                        ctx.context.logger.error("User not found", { email });
                        throw new APIError("UNAUTHORIZED", {
                                message: BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD,
                        });
                }
@erickweil commented on GitHub (Sep 3, 2025): The way I see this, maybe returning a opaque UNAUTHORIZED error regardless of the underlying problem is the intended behaviour, one shouldn't adquire any info from the login errors, like if the email exists or not vs just the password that mismatched. This is yet more evident when you look at the source code and you'll found they do a hash of the password even if the user isn't found (email incorrect) so that by measuring response times this info isn't revealed see below code from /sign-in/email ``` if (!user) { // Hash password to prevent timing attacks from revealing valid email addresses // By hashing passwords for invalid emails, we ensure consistent response times await ctx.context.password.hash(password); ctx.context.logger.error("User not found", { email }); throw new APIError("UNAUTHORIZED", { message: BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD, }); } ```
Author
Owner

@austinm911 commented on GitHub (Sep 4, 2025):

@erickweil great point, thanks for the feedback. I guess that leaves getting the UNAUTHORIZED typed for convenience then 😄

@austinm911 commented on GitHub (Sep 4, 2025): @erickweil great point, thanks for the feedback. I guess that leaves getting the `UNAUTHORIZED` typed for convenience then 😄
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1830