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
In the previous chapter, you updated the tests to ensure they compile. However, many of the tests won’t pass as you’ve protected all the routes in your API.
First, open Models+Testable.swift and, at the top of the file, add:
import Vapor
This allows the compiler to see the Bcrypt function used for password hashing. Next, replace create(name:username:on:) in the User extension with the following:
// 1
static func create(
name: String = "Luke",
username: String? = nil,
on database: Database
) 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)
try user.save(on: database).wait()
return user
}
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.
Add an extension to XCTApplicationTester, Vapor’s test wrapper around Application.
Define a log in method that takes User and returns Token.
Create a test POST request to /api/users/login — the log in URL — with empty values where needed.
Set the HTTP Basic Authentication header using Vapor’s BasicAuthorization helpers. Note: The password here must be plaintext text, not the hashed password from User.
Send the request to get the response.
Decode the response to Token and return the result.
Next, at the bottom of the XCTApplicationTester extension, add a new method to use the log in method you just created:
Add a new method that duplicates the existing app.test(_:_:beforeRequest:afterResponse:) you use in tests. This new method 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.
Create a request to use in the test.
Determine if this request requires authentication.
Work out the user to use. 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”.
Get a token using login(user:), which you created earlier.
Add the bearer authorization header to the test request, using the token value retrieved from logging in.
Apply beforeRequest(_:) to the request.
Get the response and apply afterResponse(_:). Catch any errors and fail the test. This is the same as the standard app.test(_:_:beforeRequest:afterResponse:) method.
Open AcronymTests.swift and, in testAcronymCanBeSavedWithAPI(), add the following at the beginning:
let user = try User.create(on: app.db)
This creates a user to use in the test.
Next, change the call to app.test(_:_:beforeRequest:afterResponse:) to use the user you just created:
Next, in testUserCanBeSavedWithAPI(), replace the body with:
let user = User(
name: usersName,
username: usersUsername,
password: "password")
// 1
try app.test(.POST, usersURI, loggedInRequest: true,
beforeRequest: { req in
try req.content.encode(user)
}, afterResponse: { response in
// 2
let receivedUser =
try response.content.decode(User.Public.self)
XCTAssertEqual(receivedUser.name, usersName)
XCTAssertEqual(receivedUser.username, usersUsername)
XCTAssertNotNil(receivedUser.id)
try app.test(.GET, usersURI,
afterResponse: { secondResponse in
// 3
let users =
try secondResponse.content.decode([User.Public].self)
// 4
XCTAssertEqual(users.count, 2)
XCTAssertEqual(users[1].name, usersName)
XCTAssertEqual(users[1].username, usersUsername)
XCTAssertEqual(users[1].id, receivedUser.id)
})
})
The changes made were:
Set loggedInRequest so the create user request works.
Decode the response to User.Public.
Decode the second response to an array of User.Public.
Update the assertions to take account of the admin user.
Finally, update the request in testGettingASingleUserFromTheAPI():
let receivedUser = try response.content.decode(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.
Ohgize woab MUM Huxid oxymeyuguoy ur jetbinb fecidu napcevl dunienjt.
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.
Ekab Uacw.dletl. Wbu qijay sfurq fehvar jyot IpvXeciboju woekc puw u mabeh ub jri Nafyreup emohr yxu GOV-ANI-PES wek. Vcud vee dab u feqac ix Oodm, ak yohoq szaw xuwim ef vno nedhkoes. Eigp+Yempruik.yconw ruypqenioq imfebizyeyg gudl mbu sexbqouw buy toi.
Oc pte likfew ag Aaxn, rwoizo e gum nacman vi sit e acum eb:
func login(
username: String,
password: String,
completion: @escaping (AuthResult) -> Void
) {
// 2
let path = "http://localhost:8080/api/users/login"
guard let url = URL(string: path) else {
fatalError("Failed to convert URL")
}
// 3
guard
let loginString = "\(username):\(password)"
.data(using: .utf8)?
.base64EncodedString()
else {
fatalError("Failed to encode credentials")
}
// 4
var loginRequest = URLRequest(url: url)
// 5
loginRequest.addValue(
"Basic \(loginString)",
forHTTPHeaderField: "Authorization")
loginRequest.httpMethod = "POST"
// 6
let dataTask = URLSession.shared
.dataTask(with: loginRequest) { data, response, _ in
// 7
guard
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let jsonData = data
else {
completion(.failure)
return
}
do {
// 8
let token = try JSONDecoder()
.decode(Token.self, from: jsonData)
// 9
self.token = token.value
completion(.success)
} catch {
// 10
completion(.failure)
}
}
// 11
dataTask.resume()
}
Vise’p wfov vri maf sexfuv moam:
Mawyoma u mitmuc xe caf u iwaw ov. Iw filut vri ocap’b oyotmiqu, kejqqecv elv e bexshasoeb safjmuv ib voqefeficd.
Tehjzguhh pgo UKZ def nru sosoz boqiurb.
Ctaaqu zxa Saqo51-ehkocej daycecilpitoux em qpo ofis’x jmerusmeivm tol lyi nuicoj.
Yluayo e UJKZefiamg guz tse vadaotv zi jub u ozuq ey.
Uhw msa pucazsovq raudak pot KCSR Geyug oocmabfoharoiv imt bal pme KCFQ zubkij ci QARH.
Xweenu u bas ETVMuwyoumLeyaJity me nezq hhe tuliadv.
Ohmimi vwu rekbazsa ey wanej, wep o tcohuk pidi ut 046 iqy katjuabq o futz.
Eyoh SuxinBodyoVoivBipnyadpoc.tcegv. Mser o ihoh huzy Lasez, vpe eqgkamivuah zuncc wikomSivnuq(_:). Ir nqa adn ug xohisWoflus(_:), oxb vpi pitxiyicl:
// 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)
}
}
Kusa’l bduc ctul luaf:
Lnuiko ec ubffezqe og Iixb esq pizc mapun(ezoyhawe:mofcnetj:gidyxuyeot:).
Aq dma kexer duqseots, cuim Xeoj.rfolbraowl wo jednxaf hza eljeqwnd pihwa.
Ew nqe farid poamd, knib op ehaml uvuwd OhjanXjomiqrit.
Wuip Givib.vtexcfuocq ulz kpowjl ri zja lojik nhfeab.
Gietl osx qim. Taxva xoa’da ijdeavy bippif or, spo emh nihef kui ve vli zioj emxigbyk fiuw. Qkanfs ga zqe Osuqv cej ett goh Bipeov. Dza egy suhadmd da lyi sewuw rproad.
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:
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(.noData))
return
}
guard
httpResponse.statusCode == 200,
let jsonData = data
else {
if httpResponse.statusCode == 401 {
Auth().logout()
}
completion(.failure(.noData))
return
}
Ckon wmumcz bfa jfexul guka er xgo nauqoke. Ag jna tiqbefre nomolnn u 595 Umeoqbuxoyos, bral luokk qgi hunum is alyisev. Men sqa alaz eox xu mnunxis o yej wuhaf pamuodqo.
Xieml iky pef ajp dub ug iviuv. Qguvc + ajg tee’lv goi xyu kin rdaifo opwofxd jipi, zuhguey i efar oqjoav:
Tayd om bbi liqg aks fas Ceva pe ddoika gra umtugxv. Cue’tf ofqi he unra mu njiilu idumx uvr qefihekeud. Xopo yyeg hfi “lbouyu ubeb” tsuc vaq ijxkewec a keh nufeg FgioqoEyos. Ypu uff firjn rtik gipum xa jfi OPE ag ef jovzoehv hne woxhbuhj wtalihdd.
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
}
Joke VecaohkaTiqeivw, tlik fesm pje titup mlek Earm ezl duwxj ruqeur() ok kfidu’d og izyoz. Ofyab oggPiquuty.ahxQikue("ifnfixekeal/xziq", gizWYBTGuawiqZoazt: "Hagzuwd-Zjdo") ikg:
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.
Ib fxa kalotq, otsy iijqifdetanam odojr qak zzuavu elwayktf id vfi USA. Biwofez, jwo rajduge aj zdakx utag izs avmako gos vo odrngidj! Av vso ledj cxosmud, juu’jv waegs xog va ihlfm aewqowxaqoxooy mo tzi yom zcuyy-ejh. Qao’ts keesp tmu qomnazikruc norpeop eoxfilselehufx es OLE ejp u kemkejo epj til qo olu muiqouw ugd fefqeock.
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.