Creative Bracket

Build a User Authentication system (Part 2)

In the first part we implemented the registration flow as part of adding a user to our database. We learnt about the recommended approach to storing passwords in the database using the SHA256 algorithm to convert our password into a string ‘hash’ combined with a salt.

In this part we will look at the login and logout journey for our registered user. At the end of this article you will be comfortable understanding and writing basic user authentication systems.


1. Create the login route

In bin/main.dart we have a couple of if statements checking the route and action of the request and responding accordingly to it.

Let’s add another condition within the request listener block for a GET to /login:

...
server.listen((HttpRequest request) async {
  // ...
  // ...
  
  // Check a `GET` on `/login`
  if (method == 'GET' && path == '/login') {
    response.write(renderHtml('''
      <h1>Login</h1>
      <form action="/login" method="post">
        <input type="text" name="username" placeholder="Enter your username" />
        <input type="password" name="password" placeholder="Enter your password" />
        <button>Go to the dashboard</button>
      </form>
    ''', 'Login to your Account'));
  }

  await response.close();

});

Running dart bin/main.dart and visiting http://localhost:8089/login should display the screen below:

User Authentication Login Screen

Submitting the form will attempt to post the results to the backend which will result in am empty response. We need the logic to receive whatever we have entered into the form and use that to give the user access.

2. Listen for the posted credentials and attempt to log in the user

Here’s what we need to do:

  1. Define a conditional statement to check for POST request to /login
  2. Parse the POST request payload which is sent as application/x-www-form-urlencoded content type
  3. Find the user in the database with the matching username. If the user exists we will receive an object with the username, hashed password and salt.
  4. Take the salt from the retrieved user and pass that into our hashPassword function (this was created in Part 1) along with the password the user has provided for logging in
  5. Attempt to match the generated hash in step 3 with the hashed password already stored in the database. If they match then log the user in. If not respond with an error message.
  6. Create a randomised string value to represent the logged in user. This will be stored in a separate MongoDB collection to represent the logged in user.
  7. Redirect the user to the /dashboard route

Here’s the logic covering the first two steps:

server.listen((HttpRequest request) {
  // ...
  // ...

  // 1. Check for `POST` on `/login`
  if (method == 'POST' && path == '/login') {
    // 2. Parse the payload
    var content = await request.transform(Utf8Decoder()).join();
    var params = Uri.splitQueryString(content);
    var user = params['username'];
    var password = params['password'];

    // TODO: 3. Find the user in the database with matching details

    // TODO: 4. Take salt and hash provided password

    // TODO: 5. Compare hash and login user. Or respond with error

    // TODO: 6. Create a string-based token to represent the logged in user

    // TODO: 7. Redirect the user to the /dashboard route

  }
  
  await response.close();
});

We are using the same logic to parse the payload like we did in Part 1 during the registration journey. Perhaps we should create a function that takes the request as input, run the parsing logic and return the credentials. I would challenge you to look at how you can do that. If you can’t inform me by posting a comment below.

Here’s the logic for step 3:

server.listen((HttpRequest request) {
  // ...
  // ...

  // 1. Check for `POST` on `/login`
  if (method == 'POST' && path == '/login') {
    // 2. Parse the payload
    var content = await request.transform(Utf8Decoder()).join();
    var params = Uri.splitQueryString(content);
    var user = params['username'];
    var password = params['password'];

    // 3. Find the user in the database with matching details
    var user = await store.findOne(where.eq('username', username));
    if (user == null) {
      response..statusCode = HttpStatus.notAuthorised
              ..write('Incorrect user and/or password');
    }
  }
  // ...
});

Sharing is caring 🤗

If you enjoyed reading this post, please share this through the various social buttons hovering on the left/top side of the screen ↖️⬆️. Also, check out and subscribe to my YouTube channel (hit the bell icon too) for videos on Dart.

Watch my Free Get started with Dart course on Egghead.io and Subscribe to my email newsletter to download my Free 35-page eBook titled Get started with Dart and to be notified when new content is released.

Like, share and follow me 😍 for more content on Dart.

Jermaine Oppong

Hello 👋, I show programmers how to build full-stack web applications with the Dart SDK. I am passionate about teaching others, having received tremendous support on sites like dev.to and medium.com for my articles covering various aspects of the Dart language and ecosystem.

Useful learning materials