[GH-ISSUE #779] Password strength #25750

Closed
opened 2026-04-17 16:01:18 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @emroot on GitHub (Dec 6, 2024).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/779

I can't see to find a config for this but it would be nice an option to configure the password strength, (special chars, numbers, uppercase, etc.). Right now I could just type 12345678...

Unless I missed it in the docs.

Originally created by @emroot on GitHub (Dec 6, 2024). Original GitHub issue: https://github.com/better-auth/better-auth/issues/779 I can't see to find a config for this but it would be nice an option to configure the password strength, (special chars, numbers, uppercase, etc.). Right now I could just type 12345678... Unless I missed it in the docs.
GiteaMirror added the locked label 2026-04-17 16:01:18 -05:00
Author
Owner

@Bekacru commented on GitHub (Dec 6, 2024):

according to NIST, password composition rules are discouraged, which is why they won't be implemented natively in Better Auth to follow best practices. But, NIST recommends checking passwords against leaked password databases. Planning to introduce this natively in one of the next couple of releases.

https://pages.nist.gov/800-63-4/sp800-63b/passwords/#complexity

<!-- gh-comment-id:2522268083 --> @Bekacru commented on GitHub (Dec 6, 2024): according to NIST, password composition rules are discouraged, which is why they won't be implemented natively in Better Auth to follow best practices. But, NIST recommends checking passwords against leaked password databases. Planning to introduce this natively in one of the next couple of releases. https://pages.nist.gov/800-63-4/sp800-63b/passwords/#complexity
Author
Owner

@emroot commented on GitHub (Dec 7, 2024):

Gotcha. I can get around with introducing a weak, strong UI component to encourage my users to choose a stronger password but not require it.
Thanks for the response.

<!-- gh-comment-id:2524959050 --> @emroot commented on GitHub (Dec 7, 2024): Gotcha. I can get around with introducing a weak, strong UI component to encourage my users to choose a stronger password but not require it. Thanks for the response.
Author
Owner

@jasongerbes commented on GitHub (Dec 10, 2024):

@Bekacru, in case it's helpful - here's my basic custom passwordStrength plugin that uses the Have I Been Pwned API to check if a password has been compromised before sign up/set password/change password.

import { sha1 } from '@oslojs/crypto/sha1';
import { encodeHexLowerCase } from '@oslojs/encoding';
import { APIError } from 'better-auth/api';
import { type BetterAuthPlugin } from 'better-auth/plugins';

export const passwordStrength = () => {
  return {
    id: 'password-strength',
    hooks: {
      before: [
        {
          matcher: (context) => {
            return (
              context.path.startsWith('/sign-up') ||
              context.path.startsWith('/change-password') ||
              context.path.startsWith('/set-password') ||
              context.path.startsWith('/reset-password') ||
              context.path.startsWith('/email-otp/reset-password')
            );
          },
          handler: async (ctx) => {
            const { password } = ctx.body;

            if (!password || typeof password !== 'string') {
              return;
            }

            const isCompromised = await hasPasswordBeenPwned(password);
            if (isCompromised) {
              throw new APIError('BAD_REQUEST', {
                message:
                  'Password has been compromised. Choose a strong new password.',
              });
            }
          },
        },
      ],
    },
  } satisfies BetterAuthPlugin;
};

async function hasPasswordBeenPwned(password: string): Promise<boolean> {
  const hash = encodeHexLowerCase(sha1(new TextEncoder().encode(password)));
  const hashPrefix = hash.slice(0, 5);
  const response = await fetch(
    `https://api.pwnedpasswords.com/range/${hashPrefix}`,
  );

  const data = await response.text();
  const items = data.split('\n');

  for (const item of items) {
    const hashSuffix = item.slice(0, 35).toLowerCase();
    if (hash === hashPrefix + hashSuffix) {
      return true;
    }
  }

  return false;
}

My intention was to extend this to use zxcvbn-ts to enforce a minimum password strength (and potentially use @zxcvbn-ts/matcher-pwned for the Have I Been Pwned check), but it sounds like checking password strength with such a library is discouraged (?)

Note: The hasPasswordBeenPwned function is based on verifyPasswordStrength from a Lucia Auth example.

<!-- gh-comment-id:2530417834 --> @jasongerbes commented on GitHub (Dec 10, 2024): @Bekacru, in case it's helpful - here's my basic custom `passwordStrength` plugin that uses the [Have I Been Pwned](https://haveibeenpwned.com/API/v3#SearchingPwnedPasswordsByRange) API to check if a password has been compromised before sign up/set password/change password. ```ts import { sha1 } from '@oslojs/crypto/sha1'; import { encodeHexLowerCase } from '@oslojs/encoding'; import { APIError } from 'better-auth/api'; import { type BetterAuthPlugin } from 'better-auth/plugins'; export const passwordStrength = () => { return { id: 'password-strength', hooks: { before: [ { matcher: (context) => { return ( context.path.startsWith('/sign-up') || context.path.startsWith('/change-password') || context.path.startsWith('/set-password') || context.path.startsWith('/reset-password') || context.path.startsWith('/email-otp/reset-password') ); }, handler: async (ctx) => { const { password } = ctx.body; if (!password || typeof password !== 'string') { return; } const isCompromised = await hasPasswordBeenPwned(password); if (isCompromised) { throw new APIError('BAD_REQUEST', { message: 'Password has been compromised. Choose a strong new password.', }); } }, }, ], }, } satisfies BetterAuthPlugin; }; async function hasPasswordBeenPwned(password: string): Promise<boolean> { const hash = encodeHexLowerCase(sha1(new TextEncoder().encode(password))); const hashPrefix = hash.slice(0, 5); const response = await fetch( `https://api.pwnedpasswords.com/range/${hashPrefix}`, ); const data = await response.text(); const items = data.split('\n'); for (const item of items) { const hashSuffix = item.slice(0, 35).toLowerCase(); if (hash === hashPrefix + hashSuffix) { return true; } } return false; } ``` My intention was to extend this to use [zxcvbn-ts](https://github.com/zxcvbn-ts/zxcvbn) to enforce a minimum password strength (and potentially use [@zxcvbn-ts/matcher-pwned](https://www.npmjs.com/package/@zxcvbn-ts/matcher-pwned) for the Have I Been Pwned check), but it sounds like checking password strength with such a library is discouraged (?) Note: The `hasPasswordBeenPwned` function is based on [verifyPasswordStrength](https://github.com/lucia-auth/example-nextjs-email-password-webauthn/blob/main/lib/server/password.ts#L18) from a Lucia Auth example.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#25750