In this episode we wrap up the Easy Auth series building the tvOS application to use our API. We'll create an authentication client and discuss how to pass around a set of values to and from the API, as well as polling for status.
Episode Links Source Code Setting up a data structure For our Workflow struct AuthenticationRequest { var clientID: String? var token: String? var code: String? var error: NSError? var status: String? var authTokenData: String? init(error: NSError) { self.error = error } init(clientID: String, code: String, token: String) { self.clientID = clientID self.code = code self.token = token } } This will hold all the relevant pieces of information that we'll use for the workflow. Creating the AuthenticationClient We'll use an authentication client to perform the tasks required for the workflow: class AuthenticationClient { let session: NSURLSession let clientID: String let baseURL: NSURL init(clientID: String) { self.clientID = clientID self.session = NSURLSession.sharedSession() self.baseURL = NSURL(string: "http://localhost:9393")! } var activateURLString: String { return baseURL.URLByAppendingPathComponent("/activate").absoluteString } } Next we'll add a method to start the process and get a code: func requestCode(completion: (AuthenticationRequest) -> Void) { let url = baseURL.URLByAppendingPathComponent("/easy_auth") let request = NSMutableURLRequest(URL: url) request.HTTPMethod = "POST" request.HTTPBody = "client_id=\(clientID)".dataUsingEncoding(NSUTF8StringEncoding) let mainCallback: (AuthenticationRequest) -> Void = { (request) in dispatch_async(dispatch_get_main_queue()) { completion(request) } } let task = session.dataTaskWithRequest(request) { (data, response, error) in if let e = error { mainCallback(AuthenticationRequest(error: e)) } else { let http = response as! NSHTTPURLResponse let body = String(data: data!, encoding: NSUTF8StringEncoding)! if http.statusCode == 200 { let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: []) let code = (json["code"] as! NSNumber).integerValue let codeString = "\(code)" let token = json["token"] as! String let authReq = AuthenticationRequest(clientID: self.clientID, code: codeString, token: token) mainCallback(authReq) } else { print("Received HTTP \(http.statusCode)") print("Body: \(body)") let error = NSError(domain: "authError", code: http.statusCode, userInfo: ["body": body]) mainCallback(AuthenticationRequest(error: error)) } } } task.resume() } Checking Status We'll need to continuously check status for a request and be able to react when the request has been authenticated: func statusForRequest(authRequest: AuthenticationRequest, completion: (AuthenticationRequest) -> Void) { let url = baseURL.URLByAppendingPathComponent("/easy_auth/\(authRequest.token!)") let mainCallback: (AuthenticationRequest) -> Void = { (request) in dispatch_async(dispatch_get_main_queue()) { completion(request) } } let task = session.dataTaskWithURL(url) { (data, response, error) in var updatedRequest = authRequest if let e = error { updatedRequest.error = e } else { let http = response as! NSHTTPURLResponse let body = String(data: data!, encoding: NSUTF8StringEncoding)! if http.statusCode == 200 { let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: []) updatedRequest.status = json["status"] as? String if updatedRequest.status == "authenticated" { updatedRequest.authTokenData = json["auth_token_data"] as? String } } else { print("Received HTTP \(http.statusCode)") print("Body: \(body)") let error = NSError(domain: "authError", code: http.statusCode, userInfo: ["body": body]) updatedRequest.error = error } } mainCallback(updatedRequest) } task.resume() } Implementing the View Controller In our LoginViewController we'll kick off the process and show our code on the screen: override func viewDidLoad() { super.viewDidLoad() authCodeLabel.hidden = true activityIndicator.startAnimating() let clientID = UIDevice.currentDevice().identifierForVendor!.UUIDString authClient = AuthenticationClient(clientID: clientID) directionLabel.text = directionLabel.text?.stringByReplacingOccurrencesOfString("URL", withString: authClient.activateURLString) requestCode() } func requestCode() { authClient.requestCode { (req) in self.activityIndicator.stopAnimating() if let e = req.error { self.displayAlert(e) } else { self.authCodeLabel.text = req.code! self.authCodeLabel.hidden = false self.pollForStatus(req, delay: 1) } } } Polling For Status Our pollForStatus method is written so that we can have it call itself over and over again: func pollForStatus(authReq: AuthenticationRequest, delay: NSTimeInterval) { let time: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))) dispatch_after(time, dispatch_get_main_queue()) { self.authClient.statusForRequest(authReq, completion: { (updatedRequest) in if let e = updatedRequest.error { self.displayAlert(e) } else if updatedRequest.status == "authenticated" { self.finalizeAuthentication(updatedRequest) } else { self.pollForStatus(updatedRequest, delay: delay) } }) } } Finalizing the Workflow func finalizeAuthentication(authRequest: AuthenticationRequest) { let authToken = deriveAuthToken(authRequest) AuthStore.instance.authToken = authToken NSNotificationCenter.defaultCenter().postNotificationName("LoggedIn", object: nil) self.dismissViewControllerAnimated(true, completion: nil) } func deriveAuthToken(authRequest: AuthenticationRequest) -> String { let algorithm = CCHmacAlgorithm(kCCHmacAlgSHA1) let digestLength = Int(CC_SHA1_DIGEST_LENGTH) let buffer = UnsafeMutablePointer<UInt8>.alloc(digestLength) let key = authRequest.clientID! let data = authRequest.authTokenData! CCHmac(algorithm, key, key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding), data, data.lengthOfBytesUsingEncoding(NSUTF8StringEncoding), buffer) var str = "" for i in 0..<digestLength { str = str.stringByAppendingFormat("%x", buffer[i]) } return str }