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.
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:
Define a conditional statement to check for
POST
request to/login
Parse the POST request payload which is sent as
application/x-www-form-urlencoded
content typeFind 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.
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 inAttempt 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.
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.
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');
}
}
// ...
});