Edward Potter Edward Potter - 1 month ago 39
Swift Question

OAuth2, Swift 3, Instagram

There seem to be lots of changes at IG. Many OAuth2 repos, all seem to have bugs, or really not easily converted to Swift3. Wondering if anyone has a solution for moving to Swift3 and working with the latest changes at Instagram?

Solutions most welcome. OAuth2 implementation seems to one of the more complicated things out there. Surprised that IG has not offered their own example docs on how to do this with iOS. They only have docs for web based solutions.

Maybe something brewing there? Zillions of coders they have on staff. But for now, on the hunt for a (dare I say?) simple solution.

thanks a million. :-)

Answer

I have provided my answer as Github project. I am using Simple Auth. It's written in Objective-C, but it is using old dependencies using Swift 2.

Within the Result Framework: Change the code from the Result.swift and the ResultType.swift to the code provided by my project. (Or translate it to Swift 3 yourself)

Within the Reactive Cocoa Framework: Delete all the .swift files. Reactive Cocoa got split up into stand alone ObjC and Swift Frameworks over time and Simple Auth is not using anything from the Swift files.

pod 'SimpleAuth/Instagram', '~> 0.3.9'

I have a struct for the Instagram Account:

struct InstagramUser {

    var token: String = ""
    var uid: String = ""
    var bio: String = ""
    var followed_by: String = ""
    var follows: String = ""
    var media: String = ""
    var username: String = ""
    var image: String = ""
}

The function to receive the token:

import SimpleAuth
typealias JSONDictionary = [String:Any]
var user: InstagramUser?
let INSTAGRAM_CLIENT_ID = "16ee14XXXXXXXXXXXXXXXXXXXXXXXXX"
let INSTAGRAM_REDIRECT_URI = "http://www.davidseek.com/just_a_made_up_dummy_url" //just important, that it matches your developer account uri at Instagram

extension ViewController {

    func connectInstagram() {

        let auth: NSMutableDictionary = ["client_id": INSTAGRAM_CLIENT_ID,
                                         SimpleAuthRedirectURIKey: INSTAGRAM_REDIRECT_URI]

        SimpleAuth.configuration()["instagram"] = auth            
        SimpleAuth.authorize("instagram", options: [:]) { (result: Any?, error: Error?) -> Void in

            if let result = result as? JSONDictionary  {

                var token = ""
                var uid = ""
                var bio = "" 
                var followed_by = ""
                var follows = ""
                var media = ""
                var username = ""
                var image = ""

                token = (result["credentials"] as! JSONDictionary)["token"] as! String
                uid = result["uid"] as! String

                if let extra = result["extra"] as? JSONDictionary,
                    let rawInfo = extra ["raw_info"] as? JSONDictionary,
                    let data = rawInfo["data"] as? JSONDictionary {

                    bio = data["bio"] as! String

                    if let counts = data["counts"] as? JSONDictionary {
                        followed_by = String(describing: counts["followed_by"]!)
                        follows = String(describing: counts["follows"]!)
                        media = String(describing: counts["media"]!)
                    }
                }

                if let userInfo = result["user_info"] as? JSONDictionary {
                    username = userInfo["username"] as! String
                    image = userInfo["image"] as! String
                }

                self.user = InstagramUser(token: token, uid: uid, bio: bio, followed_by: followed_by, follows: follows, media: media, username: username, image: image)


            } else {
                // this handles if user aborts or the API has a problem like server issue
                let alert = UIAlertController(title: "Error!", message: nil, preferredStyle: UIAlertControllerStyle.alert)
                alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }

            if error != nil {
                print("Error during SimpleAuth.authorize: \(error)")
            }
        }
    }
}

As you can see, I'm gathering all the information that is delivered by the OAuth and am creating a model of InstagramUser. Also, you could store the information into your database (I'm using Firebase for that porpuse:

class SaveInstagramAccount {

    fileprivate let firebase = FIRDatabase.database().reference().child("InstagramAccount")
    let accountDictionary: NSMutableDictionary

    init (backendlessObjectID: String,
          token: String,
          uid: String,
          bio: String,
          followed_by: String,
          follows: String,
          media: String,
          username: String,
          image: String) {

        accountDictionary = NSMutableDictionary(objects: [backendlessObjectID, token, uid, bio, followed_by, follows, media, username, image], forKeys: ["backendlessObjectID" as NSCopying, "token" as NSCopying, "uid" as NSCopying, "bio" as NSCopying, "followed_by" as NSCopying, "follows" as NSCopying, "media" as NSCopying, "username" as NSCopying, "image" as NSCopying])
    }

    func saveInstagramAccount(_ item: NSMutableDictionary) {

        let reference = firebase.child(ME).childByAutoId()
        item["accountID"] = reference.key
        reference.setValue(item) { (error, ref) -> Void in
            if error != nil {
                print("\(error)")
            }
        }            
    }
}

func saveAccount(token: String, uid: String, bio: String, followed_by: String, follows: String, media: String, username: String, image: String) {

    let newAccount = SaveInstagramAccount(backendlessObjectID: ME, token: token, uid: uid, bio: bio, followed_by: followed_by, follows: follows, media: media, username: username, image: image)       
    newAccount.saveInstagramAccount(newAccount.accountDictionary)
}

Simply called by:

saveAccount(token: token,
            uid: uid,
            bio: bio,
            followed_by: followed_by,
            follows: follows,
            media: media,
            username: username,
            image: image)

Instagram also says:

Important

Even though our access tokens do not specify an expiration time, your app should handle the case that either the user revokes access, or Instagram expires the token after some period of time. If the token is no longer valid, API responses will contain an “error_type=OAuthAccessTokenException”. In this case you will need to re-authenticate the user to obtain a new valid token. In other words: do not assume your access_token is valid forever.

So handle the case of receiving the OAuthAccessTokenException