Chapters

Hide chapters

Server-Side Swift with Vapor

Third Edition - Early Acess 1 · iOS 13 · Swift 5.2 - Vapor 4 Framework · Xcode 11.4

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section I: Creating a Simple Web API

Section 1: 13 chapters
Show chapters Hide chapters

18. API Authentication, Part 1
Written by Tim Condon

Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.

The TILApp you’ve built so far has a ton of great features, but it also has one small problem: Anyone can create new users, categories or acronyms. There’s no authentication on the API or the website to ensure only known users can change what’s in the database. In this chapter, you’ll learn how to protect your API with authentication. You’ll learn how to implement both HTTP basic authentication and token authentication in your API. You’ll also learn best-practices for storing passwords and authenticating users.

Note: You must have PostgreSQL set up and configured in your project. If you still need to do this, follow the steps in Chapter 6, “Configuring a Database”.

Passwords

Authentication is the process of verifying who someone is. This is different from authorization, which is verifying that a user has permission to perform a particular action. You commonly authenticate users with a username and password combination and TILApp will be no different.

Open the Vapor application in Xcode and open User.swift. Add the following property to User below var username: String:

var password: String

This property stores the user’s password. Next, to account for the new property, replace the initializer with the following:

init(name: String, username: String, password: String) {
  self.name = name
  self.username = username
  self.password = password
}

Password storage

Thanks to Codable, you don’t have to make any additional changes to create users with passwords. The existing UserController now automatically expects to find the password property in the incoming JSON. However, without any changes, you’ll be saving the user’s password in plain text.

You should never store passwords in plain text. You should always store passwords in a secure fashion. BCrypt is an industry standard for hashing passwords and Vapor has it built in.

BCrypt is a one-way hashing algorithm. This means that you can turn a password into a hash, but can’t convert a hash back into a password. Since BCrypt is designed to be slow, if someone steals a password hash, it takes a long time to brute-force the password. BCrypt hashes a salt with the password. A salt is a unique, random value to help defend against common attacks. BCrypt also provides a mechanism to verify a password using the password and a hash.

Open UsersController.swift and add the following under import Vapor:

import Crypto

This brings in the Crypto module so you can use BCrypt. Next, in createHandler(_:user:) add the following before return user.save(on:req):

user.password = try BCrypt.hash(user.password)

This hashes the user’s password before saving it in the database.

Making usernames unique

In the coming sections of this chapter, you’ll be using the username and password to uniquely identify users. At the moment, there’s nothing to prevent multiple users from having the same username.

Open User.swift and replace:

extension User: Migration {}

With the following:

extension User: Migration {
  static func prepare(on connection: PostgreSQLConnection)
    -> Future<Void> {
    // 1
    return Database.create(self, on: connection) { builder in
      // 2
      try addProperties(to: builder)
      // 3
      builder.unique(on: \.username)
    }
  }
}

This implements a custom migration, much like adding foreign key constraints in Chapter 9, “Parent Child Relationships”. Here’s what the custom migration does:

  1. Create the User table.
  2. Add all the columns to the User table using User’s properties.
  3. Add a unique index to username on User.

After the application has run the custom migration, any attempts to create duplicate usernames result in an error.

Returning users from the API

Since the model has changed you need to revert your database so Vapor can add the new column to the table. Option-Click the Run button in Xcode — or press Option-Command-R — to open the scheme editor. On the Arguments tab, click + in the Arguments Passed On Launch section, and enter:

revert --all --yes

You’ll see the following:

Click Run and watch the reversion run in the Xcode console. Option-Click the Run button once more, clear the checkbox next to the arguments you entered, then click Run.

Note: Entering vapor run revert –all –yes in Terminal is another way to revert your local database.

Launch RESTed, create a new request and configure it as follows:

Add three parameters with names and values:

  • name: your name
  • username: a username of your choice
  • password: a password of your choice

Click Send Request. Your application creates the requested user but the response returns the password hash:

This isn’t good! You should protect password hashes and never return them in responses. In fact, any user returned by the API includes the password hash, including listing all the users! This happens because you’re returning User in all your routes. You should instead return a “public view” of User.

In Xcode, open User.swift and add the following below the User initializer:

final class Public: Codable {
  var id: UUID?
  var name: String
  var username: String

  init(id: UUID?, name: String, username: String) {
    self.id = id
    self.name = name
    self.username = username
  }
}

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

extension User.Public: Content {}

This conforms User.Public to Content, allowing you to return the public view in responses. Next, add the following at the bottom of User.swift:

extension User {
  // 1
  func convertToPublic() -> User.Public {
    // 2
    return User.Public(id: id, name: name, username: username)
  }
}

Here’s what the new method does:

  1. Define a method on User that returns User.Public.
  2. Create a public version of the current object.

Finally, add the following below the new extension:

// 1
extension Future where T: User {
  // 2
  func convertToPublic() -> Future<User.Public> {
    // 3
    return self.map(to: User.Public.self) { user in
      // 4
      return user.convertToPublic()
    }
  }
}

Here’s what this does:

  1. Define an extension for Future<User>.
  2. Define a new method that returns a Future<User.Public>.
  3. Unwrap the user contained in self.
  4. Convert the User object to User.Public.

This extension allows you to call convertToPublic() on Future<User> which helps tidy up your code and reduce nesting. These new methods allow you to change your route handlers to return public users.

First, open UsersController.swift and change the return type of createHandler(_:user:):

func createHandler(_ req: Request, user: User) throws
  -> Future<User.Public> {

Next, change your return to return a public user instead:

return user.save(on: req).convertToPublic()

This uses the extension for Future<User>. As a result, you don’t need to unwrap the result of the save yourself, making your code much cleaner!

Build and run, then create a new user in RESTed. You’ll notice the user’s password hash is no longer returned:

Now, you must update the rest of the routes that return User.

First, in UsersController.swift change the signature of getAllHandler(_:) to the following:

func getAllHandler(_ req: Request) throws
  -> Future<[User.Public]> {

Next, change the body of getAllHandler(_:) to the following:

return User.query(on: req).decode(data: User.Public.self).all()

Instead of converting the User models to User.Public, this code decodes the data returned from the query into User.Public. This makes your code far simpler and more efficient. Next, change the signature of getHandler(_:) to return a public user:

func getHandler(_ req: Request) throws -> Future<User.Public> {

Next, change the body to return a public user:

return try req.parameters.next(User.self).convertToPublic()

Finally, open AcronymsController.swift and replace getUserHandler(_:) so it returns a public user:

// 1
func getUserHandler(_ req: Request) throws
  -> Future<User.Public> {
  // 2
  return try req.parameters.next(Acronym.self)
    .flatMap(to: User.Public.self) { acronym in
      // 3
      acronym.user.get(on: req).convertToPublic()
  }
}

Here’s what changed:

  1. Change the return type of the method to Future<User.Public>.
  2. Change the parameter of flatMap(to:) to User.Public.self.
  3. Call convertToPublic() on the acronym’s user to return a public user.

Now, any calls to your API to retrieve a user won’t return a password hash.

Basic authentication

HTTP basic authentication is a standardized method of sending credentials via HTTP and is defined by RFC 7617. You typically include the credentials in an HTTP request’s Authorization header.

To generate the token for this header, you combine the username and password, then base64-encode the result.

For example, for the username timc and password password the combined credential string is:

timc:password

You then base64-encode this which gives you:

dGltYzpwYXNzd29yZA==

The full header becomes:

Authorization: Basic dGltYzpwYXNzd29yZA==

Vapor 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/leaf.git", from: "3.0.0") with the following:

.package(
  url: "https://github.com/vapor/leaf.git",
  from: "3.0.0"),
.package(
  url: "https://github.com/vapor/auth.git",
  from: "2.0.0")

This adds the authentication package as a dependency to your project. Change the dependencies array for the App target to the following:

dependencies: ["FluentPostgreSQL",
               "Vapor",
               "Leaf",
               "Authentication"]

This adds the Authentication module as a dependency to the App target. In Terminal, regenerate the Xcode project to bring in the new dependency:

vapor xcode -y

Open User.swift and below import FluentPostgreSQL add the following:

import Authentication

This allows you to use the HTTP Basic helpers in the authentication module. At the bottom of the file, add the following:

// 1
extension User: BasicAuthenticatable {
  // 2
  static let usernameKey: UsernameKey = \User.username
  // 3
  static let passwordKey: PasswordKey = \User.password
}

Here’s what this does:

  1. Conform User to BasicAuthenticatable.
  2. Tell Vapor which key path of User is the username.
  3. Tell Vapor which key path of User is the password.

Open AcronymsController.swift and, under import Fluent, add the following:

import Authentication

Next, add the following at the bottom of boot(router:):

// 1
let basicAuthMiddleware =
  User.basicAuthMiddleware(using: BCryptDigest())
// 2
let guardAuthMiddleware = User.guardAuthMiddleware()
// 3
let protected = acronymsRoutes.grouped(
  basicAuthMiddleware,
  guardAuthMiddleware)
// 4
protected.post(Acronym.self, use: createHandler)

Here’s what this does:

  1. Instantiate a basic authentication middleware which uses BCryptDigest to verify passwords. Since User conforms to BasicAuthenticatable, this is available as a static function on the model.
  2. Create an instance of GuardAuthenticationMiddleware which ensures that requests contain valid authorization.
  3. Create a middleware group which uses basicAuthMiddleware and guardAuthMiddleware.
  4. Connect the “create acronym” path to createHandler(_:acronym:) through this middleware group.

Middleware allows you to intercept requests and responses in your application. In this example, basicAuthMiddleware intercepts the request and authenticates the user supplied. You can chain middleware together. In the above example, basicAuthMiddleware authenticates the user. Then guardAuthMiddleware ensures the request contains an authenticated user. If there’s no authenticated user, guardAuthMiddleware throws an error. You can learn more about middleware in Chapter 25, “Middleware”.

This ensures only requests authenticated using HTTP basic authentication can create acronyms.

Next, delete the following to remove the unauthenticated route:

acronymsRoutes.post(Acronym.self, use: createHandler)

Next, open configure.swift and under import Leaf, add the following to import the authentication module:

import Authentication

Next, add the following under try services.register(LeafProvider()):

try services.register(AuthenticationProvider())

This registers the necessary services with your application to ensure authentication works. Build and run, then launch RESTed. Create a new request and configure it as follows:

Add three parameters with names and values:

  • short: OMG
  • long: Oh My God
  • userID: The ID of the user created earlier

Click Send Request and you’ll receive a 401 Unauthorized error response. You should see the following:

In RESTed click Authorization and enter the username and password for the user created earlier. Check Present Before Authentication Challenge and click OK:

This sets the basic Authorization header as described above. Click Send Request again. This time the request succeeds:

Token authentication

Getting a token

At this stage, only authenticated users can create acronyms. However, all other “destructive” routes are still unprotected. Asking a user to enter credentials with each request is impractical. You also don’t want to store a user’s password anywhere in your application since you’d have to store it in plain text. Instead, you’ll allow users to log in to your API. When they log in, you exchange their credentials for a token the client can save.

In Terminal, type the following:

# 1
touch Sources/App/Models/Token.swift
# 2
vapor xcode -y

Here’s what this does:

  1. Create a new file for the Token model.
  2. Regenerate the Xcode project to pick up the new file.

When the project regenerates, open Token.swift and add the following:

import Foundation
import Vapor
import FluentPostgreSQL
import Authentication

final class Token: Codable {
  var id: UUID?
  var token: String
  var userID: User.ID

  init(token: String, userID: User.ID) {
    self.token = token
    self.userID = userID
  }
}

extension Token: PostgreSQLUUIDModel {}

extension Token: Migration {
  static func prepare(on connection: PostgreSQLConnection)
    -> Future<Void> {
      return Database.create(self, on: connection) { builder in
        try addProperties(to: builder)
        builder.reference(from: \.userID, to: \User.id)
      }
  }
}

extension Token: Content {}

This defines a model for Token that contains the following properties:

  • id: the ID of the model.
  • token: the token string provided to clients.
  • userID: the token owner’s user ID. The migration also creates a foreign key constraint with User.

In configure.swift, add the following before services.register(migrations):

migrations.add(model: Token.self, database: .psql)

This adds Token to the list of migrations so Vapor creates the table when the application next starts. When a user logs in, the application creates a token for that user.

Open Token.swift and add the following at the bottom of the file:

extension Token {
  // 1
  static func generate(for user: User) throws -> Token {
    // 2
    let random = try CryptoRandom().generateData(count: 16)
    // 3
    return try Token(
      token: random.base64EncodedString(),
      userID: user.requireID())
  }
}

Here’s what this extension does:

  1. Define a static function to generate a token for a user.
  2. Generate 16 random bytes to act as the token.
  3. Create a Token using the base64-encoded representation of the random bytes and the user’s ID.

Open UsersController.swift and add the following under getAcronymsHandler(_:):

// 1
func loginHandler(_ req: Request) throws -> Future<Token> {
  // 2
  let user = try req.requireAuthenticated(User.self)
  // 3
  let token = try Token.generate(for: user)
  // 4
  return token.save(on: req)
}

Here’s what this does:

  1. Define a route handler for logging a user in.
  2. Get the authenticated user from the request. You’ll protect this route with the HTTP basic authentication middleware. This saves the user’s identity in the request’s authentication cache, allowing you to retrieve the user object later. requireAuthenticated(_:) throws an authentication error if there’s no authenticated user.
  3. Create a token for the user.
  4. Save and return the token.

At the bottom of boot(router:) add the following:

// 1
let basicAuthMiddleware =
  User.basicAuthMiddleware(using: BCryptDigest())
let basicAuthGroup = usersRoute.grouped(basicAuthMiddleware)
// 2
basicAuthGroup.post("login", use: loginHandler)

Here’s what this does:

  1. Create a protected route group using HTTP basic authentication, as you did for creating an acronym. This doesn’t use GuardAuthenticationMiddleware since requireAuthenticated(_:) throws the correct error if a user isn’t authenticated.
  2. Connect /api/users/login to loginHandler(_:) through the protected group.

Build and run, then head back to RESTed.

Ensure you’ve configured the HTTP basic authentication and set the URL to http://localhost:8080/api/users/login.

Click Send Request and you’ll receive a token back:

Using a token

Open Token.swift and add the following at the end of the file:

// 1
extension Token: Authentication.Token {
  // 2
  static let userIDKey: UserIDKey = \Token.userID
  // 3
  typealias UserType = User
}

// 4
extension Token: BearerAuthenticatable {
  // 5
  static let tokenKey: TokenKey = \Token.token
}

Here’s what this does:

  1. Conform Token to Authentication’s Token protocol.
  2. Define the user ID key on Token.
  3. Tell Vapor what type the user is.
  4. Conform Token to BearerAuthenticatable. This allows you to use Token with bearer authentication.
  5. Tell Vapor the key path to the token key, in this case, Token’s token string.

Bearer authentication is a mechanism for sending a token to authenticate requests. It uses the Authorization header, like HTTP basic authentication, but the header looks like Authorization: Bearer <TOKEN STRING>.

Open User.swift and add the following at the bottom of the file:

// 1
extension User: TokenAuthenticatable {
  // 2
  typealias TokenType = Token
}

Here’s what this does:

  1. Conform User to TokenAuthenticatable. This allows a token to authenticate a user.
  2. Tell Vapor what type a token is.

Currently when users create acronyms, they must send their ID in the request. However, because you’re requiring authentication, you now know which user sent each request. At the bottom of AcronymsController.swift, add the following:

struct AcronymCreateData: Content {
  let short: String
  let long: String
}

This defines the request data that a user now has to send to create an acronym.

Replace createHandler(_:acronym:) with the following:

// 1
func createHandler(
  _ req: Request,
  data: AcronymCreateData
) throws -> Future<Acronym> {
  // 2
  let user = try req.requireAuthenticated(User.self)
  // 3
  let acronym = try Acronym(
    short: data.short, 
    long: data.long,
    userID: user.requireID())
  // 4
  return acronym.save(on: req)
}

Here’s what the new function handler does:

  1. Define a route handler that accepts AcronymCreateData as the request body.
  2. Get the authenticated user from the request.
  3. Create a new Acronym using the data from the request and the authenticated user.
  4. Save and return the acronym.

In boot(router:), remove the code you used earlier to protect the “create an acronym” route and replace it with the following:

// 1
let tokenAuthMiddleware = User.tokenAuthMiddleware()
let guardAuthMiddleware = User.guardAuthMiddleware()
// 2
let tokenAuthGroup = acronymsRoutes.grouped(
  tokenAuthMiddleware,
  guardAuthMiddleware)
// 3
tokenAuthGroup.post(AcronymCreateData.self, use: createHandler)

Here’s what the new code does:

  1. Create a TokenAuthenticationMiddleware for User. This uses BearerAuthenticationMiddleware to extract the bearer token out of the request. The middleware then converts this token into a logged in user.
  2. Create a route group using tokenAuthMiddleware and guardAuthMiddleware to protect the route for creating an acronym with token authentication.
  3. Connect the “create acronym” path to createHandler(_:data:) through this middleware group using the new AcronymCreateData.

Build and run, then head back to RESTed. Copy the token string returned from the user login. Configure a request like so:

Add two parameters with names and values:

  • short: IKR
  • long: I Know Right

Create a new header field for Authorization with the value Bearer <TOKEN STRING>, using the token string you copied earlier. Remove the HTTP basic authentication credentials you used for logging in.

To do this, click Authorization, remove the username and password, and uncheck Present Before Authentication Challenge.

Click Send Request and you’ll see the created acronym returned:

In AcronymsController.swift in boot(router:) delete the following lines:

acronymsRoutes.put(Acronym.parameter, use: updateHandler)
acronymsRoutes.delete(Acronym.parameter, use: deleteHandler)
acronymsRoutes.post(
  Acronym.parameter,
  "categories",
  Category.parameter,
  use: addCategoriesHandler)
acronymsRoutes.delete(
  Acronym.parameter,
  "categories",
  Category.parameter,
  use: removeCategoriesHandler)

This is all of the original routes that are not get() routes. At the bottom of boot(router:) replace them with the following:

tokenAuthGroup.delete(Acronym.parameter, use: deleteHandler)
tokenAuthGroup.put(Acronym.parameter, use: updateHandler)
tokenAuthGroup.post(
  Acronym.parameter,
  "categories",
  Category.parameter,
  use: addCategoriesHandler)
tokenAuthGroup.delete(
  Acronym.parameter,
  "categories",
  Category.parameter,
  use: removeCategoriesHandler)

This ensures that only authenticated users can create, edit and delete acronyms, and add categories to acronyms. Unauthenticated users can still view details about acronyms.

Next, replace updateHandler(_:) with the following:

func updateHandler(_ req: Request) throws -> Future<Acronym> {
  // 1
  return try flatMap(
    to: Acronym.self,
    req.parameters.next(Acronym.self),
    req.content.decode(AcronymCreateData.self)
  ) { acronym, updateData in
      acronym.short = updateData.short
      acronym.long = updateData.long
      // 2
      let user = try req.requireAuthenticated(User.self)
      acronym.userID = try user.requireID()
      return acronym.save(on: req)
  }
}

Here’s what changed:

  1. Decode the request’s data to AcronymCreateData since request no longer contains the user’s ID in the post data.
  2. Get the authenticated user from the request and use that to update the acronym.

Now, open CategoriesController.swift and in boot(router:) delete categoriesRoute.post(Category.self, use: createHandler).

Replace it with the following at the end of the method:

let tokenAuthMiddleware = User.tokenAuthMiddleware()
let guardAuthMiddleware = User.guardAuthMiddleware()
let tokenAuthGroup = categoriesRoute.grouped(
  tokenAuthMiddleware,
  guardAuthMiddleware)
tokenAuthGroup.post(Category.self, use: createHandler)

This uses the token middleware to protect category creation, just like creating an acronym, ensuring only authenticated users can create categories. Finally, open UsersController.swift and delete usersRoute.post(User.self, use: createHandler). At the bottom of boot(router:), add the following:

let tokenAuthMiddleware = User.tokenAuthMiddleware()
let guardAuthMiddleware = User.guardAuthMiddleware()
let tokenAuthGroup = usersRoute.grouped(
  tokenAuthMiddleware,
  guardAuthMiddleware)
tokenAuthGroup.post(User.self, use: createHandler)

Again, using tokenAuthMiddleware and guardAuthMiddleware ensures only authenticated users can create other users. This prevents anyone from creating a user to send requests to the routes you’ve just protected!

Now all API routes that can perform “destructive” actions — that is create, edit or delete resources — are protected. For those actions, the application only accept requests from authenticated users.

Database seeding

At this point the API is secure, but now there’s another problem. When you deploy your application, or next revert the database, you won’t have any users in the database.

But, you can’t create a new user since that route requires authentication! One way to solve this is to seed the database and create a user when the application first boots up. In Vapor, you do this with a migration.

At the bottom of User.swift, add the following:

// 1
struct AdminUser: Migration {
  // 2
  typealias Database = PostgreSQLDatabase

  // 3
  static func prepare(on connection: PostgreSQLConnection)
    -> Future<Void> {
    // 4
    let password = try? BCrypt.hash("password")
    guard let hashedPassword = password else {
      fatalError("Failed to create admin user")
    }
    // 5
    let user = User(
      name: "Admin",
      username: "admin",
      password: hashedPassword)
    // 6
    return user.save(on: connection).transform(to: ())
  }

  // 7
  static func revert(on connection: PostgreSQLConnection)
    -> Future<Void> {
    return .done(on: connection)
  }
}

Here’s what this does:

  1. Define a new type that conforms to Migration.
  2. Define which database type this migration is for.
  3. Implement the required prepare(on:).
  4. Create a password hash and terminate with a fatal error if this fails.
  5. Create a new user with the name Admin, username admin and the hashed password.
  6. Save the user and transform the result to Void, the return type of prepare(on:).
  7. Implement the required revert(on:). .done(on:) returns a pre-completed Future<Void>.

Note: Obviously, in a production system, you shouldn’t use password as the password for your admin user! You also don’t want to hardcode the password in case it ends up in source control. You can either read an environment variable or generate a random password and print it out.

Open configure.swift and add the following before services.register(migrations):

migrations.add(migration: AdminUser.self, database: .psql)

This adds AdminUser to the list of migrations so the app executes the migration at the next app launch. You use add(migration:database:) instead of add(model:database:) since this isn’t a full model.

Build and run. Head to RESTed and try out all of your newly protected routes. You can even log in with the new admin user.

Where to go from here?

In this chapter, you learned about HTTP basic and bearer authentication. You saw how authentication middleware can simplify your code and do much of the heavy lifting for you. You saw how to modify your existing model to work with Vapor’s authentication capabilities. You glued it all together to add authentication to your API.

But, there’s much more to be done. Turn the page and get busy updating your test suite and your iOS app to work with the new authentication capabilities.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.