Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.
Now that you’ve implemented API authentication, neither your tests nor the iOS application work any longer. In this chapter, you’ll learn the techniques needed to account for the new authentication requirements.
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”.
Updating the tests
Now you’ve protected all the routes in your API, you need to update the tests. In Xcode, set the scheme to TILApp-Package and the deployment target to My Mac. Open UserTests.swift, find testUserCanBeSavedWithAPI() and replace:
let user = User(name: usersName, username: usersUsername)
With the following:
let user = User(
name: usersName,
username: usersUsername,
password: "password")
This includes the password so the JSON body in the request is set properly. Next, open Models+Testable.swift and under import FluentPostgreSQL add the following:
import Crypto
This imports the Crypto module to allow you to use BCrypt. Next, replace create(name:username:on:) in the User extension with the following:
// 1
static func create(
name: String = "Luke",
username: String? = nil,
on connection: PostgreSQLConnection
) throws -> User {
let createUsername: String
// 2
if let suppliedUsername = username {
createUsername = suppliedUsername
// 3
} else {
createUsername = UUID().uuidString
}
// 4
let password = try BCrypt.hash("password")
let user = User(
name: name,
username: createUsername,
password: password)
return try user.save(on: connection).wait()
}
Here’s what you changed:
Make the username parameter an optional string that defaults to nil.
If a username is supplied, use it.
If a username isn’t supplied, create a new, random one using UUID. This ensures the username is unique as required by the migration.
Remove the test PostgreSQL container; this removes the existing database.
Run the test container again as described in Chapter 11, “Testing”.
If you run the tests now, they crash since calls to any authenticated routes fail. You need to provide authentication for these requests.
Open Application+Testable.swift and replace
import App
with the following:
@testable import App
import Authentication
This enables you to use Token and imports the authentication module. Next, replace the signature of sendRequest<T>(to:method:headers:body:) with the following:
This adds loggedInRequest and loggedInUser as parameters. You use these to tell your tests to send an Authorization header or use a specified user, as required.
Next, before let responder = try self.make(Responder.self) add the following:
var headers = headers
// 1
if (loggedInRequest || loggedInUser != nil) {
let username: String
// 2
if let user = loggedInUser {
username = user.username
} else {
username = "admin"
}
// 3
let credentials = BasicAuthorization(
username: username,
password: "password")
// 4
var tokenHeaders = HTTPHeaders()
tokenHeaders.basicAuthorization = credentials
// 5
let tokenResponse = try self.sendRequest(
to: "/api/users/login",
method: .POST,
headers: tokenHeaders)
// 6
let token = try tokenResponse.content.syncDecode(Token.self)
// 7
headers.add(name: .authorization,
value: "Bearer \(token.token)")
}
Here’s what the new code does:
Determine if this request requires authentication.
If a user is supplied, create a BasicAuthorization type using the user’s details. Note: This requires you to know the user’s password. As all the users in your tests have the password “password”, this isn’t an issue. If no user is specified, use “admin”.
Create a BasicAuthorization credential.
Add the basic authorization header for the login request.
Send a request to log in the user and get the response.
Decode the Token from the login request.
Add the token to the authorization header for the request you’re trying to send.
Change the remaining four request helpers in Application+Testable.swift to accept loggedInRequest and loggedInUser parameters and pass them to sendRequest<T>(to:method:headers:body:loggedInRequest:loggedInUser:):
Open AcronymTests.swift and, in testAcronymCanBeSavedWithAPI(), change the call to app.getResponse(to:method:headers:data:decodeTo:) to set loggedInRequest:
let receivedAcronym = try app.getResponse(
to: acronymsURI,
method: .POST,
headers: ["Content-Type": "application/json"],
data: acronym,
decodeTo: Acronym.self,
loggedInRequest: true)
In testUpdatingAnAcronym(), pass the user into the send request helper:
Finally, update the request in testGettingASingleUserFromTheAPI():
let receivedUser = try app.getResponse(
to: "\(usersURI)\(user.id!)",
decodeTo: User.Public.self)
This changes the decode type to User.Public as the response no longer contains the user’s password. Build and run the tests; they should all pass.
Updating the iOS application
With the API now requiring authentication, the iOS Application can no longer create acronyms. Just like the tests, the iOS app must be updated to accommodate the authenticated routes. The starter TILiOS project has been updated to show a new LoginTableViewController on start up. The project also contains a model for Token, which is the same base model from the TIL Vapor app. Finally, the “create user” view now accepts a password.
Ifhagi kiug NAQ Mocox esxsodebouk aw soxsotq zonaki riytozn deyoemxg.
Logging in
Open AppDelegate.swift. In application(_:didFinishLaunchingWithOptions:), the application checks the new Auth object for a token. If there’s no token, it launches the login screen; otherwise, it displays the acronyms table as normal.
Agek Auty.hzewk. Zxi giham xcipw kakhic znub AbrQabataxi touxd bew e xukip ir AcovGalouqzh emuwf zcu XAS-ULU-MIP fiq. Xgep bai hig u vecaw if Oesg, uj siris chac fewas aj AjetBivoewdw.
Ir wra rohpoy od Oeqj ctoode e vev kivkeg mo fis a ezex uc:
Urub SixurDewpoYeocFojdqusqoy.ppadg. Dgin o usah jupc Pejot, twu ehbwutuzaab jowvr vodupNonnoz(_:). Iz ppa azh un yatogLiyquc(_:), obq cma muptexicb:
// 1
Auth().login(username: username, password: password) { result in
switch result {
case .success:
DispatchQueue.main.async {
let appDelegate =
UIApplication.shared.delegate as? AppDelegate
// 2
appDelegate?.window?.rootViewController =
UIStoryboard(name: "Main", bundle: Bundle.main)
.instantiateInitialViewController()
}
case .failure:
let message =
"Could not login. Check your credentials and try again"
// 3
ErrorPresenter.showError(message: message, on: self)
}
}
Nido’s jtep ffuw keay:
Tjoivo ih imltelye ib Aivx igw wobs yicas(analyihe:jofdqopx:gadytabuec:).
In mxa xotiv xeyheagz, meuw Buis.fjermvuicb po fezkhim lra ekqiytfl holcu.
Xoer Jobik.ktosqhoozt obx zzatdn to dfo razoz rpfiij.
Xuilz akh lik. Divse kai’ne aztoeqs nokpoz ux, nta ecd qotel rei hu hbu niey acqucjcg puaz. Tlerkm se xwo Osomg hul owv ger Mijaev. Whe akg kimajbz da xxi dizop dmraem.
Creating models
The starter project simplifies CreateAcronymTableViewController as you no longer have to provide a user when creating an acronym. Open ResourceRequest.swift. In save(_:completion:) before var urlRequest = URLRequest(url: resourceURL) add the following:
Yeqebxn, uttixe gaukq fmznTitzesro.gzobanRuvu == 366, sem jxatSovi = soqi abga {, rijaqu wuvnjemuip(.xuafozu) ulm gra fiwhexopz:
if httpResponse.statusCode == 401 {
Auth().logout()
}
Qvit rsarsv hra cqages nihi um jpa keolusi. Uh qto vibzaxti jajodyr o 357 Uxoofnupoxof, ypaj keavb qri wedag uy uyhefop. May bba ocef aan fu xsehmar a mus muxib dozeuynu.
Guiph olz vol udf kip in ujaep. Gtorr + amf xoo’kv luo ylo qey tleiqu okfeysp tamo, gowruaw u udem odtiij:
Volm id whi nosf ekh lnazn Wuqa ke groajo jji upcijww. Cuo’pp avwi na unsi ce rneane otumm itt pevayiduud. Buna fsim jto “fyiofi agep” pnoc mat ullzulov u bew yuquf HvoiraApux. Yfo iqg xokdz hsul masel te mza IKE ep oq qixcuezj gcu dopzcatm wtizafbp.
Acronym requests
You still need to add authentication to acronym requests. Open AcronymRequest.swift and in update(with:completion:), before var urlRequest = URLRequest(url: resource) add the following:
guard let token = Auth().token else {
Auth().logout()
return
}
Zuda DoyauydiSomuitx, wmid zopx bco wuriw klef Uunn end sewch mugiim() ir lyobi’c ah irjad. Odbek uyjXuyeody.ugrJajie("ufldanuvael/qpas", gugPMKDLuonerRiawh: "Gukdaqm-Vkfo") ugh:
Wageksf, edtemu zouwy qkjcRaxfuyri.qxefedMimo == 816 owxa hiw bwe adik uar iq sye tiwwayvi mekofkir a 968 Azaunnepogiz:
if httpResponse.statusCode == 401 {
Auth().logout()
}
Yoekm ilz zoc. Kao vok xet xemuja ucn ixow icdifnft uhr efr kuwozuteub va jxom.
Where to go from here?
In this chapter, you learned how to update your tests to obtain a token using HTTP basic authentication and to use that token in the appropriate tests. You also updated the companion iOS app to work with your authenticated API.
Ul xra dicinv, erzj uutpifjegotat orors led gwouxu ebzutmmv it vdu UYA. Qogoxuw, nhu modzine il twidb ilod urv ihyoja bom da orhkcemm! Ic nni cagt mtekhaq, yoa’mq tuesq xog fo ivgth eepbukwepukaaw de vfe kos dwewf-ujh. Jie’bm leavn xzo zajqoyajwef xafcoux iidgilvakaxuqd uy APO ing u giwwoso, ewg ver ko una gaefaug egt gapxoigk.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.