Middleware Tutorial for Server-Side Swift Using Vapor 4: Getting Started
In this tutorial, you’ll learn how to create middleware — a module that sits between an app and the browser and removes some of the work load from your web app. By Walter Tyree.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Middleware Tutorial for Server-Side Swift Using Vapor 4: Getting Started
20 mins
API Key for Authorization
For your first piece of middleware, you’ll add a way to protect the administrative routes.
Authorization vs. Authentication
A common method of authorization for APIs is an API key. Generally, you request a secret key from an API provider after providing whatever contact or billing information they require. They’ll send you the key in an email, and then every request you make will need to include that API key in the header.
Middleware can determine if the key, and therefore the request, is valid. This way, the application doesn’t need to concern itself with verifying requests. Authentication is how someone verifies that you are who you say you are, and the API vendor went through authentication before sending you your key. Once you have the API key or some other credential, you go through the process of authorization for every request you make. Authorization determines whether the server should fill a request.
Start by finding Sources/App/Middleware/APIKeyCheck.swift
and opening it. All middleware needs to implement func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response>
. This one does that, but nothing more.
Note that there can be any amount of middleware in a responder chain, each of which passes along to the next after performing whatever tasks. Eventually, your app will be the recipient of the request.
Find the line // TODO: Add API Key Check
and replace it with the following:
// 1
guard let apiKey: String = request.headers["x-api-key"].first,
// 2
let storedKey = Environment.get("API_KEY"),
apiKey == storedKey else {
// 3
return request.eventLoop.future(error: Abort(.unauthorized))
}
Here’s what this code does:
- Searches the
request
for the header containing the API key. - Loads an API key from the server’s environment.
- Immediately returns with an
unauthorized
error if the stored key and the header don’t match.
Environment
, which reads from a hidden .env file. When you deploy your app on a service like Heroku or Azure, they’ll have other ways to provide secrets to your app. The documentation for the Vapor Environment can provide more information and help you create a more robust .env file.
All the code for this middleware is in a guard
statement. If a valid API key is present, there’s nothing to do and the request is passed along.
Protecting a Group
During the tour of the project, you found where to add middleware in configue.swift. You could add your new middleware to that part of the app by inserting the following after the line for FileMiddleware
:
app.middleware.use(APIKeyCheck())
However, this would check for an API key on every request, and you only want to protect the administrative routes.
So, open Sources/App/AdminRoutes.swift and find // TODO: Protect Routes
. Modify the line that creates the group so that it looks like the following:
let protected = routes.grouped(APIKeyCheck())
protected.group("admin") { admin in
This creates a new grouping called protected, and then uses it to group all the routes. Note the use of the operator grouped
instead of the operator group
. Using grouped
assigns a variable you can use for composing more complicated routing, where group
has a closure for its routes.
Now the API checking middleware only applies to the routes in this group instead of all of the routes. Build and run.
The standard GET
command will still work, but any commands to the administrative routes won’t. If you’re using Postman, enable the API key by turning it on in the header section. If you’re using curl, modify the commands to add a new -H
parameter like what’s shown in the example below:
curl -i -X POST -H "Content-Type: application/json" \ -H "x-api-key: 4310f636-43ec-41ba-aa34-b3e3c378d687" \ -d '{"shortUrl": "rw", "longUrl": "http://www.raywenderlich.com"}' \ http://localhost:8080/admin/shorten
Add the API key to commands and confirm everything is working. If you change the API key in the .env file and rebuild the app, you’ll have to change the API key in all your requests.
Bringing Middleware From Vapor to Your Projects
In addition to any middleware you write, Vapor has middleware you can bring into your projects. Unless you explicitly override them, the middleware for logging and error are always included and will be the first middleware in the chain. If you ever want to override them, before the first app.middleware.use()
statement in your configure.swift, add app.middleware = init()
to clear the defaults.
The files middleware lets you provide a directory that holds static assets, like CSS files, images and JavaScript files. Any web requests for these assets are returned directly without burdening your routes.swift
file with the extra work.
By default, the files middleware uses the Public folder in a Vapor project. If you open the configure.swift
of this project, you can see the file middleware is enabled. If you comment out the middleware and run the app, you’ll see some errors in the logs when you use the app. This is because when browsers make GET
requests, they ask for favicon
files, which are the little files that show up in a browser’s tab bar. Without the file middleware enabled, a Vapor app doesn’t have any icons to return.
lnkshrtnr uses API keys for authorization on single requests. When you want to have usernames and passwords and session management, Vapor provides the Sessions middleware to help you manage session state. There’s an excellent tutorial if you want more information on how to implement that.
Vapor also provides CORS middleware. CORS is necessary when you have different parts or resources for your app hosted at different domains. This will most commonly happen when using web fonts or a cloud hosting provider with many different microservices. You’ll know it’s time to implement CORS when you see errors about “Cross-Origin Request Blocked” or “Same Origin Policy” in the logs. An important thing to pay attention to with CORS is where it sits in the list of middleware.
Testing Requests
Now that the API key is protecting them, your tests for the admin routes are all failing. Run the tests now and you’ll see four failed tests. In Xcode, run tests using Command-U or Product > Test. When using Terminal, type swift test
.
The reason tests fail is because the API key isn’t in the test headers. Vapor includes some extensions specifically for helping you modify headers when sending test requests, so you can read the documentation to get a complete understanding.
First, open Tests/AppTests/AdminTests.swift and create a helper method at the bottom of the class that will add the header value to the admin requests:
private func addAuthHeader(request: inout XCTHTTPRequest) {
request.headers.add(name: "x-api-key", value: apiKey)
}
Notice the use of the inout
keyword. inout
makes the request mutable as a parameter so you don’t have to copy it to a variable in the helper method.
To add your API key to a test request, find testRemoveLink()
. Change the test so that it looks like the code below. Notice that you’re adding beforeRequest
and afterResponse
in the middle of the current code:
// 1
try testApp.test(.DELETE, "admin/omw3", beforeRequest: {request in
// 2
addAuthHeader(&request)
},
// 3
afterResponse: {res in
XCTAssert(res.status == HTTPResponseStatus.noContent,
"Expected a status of NO CONTENT but got \(res.status)")
//4
})
Here’s what this code does:
Adds the parameter of beforeRequest
to let you modify request
before sending it to the server.
Adds the apiKey
value to the correct header.
Assigns the trailing closure to the afterResponse
parameter.
Adds a close parenthesis now that you no longer have a trailing closure.
-
Adds the parameter of
beforeRequest
to let you modifyrequest
before sending it to the server. -
Adds the
apiKey
value to the correct header. -
Assigns the trailing closure to the
afterResponse
parameter. -
Adds a close parenthesis now that you no longer have a trailing closure.
Now, run the tests again and this one should pass. See if you can figure out how to make the other three tests pass. If you get stuck, look at the tests in the Final project of the download materials.