Sparsh Agrawal
Sparsh Agrawal

Reputation: 45

OAuth authentication using GitHub in iOS apps - how does it work

I am trying to understand how oauth authentication with GITHUB works in case of IOS Apps.

I am developing an IOS App and want to use GITHUB for login authentication.

Here is flow of my app.

  1. User registration - This is an offline process and happens outside of my app. When I create an account for user, I ask them to provide their GITHUB email address. I store this email address in our DB as userid for that user.

  2. Get access token from GITHUB - when user opens our app we direct them to https://github.com/login/oauth/authorize using webview. Once user successfully login to GITHUB account I use https://github.com/login/oauth/access_token to get access token.

  3. Get email address using access token - I am using https://api.github.com/user/emails to get email address of logged account using access token I got in step 2.

  4. Verify email address: I verify email address I got in step3 against my database. If userid exists then user will be able to do transactions on our app.

Right now after GITHUB verification control is coming back to viewcontroller that has webview for GITHUB and a blank screen appears. How do I move flow to next viewcontroller ?

Here is my ViewController Code:

import UIKit
import WebKit

class Login: UIViewController, UIWebViewDelegate {

    @IBOutlet weak var webview: UIWebView!
    override func viewDidLoad() {
        super.viewDidLoad()

        let authURL = String(format: "%@?client_id=%@&redirect_uri=%@&scope=%@", arguments: [GITHUB.GITHUB_AUTHURL,GITHUB.GITHUB_CLIENT_ID,GITHUB.GITHUB_REDIRECT_URI,GITHUB.GITHUB_SCOPE])

        let urlRequest = URLRequest.init(url: URL.init(string: authURL)!)
        webview.loadRequest(urlRequest)
        webview.delegate = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func webView(_ webview: UIWebView, shouldStartLoadWith request:URLRequest, navigationType: UIWebViewNavigationType) -> Bool{
        return checkRequestForCallbackURL(request: request)
    }

    func checkRequestForCallbackURL(request: URLRequest) -> Bool {
        //print("3. IN FUNCTION checkRequestForCallbackURL")

        let requestURLString = (request.url?.absoluteString)! as String
        //print("3. requestURLString=\(requestURLString)")

        if requestURLString.hasPrefix(GITHUB.GITHUB_REDIRECT_URI) {

            let range: Range<String.Index> = requestURLString.range(of: "?code=")!

            handleGithubCode(code: requestURLString.substring(from: range.upperBound))

            return false;
        }

        return true
    }

    func handleGithubCode(code: String) {

            let urlString = "https://github.com/login/oauth/access_token"
            if let tokenUrl = URL(string: urlString) {

                let req = NSMutableURLRequest(url: tokenUrl)
                req.httpMethod = "POST"
                req.addValue("application/json", forHTTPHeaderField: "Content-Type")
                req.addValue("application/json", forHTTPHeaderField: "Accept")

                let params = [
                    "client_id" : GITHUB.GITHUB_CLIENT_ID,
                    "client_secret" : GITHUB.GITHUB_CLIENTSECRET,
                    "code" : code
                ]

                req.httpBody = try? JSONSerialization.data(withJSONObject: params, options: [])
                let task = URLSession.shared.dataTask(with: req as URLRequest) { data, response, error in

                    if let data = data {
                        do {
                            if let content = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] {
                                if let accessToken = content["access_token"] as? String {
                                    self.getComposerToken(accessToken: accessToken)

                                }
                            }
                        } catch {}
                    }
                }
                task.resume()
            }
    }

    func getComposerToken(accessToken: String) {
        print("5. accessToken=\(accessToken)")

        let def = "NO_DATA"

        let composerUrl = "http://192.168.100.112/kubher/getAccessToken.php?token=\(accessToken)"
        guard let url = URL(string: composerUrl) else { return }

        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let response = response {
                //print(response)
            }

            if let data = data {
                do {
                    let json = try? JSONSerialization.jsonObject(with: data, options: [])

                    if let dict = json as? [String: Any],
                        let token = dict["accessToken"] {
                        print("Blockchain Token:\(token)")
                    }
                } catch {
                    print(error)
                }
            }
        }.resume()
  }
}

Upvotes: 2

Views: 1584

Answers (1)

Razib Mollick
Razib Mollick

Reputation: 5052

Move to next viewcontroller depends on the architecture of your code. Try the following code based on your design. opt 1: If you need to go back to previous ViewController, just modify your getComposerToken function:

if let dict = json as? [String: Any], {
    let token = dict["accessToken"] {
    print("Blockchain Token:\(token)")
    dispatch_async(dispatch_get_main_queue()) {
        self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
    }
    }
}

opt 2: On the other hand, If you are using Segue for next viewController in stoaryboard, give your segue a name(Identifier) and then follow this code:

if let dict = json as? [String: Any], {
        let token = dict["accessToken"] {
        print("Blockchain Token:\(token)")
        dispatch_async(dispatch_get_main_queue()) {
            self.performSegue(withIdentifier: "YourSegueName", sender: token)
        }
      }
    }

Additionally, you have to override prepare method to pass data

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "segueName" {
            let viewController = segue.destination as? YourViewController
            if let token = sender as? String {
                viewController?.token = token
            }
        }
    }
}

OPT 3: If you use push view controller after creating from storyboard, you have to give your view controller an Identifier in storyboard and then you can instantiate it using and push it using:

if let dict = json as? [String: Any], {
    let token = dict["accessToken"] {
    let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
    let abcViewController = storyboard.instantiateViewControllerWithIdentifier("YourControlleridentifier") as! YourViewController
YourViewController.token = token
navigationController?.pushViewController(YourViewController, animated: true)
}
}

Upvotes: 1

Related Questions