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.
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
User Authentication on iOS with Ruby on Rails and Swift
40 mins
- Getting Started
- Setting Up Your Rails Application
- Deploying Rails Application on Heroku
- Configuring Amazon S3 (Simple Storage Service)
- Setting up Heroku Environment Variables
- About the APIs
- Getting Started with the Swift Application
- Sign up and Sign in
- Display Existing Selfies
- Uploading a Selfie to the Server
- Deleting a Selfie
- Handling Signing Out
- Where To Go From Here?
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.
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:
- Removes
userLoggedIn
flag from NSUserDefaults. - 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.
- 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.