Checking Password Strength in Elm, Part 1: Zxcvbn

March 4, 2018 (Updated, April 2, 2024)

As part of a recent Elm project, I wrote some front-end user registration code which required that new users choose a password. I used the zxcvbn library to measure password strength and this seems like a good, non-trivial example to show how to call Javascript from Elm. In Part 2, we’ll look at how to add extra checks by calling the Have I Been Pwned passwords API which maintains a database of half a billion compromised password hashes.

The full code for both parts can be found on github.

Zxcvbn is a password strength estimator which makes use of password frequency lists, dictionaries and common patterns. Note that the default version is heavily biased towards English language so it would need to be built with custom dictionaries if that’s an issue 1.

The strength of a password is based on estimating the number of guesses it would take for a password cracker to find it. It’s not perfect, but is better than forcing annoying rules on users such as adding numbers and symbols to passwords which is no longer seen as good practice. Standards authorities like NIST now recommend disallowing simple sequences and rejecting passwords which are found in password breach databases.

Elm Ports

From Elm, we want to be able to call a checkPassword function, passing the password as a string. This function is a “port”, so the Elm runtime knows to call an external Javascript function as the implementation.

port checkPassword : String -> Cmd msg

port passwordChecked : (Json.Value -> msg) -> Sub msg

Zxcvbn produces a JSON value containing a score for the password and information about how the score was calculated. The second port, passwordChecked allows us to subscribe to the Zxcvbn response. The responses will then be received as messages through the application update function.

Javascript

In Javascript we need to hook up our port implementations to Javascript code. The checkPassword function is just implemented as a call to the zxcvbn function, passing in the password. It then sends the response back through the passwordChecked port.

app.ports.checkPassword.subscribe(function(password) {
    var report = zxcvbn(password);
    app.ports.passwordChecked.send(report);
});

Decoding JSON

When we receive the Zxcvbn report as a JSON value, we decode it into a matching Elm data structure to make sure it contains what we expect and to make it easier to work with:

type alias Zxcvbn =
    { password : String
    , guesses : Float
    , guessesLog10 : Float
    , calcTime : Int
    , crackTimesSeconds : CrackTimesSeconds
    , crackTimesDisplay : CrackTimesDisplay
    , score : Int
    , feedback : Feedback
    , matchSequence : List String
    }

Writing decoders in Elm is straightforward enough but requires quite a lot of boilerplate code. You can check the github repo for details. The call to Zxcvbn returns a lot of data, most of which isn’t needed for a real-world application. Even though the Zxcvbn Elm record type above retains most of that data it could be a lot simpler 2. For a real application, we would probably only need the score and feedback fields to provide the strength meter and information on why particular passwords are unsuitable. The decoder would then also be a lot simpler. For a demo like this though, it’s interesting to show how a password is matched by the library.

The Main module

The rest of the code is a standard Elm application. We have a simple model to contain the entered password and the decoded Zxcvbn report

type alias Model =
    { password : String
    , zxcvbn : Maybe Zxcvbn
    }

In our update function we need to handle messages for both password input and password check responses

type Msg
    = SetPassword String
    | PasswordChecked Json.Value

The first message constructor is handled by just setting the password value in the model. For the second we call the Zxcvbn decoder and set the zxcvbn field in the model. The view function just renders an HTML input field for the password and shows the strength information for the entered value.

It’s important that we remember to include the passwordChecked port in our subscriptions function so that we receive responses from the Javascript code. These will arrive wrapped in a PasswordChecked message

subscriptions : Model -> Sub Msg
subscriptions _ =
    passwordChecked PasswordChecked

Conclusion

It’s quite easy to make use of a Javascript library like Zxcvbn, getting it to do a lot of work for us, but without compromising the safety of our Elm code. Ports provide a boundary which forces us to decode the data we get from Javascript into type-safe values before using them.

In the second part, we’ll look at calling an external API to get extra information on the suitability of our chosen password.


  1. For example, it will flag up “iloveyou” as being one of the most common passwords but it doesn’t have a problem with “ichliebedich”. Have I Been Pwned? doesn’t suffer from this issue and will tell you that the latter has been found more than 16000 times in password data breaches, so is clearly unsuitable. ↩︎

  2. The matchSequence field is simplified to just include a list of matched tokens and the pattern type (dictionary, date, keyboard sequence etc.) rather than the full details for each sub-match. ↩︎