Vapor 3 Series II — Authentication

ShengHua Wu
Swift2Go
Published in
6 min readAug 9, 2018
“Two books on a desk near a MacBook with lines of code on its screen” by Émile Perron on Unsplash

In the previous article, we finished a simple RESTful API server with Vapor 3. More specifically, we created our Usermodel to store data into an in-memory SQLite database and implemented our UsersController to handle interactions from a client. Although our server has many great features already, it also has one problem: anyone can create new users and delete them. In other words, there is no authentication on the endpoints to ensure that only known users can manipulate the database. In this article, I am going to demonstrate how to store passwords and authenticated users, and how to protect our endpoints with HTTP basic and bearer token authentications.

Please notice that this article will base on the previous implementation.

User Password

Generally speaking, authentication is the process of verifying who someone is, and one common way to authenticate users is using the username and password. Open our User.swift file and add the following property below var username: String.

Next, we should replace the initializer to store the new property with the following.

Since our User model already conforms Content protocol, we don't have to make any additional changes to create users with passwords.

However, instead of storing passwords in plain text, we should always store passwords in a secure way. Fortunately, Vapor provides BCrypt to hash passwords, and we can take the advantage of this to secure passwords. Switch to UsersController.swift file, and append the following line below import Vapor.

This import statement allows us to use BCrypt, and we can use it within createHandler and updateHandler methods. Let's replace these two methods with the following implementation.

These two new methods will hash the user’s password before saving it into the database.

At this point, if we try to create one user and retrieve afterward with Postman, we can see that the response contains the password hash. However, we should protect password hashes and never return them in responses. In order to achieve this, go back to User model and write the following lines below User's new initializer.

This creates an inner class to represent a public view of User. Next, add the following line under extension User: Content {}.

This line allows us to return this public view in responses. Moreover, let’s write some helper methods for User and Future to keep our UsersController concise. Please append the following extensions at the bottom of our Usermodel.

These two new methods help us to change our router handlers to return the public view. Finally, within our UsersController, change our handler methods to return the public view.

Now, any calls to our endpoints to retrieve a user won’t return a password hash.

Basic Authentication

Vapor already has a package to help with handling many types of authentication, including HTTP basic authentication. Open Package.swift and replace .package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0") with the following lines.

This adds the authentication package as a dependency to our project. Furthermore, change the dependencies array for the App target to the following.

This adds the Authentication module as a dependency to the App target. Then, please regenerate the Xcode project to install the new dependency with vapor xcode -y. Before modifying our User model, we have to change configure.swift file to adopt the new dependency. Go to configure.swift file and append the following line below import Vapor.

Additionally, add the following line under try services.register(FluentSQLiteProvider()) as well.

This line registers the necessary service with our application to make sure authentication works.

Let’s start to use the new dependency in our User model. First of all, append the following line below import FluentSQLite.

After that, we are able to adopt HTTP basic authentication with the Authentication module. Secondly, please add the following lines at the bottom of our User model.

This extension tells Vapor which property of our User model the username is and which one the password is. Since HTTP basic authentication uses the username and password to identify users, we should prevent multiple users from having the same username. Let's replace extension User: Migration {} with the following lines.

This custom migration will add all the columns to the User table using User's properties, and more importantly add a unique index to username on User.

Next, switch to our UsersController and add the following lines at the bottom of boot(router:) method.

Here we use two middlewares to ensure that requests of creating a user contain a valid authorization, otherwise an error will be thrown. Moreover, inside boot(router:) method, please remove the following line as well.

At this point, if we run the application and try to create a new user with Postman, we are going to receive a 401 Unauthorized error response. Since we are using an in-memory database, there is no existing user in the database every time we run the application. As a result, the authentication causes it's impossible for us to create a new user. However, one way to solve this is to seed the database and create a user when the application boots up, and Vapor's Migrationprotocol give us a perfect place to achieve it. Let's go back to our User model and append the following lines at the bottom of the file.

Obviously, for production neither should we use password as the password for our admin user, nor hardcode the password. One possible solution is to read the password from an environment variable. Last but not least, inside configure.swift please append the following line under migrations.add(model: User.self, database: .sqlite).

This line adds our AdminUser to the list of migrations so our application executes the migration at the next launch. Now, we are able to fulfill HTTP basic authentication with the username and password of our AdminUser on Postman.

Token Authentication

At this stage, only authenticated users can create users, but all other endpoints are still unprotected. Besides, asking a user to give credentials with each request is impractical, and we don’t want to store a user’s password in our application. Instead, we should provide an endpoint for users to log in, and we can replace their credentials with a token after they log in.

Let’s start with our Token class. As usual, open Terminal to create a new file and regenerate Xcode project as the followings.

Next, please write the following code into our new Token.swift file.

Basically, we define our Token model that contains the token string and the token owner's ID, and a helper method to generate a token for a login user. Then, Switch to configure.swift and add the following line before services.register(migrations).

This line adds our Token model to the list of migrations so our application executes the migration at the next launch. Open our UsersController to write the new login method below deleteHandler method.

Since we are going to protect this method with HTTP basic authentication, we can get the authenticated user from the request, and then create a token for the user. After this, inside boot(router:) method, please add the following line under basicProtected.post(use: createHandler).

Now, we can run our application and create a token for our admin user with Postman.

Bearer token authentication is a mechanism for sending a token to authenticated requests, and it uses Authorizationheader as well. Please go back to our Token model and append the following code at the bottom of the file.

This extension tells Vapor what type the user is, which property the token is, and which one the user ID is. Then, switch to our User model and add the following extension.

This extension tells Vapor what type a token is. The final step is to modify boot(router:) method within our UsersController. Let's replace the entire boot(router:) method with the following code.

We use basicAuthMiddleware and guardAuthMiddleware to protect the login endpoint, and on the other hand, we use tokenAuthMiddleware and guardAuthMiddleware to protect our CRUD endpoints. Now, all of our endpoints are protected, and our application only accepts requests from authenticated users.

Conclusion

Here is the entire project.

Let’s recap this long journey of authentication. First of all, we add a new password property of our User model, and only store the hashed password into the database. Besides, we also prevent our application from returning the hashed passwords. Secondly, we install Vapor's authentication package and protect the endpoint of creating a user with HTTP basic authentication. Furthermore, we also seed the database with an admin user. Finally, we create our Token model, implement the login endpoint, and protect the CRUD endpoints with bearer token authentication. Although it's a simple server, we still can see that Vapor actually provides very good authentication capabilities to add authentication to our endpoints.

--

--

ShengHua Wu
Swift2Go

I’m an enthusiast of iOS development. Enjoy learning new things.