Skip to content

Password strength

When users create an account or change their password they will need to enter a password we consider strong enough. Whilst this is primarily for the user's benefit they will find it very annoying if we don't help them understand what strong means. In the past sites would mandate that special, upper, and lower case characters be present. Yet this, sadly, leads to weaker passwords. So instead we will require the password to have enough entropy. This is something we already do in the backend API via the zxcvbn tool.

Only checking the strength in the backend API call leads to a poor user experience as it takes too long for the user to receive feedback on the strength of their password. Fortunately there is a JS zxcvbn version which we can use to provide users instant feedback on the strength of their password.

zxcvbn can be installed via npm,

Run this command in frontend/

npm install --save zxcvbn
npm install --save-dev @types/zxcvbn

Then we can a LinearProgressMeter alongside our existing PasswordField, by adding the following to frontend/src/components/MeteredPasswordField.tsx,

import FormHelperText from "@material-ui/core/FormHelperText";
import LinearProgress from "@material-ui/core/LinearProgress";
import { TextFieldProps } from "@material-ui/core/TextField";
import { FieldHookConfig, useField } from "formik";
import React from "react";
import { useTranslation } from "react-i18next";
import zxcvbn from "zxcvbn";

import PasswordField from "src/components/PasswordField";

const MeteredPasswordField = (
  props: FieldHookConfig<string> & TextFieldProps,
) => {
  const [field] = useField<string>(props);
  const { t } = useTranslation();

  const result = zxcvbn(field.value ?? "");

  let key;
  switch (result.score) {
    case 3:
      key = "MeteredPasswordField.good";
      break;
    case 4:
      key = "MeteredPasswordField.strong";
      break;
    default:
      key = "MeteredPasswordField.weak";
  }

  return (
    <>
      <PasswordField {...props} />
      <LinearProgress value={result.score * 25} variant="determinate" />
      <FormHelperText>{t(key)}</FormHelperText>
    </>
  );
};

export default MeteredPasswordField;

Code splitting

As you can check via the npm run analyse we added in the frontend tooling section zxcvbn has a large impact on the bundle size. This is because zxcvbn includes a dictionary of bad passwords and general words. Therefore we only wish the use to download the bundle including zxcvbn when they need to use it.

We can split out the MeteredPasswordField from the main bundle by always lazily loading it and falling back to the simpler PasswordField via the following snippet,

import PasswordField from "src/components/PasswordField";

const MeteredPasswordField = React.lazy(
  () => import("src/components/MeteredPasswordField"),
);

const jsx = (
  <Suspense fallback={<PasswordField />}>
    <MeteredPasswordField />
  </Suspense>
);