User Authentication on iOS with Ruby on Rails and Swift

Learn how to secure your iOS app by adding user accounts using Swift and a custom Ruby on Rails backend. By Subhransu Behera.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Deleting a Selfie

What if the user thinks their nose looks a little too shiny or they notice there’s something in their teeth? You need to provide a way for them to wipe that humiliating shot from existence.

Open SelfieCollectionViewController.swift and replace collectionView(_:didSelectItemAtIndexPath:) with the following:

override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
  // fetch the Selfie Image Object
  var rowIndex = self.dataArray.count - (indexPath.row + 1)
  var selfieRowObj = self.dataArray[rowIndex] as SelfieImage
  
  pushDetailsViewControllerWithSelfieObject(selfieRowObj)
}

This calls pushDetailsViewControllerWithSelfieObject(_:) passing a SelfieImage object. Implement pushDetailsViewControllerWithSelfieObject(_:) by adding the following:

func pushDetailsViewControllerWithSelfieObject(selfieRowObj:SelfieImage!) {
  // instantiate detail view controller
  if let detailVC = self.storyboard?.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController {
    detailVC.editDelegate = self
    detailVC.selfieCustomObj = selfieRowObj
    
    // push detail view controller to the navigation stack
    self.navigationController?.pushViewController(detailVC, animated: true)
  }
}

The above instantiates DetailViewController from the storyboard and sets the selfie image object. When the user taps on a selfie, DetailViewController is displayed. Here the user can check out their selfie, and delete it if they don’t like it.

Open DetailViewController.swift and replace the viewDidLoad() with the following:

override func viewDidLoad() {
  super.viewDidLoad()
  
  self.activityIndicatorView.layer.cornerRadius = 10
  self.detailTitleLbl.text = self.selfieCustomObj.imageTitle
  var imgURL = NSURL(string: self.selfieCustomObj.imageThumbnailURL)
  
  // Download an NSData representation of the image at the URL
  let request = NSURLRequest(URL: imgURL!)
  
  NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), 
      completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
    if error == nil {
      var image = UIImage(data: data)
      
      dispatch_async(dispatch_get_main_queue(), {
        self.detailThumbImgView.image = image
      })
    } else {
      println("Error: \(error.localizedDescription)")
    }
  })
}

This sets the title of the image in detail view and asynchronously downloads the selfie from Amazon S3 before displaying it. When the user deletes an image, the action triggers deleteBtnTapped(_:).

Replace deleteBtnTapped(_:) with the following:

@IBAction func deleteBtnTapped(sender: AnyObject) {
  // show activity indicator
  self.activityIndicatorView.hidden = false
  
  // Create HTTP request and set request Body
  let httpRequest = httpHelper.buildRequest("delete_photo", method: "DELETE", authType: HTTPRequestAuthType.HTTPTokenAuth)
  
  httpRequest.HTTPBody = "{\"photo_id\":\"\(self.selfieCustomObj.imageId)\"}".dataUsingEncoding(NSUTF8StringEncoding);
  
  httpHelper.sendRequest(httpRequest, completion: {(data:NSData!, error:NSError!) in
    // Display error
    if error != nil {
      let errorMessage = self.httpHelper.getErrorMessage(error)
      self.displayAlertMessage("Error", alertDescription: errorMessage as String)
      
      return
    }
    
    self.editDelegate.deleteSelfieObjectFromList(self.selfieCustomObj)
    self.activityIndicatorView.hidden = true
    self.navigationController?.popToRootViewControllerAnimated(true)
  })
}

The above creates an HTTP DELETE request to delete the selfie from the server. Upon successful completion, the above code also calls the method deleteSelfieObjectFromList(_:), which deletes the selfie from the local list of selfies and updates the collection view.

Open SelfieCollectionViewController.swift and add the following two methods:

// This is in the base SelfieCollectionViewController class implementation
func removeObject<T:Equatable>(inout arr:Array<T>, object:T) -> T? {
  if let indexOfObject = find(arr,object) {
    return arr.removeAtIndex(indexOfObject)
  }
  return nil
}
// This is in edit selfie extension
func deleteSelfieObjectFromList(selfieImgObject: SelfieImage) {
  if contains(self.dataArray, selfieImgObject) {
    removeObject(&self.dataArray, object: selfieImgObject)
    self.collectionView?.reloadData()
  }
}

The first method deletes an object from an array, and the second method is the protocol implementation that deletes a local selfie and reloads the collection view.

Build and run. Delete your least favorite selfie — And just like that, it’s a distant memory. Good thing for Mr. Panda — ever since he became synonymous with SEO, he’s been rather particular about his selfies.

delete

Handling Signing Out

Open SelfieCollectionViewController.swift and replace the contents of logoutBtnTapped() with the following:

func logoutBtnTapped() {
  clearLoggedinFlagInUserDefaults()
  clearDataArrayAndReloadCollectionView()
  clearAPITokensFromKeyChain()
  
  // Set flag to display Sign In view
  shouldFetchNewData = true
  self.viewDidAppear(true)
}

Next, implement the following three methods that get called from logoutBtnTapped

// 1. Clears the NSUserDefaults flag
func clearLoggedinFlagInUserDefaults() {
  let defaults = NSUserDefaults.standardUserDefaults()
  defaults.removeObjectForKey("userLoggedIn")
  defaults.synchronize()
}

// 2. Removes the data array
func clearDataArrayAndReloadCollectionView() {
  self.dataArray.removeAll(keepCapacity: true)
  self.collectionView?.reloadData()
}

// 3. Clears API Auth token from Keychain
func clearAPITokensFromKeyChain () {
  // clear API Auth Token
  if let userToken = KeychainAccess.passwordForAccount("Auth_Token", service: "KeyChainService") {
    KeychainAccess.deletePasswordForAccount(userToken, account: "Auth_Token", service: "KeyChainService")
  }
  
  // clear API Auth Expiry
  if let userTokenExpiryDate = KeychainAccess.passwordForAccount("Auth_Token_Expiry", 
      service: "KeyChainService") {
    KeychainAccess.deletePasswordForAccount(userTokenExpiryDate, account: "Auth_Token_Expiry", 
        service: "KeyChainService")
  }
}

The above methods carry out the following tasks:

  1. Removes userLoggedIn flag from NSUserDefaults.
  2. Removes the data array and reloads the collection view. This is to make sure that when a new user signs in they don’t see any cached data.
  3. Clears the API auth token and credentials from the Keychain.

The logoutBtnTapped() also gets triggered when the API auth token expires, forcing the user to sign in again and obtain a new auth token.

Build and run. Tap Logout; you should be taken back to the Sign In screen.

Where To Go From Here?

Here’s the finished sample project with all the code from tutorial.

Congratulations! You’ve just successfully set up a backend server on Heroku that provides your API, configured an Amazon S3 bucket to store your users’ selfie images, and built an application that allows a user to upload a selfie to your service.

No doubt this absolutely essential app will help you capture your moods, snapping those awesome moments like this one right now. You did take a selfie of your victory face, right?

Take a look at the Authentication Cheat Sheet by OWASP; it’s also a good resource for many other security guides and materials that you can access for free.

Thank you for taking the time to work through this tutorial! If you have any questions, comments, or find a unique requirement about secure API design or mobile security, feel free to chime in below and I will be happy to help.

Subhransu Behera

Contributors

Subhransu Behera

Author

Over 300 content creators. Join our team.