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.
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.
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 as well, first you'll need to create the database. Simply run the following in terminal:
$ createdb usernamepasswordauth
And you're done!
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
.
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.
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.
Next tutorial: Token Authentication in Vapor 3! It will use this project as a starting point as well. Happy coding!