Build a User Authentication system (Part 2)

Build a User Authentication system (Part 2)

In part 1 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 journeys for our registered user. At the end of this article you will be comfortable understanding and writing basic user authentication systems.

Watch on YouTube

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:

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');
    }
  }
  // ...
});

Watch video for complete steps on YouTube