Username/Password Authentication in Vapor 3

942 Views

This tutorial will cover username and password authentication. This is very similar to Basic HTTP Authorization, except in this case, we manually authenticate the user ourselves--the middleware does not handle it for us.

Download the Project

To get started, download the project from Github here. It's very barebones, with a simple User class, as well as a UserController where we will handle the auth. I have also included the Authentication and FluentPostgreSQL packages.

The createUser method in the UserController has already been filled in for us, as the focus on this tutorial is the authentication, not User creation.

Database Setup

I am using Postgres for my database in this tutorial. However, you can also use SQLite (or MYSQL--but I havent used that, so not sure about the config for it)

If you're using Postgres

If you're using Postgres as well, first you'll need to create the database. Simply run the following in terminal:

$ createdb usernamepasswordauth

And you're done!

If you're using SQLite

If you prefer to use SQLite, you'll need to do three simple things:

1) Go to configure.swift, and delete lines 20-28 (starting at let psqlConfig = PostgreSQLDatabaseConfig).

2) Then, replace that with the contents of this gist.

3) Replace all instances of import FluentPostgreSQL with import FluentSQLite, and your User class will need to conform to SQLiteModel, not PostgreSQLModel.

Conform User class to PasswordAuthenticatable

The first thing we need to do, is to conform our User class to the PasswordAuthenticatable protocol:

    extension User: PasswordAuthenticatable {
    
    }

Add import Authentication to your User and UserController class as well.

The compiler will then tell us that we haven't yet conformed to the protocol properly. Let's fix that by adding the following:

    extension User: PasswordAuthenticatable {
        static var usernameKey: WritableKeyPath { return \User.username }
        static var passwordKey: WritableKeyPath { return \User.password }
    }

We need to conform to this protocol so the Authentication package knows which keys to authenticate against.

Implement the Login

Next, we need to complete the login. This part is surprisingly simple! Just replace the loginUser method in the UserController with the following:

    func loginUser(_ request: Request) throws -> Future {
        return try request.content.decode(User.self).flatMap(to: User.self) { user in // 1
            let passwordVerifier = try request.make(BCryptDigest.self) // 2
            return User.authenticate(username: user.username, password: user.password, using: passwordVerifier, on: request).unwrap(or: Abort.init(HTTPResponseStatus.unauthorized)) // 3
        }
    }

At 1, we decode the request, and call flatMap on the result. We call flatMap because the result of the User.authenticate call is a future, which is what we will need to return. Since we need to return a future from the closure, we call flatMap, not map.

At 2, we create the password verifier, which will be used by the Authentication package internally to compare our hashed password.

At 3, we use the User.authenticate method, and pass in the username, password, and password verifier. The result of this call is actually an optional future User (Future), so we need to call the .unwrap method to get the actual user object out of it. If the unwrapping fails because of failed authentication, we simply return an HTTP status code of 401 unauthorized.

You can find the source code for the project up to this point, here.

Stay Tuned!

Next tutorial: Token Authentication in Vapor 3! It will use this project as a starting point as well. Happy coding!



Enjoy this article? Consider supporting VaporForums by following us on Twitter! Get the latest Vapor articles, tutorials, and news.