Last updated on: May 27, 2023
Today, I’ll show you how to implement the ‘Log in with GitHub’ button into your iOS app.
We’re going to use 3-legged Authorization(OAuth) to get the authorization code and exchange it later for the access token. Using the access token, we’ll be able to get user’s information and email address making an HTTP request to https://api.github.com/user
Contents
Lets get started!
First, we need to create the Client ID and Client Secret on the GitHub website.
Go to https://github.com/settings/developers and press the Register a new application button.
Fill the required fields and press Register application.
On the new page, you’ll see the Client ID and Client Secret we going to use later.
Creating the GitHub Login Button
Create an UIButton in your ViewController and make an Outlet and an Action connection to your swift file. In this example, we called it githubLoginBtn and githubLoginBtnAction.
Create a new swift file to put the Constants.
Paste the following code inside.
struct GithubConstants {
static let CLIENT_ID = "MY_CLIENT_ID"
static let CLIENT_SECRET = "MY_CLIENT_SECRET"
static let REDIRECT_URI = "MY_REDIRECT_URI"
static let SCOPE = "read:user,user:email"
static let TOKENURL = "https://github.com/login/oauth/access_token"
}
Code language: Swift (swift)
Replace the Client ID, Client Secret, and Redirect URI with yours.
Next, create a ViewController with a WebView.
The user puts the email and password and gives access to the app.
@IBAction func githubLoginBtnAction(_ sender: UIButton) {
githubAuthVC()
}
var webView = WKWebView()
func githubAuthVC() {
// Create github Auth ViewController
let githubVC = UIViewController()
// Generate random identifier for the authorization
let uuid = UUID().uuidString
// Create WebView
let webView = WKWebView()
webView.navigationDelegate = self
githubVC.view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: githubVC.view.topAnchor),
webView.leadingAnchor.constraint(equalTo: githubVC.view.leadingAnchor),
webView.bottomAnchor.constraint(equalTo: githubVC.view.bottomAnchor),
webView.trailingAnchor.constraint(equalTo: githubVC.view.trailingAnchor)
])
let authURLFull = "https://github.com/login/oauth/authorize?client_id=" + GithubConstants.CLIENT_ID + "&scope=" + GithubConstants.SCOPE + "&redirect_uri=" + GithubConstants.REDIRECT_URI + "&state=" + uuid
let urlRequest = URLRequest(url: URL(string: authURLFull)!)
webView.load(urlRequest)
// Create Navigation Controller
let navController = UINavigationController(rootViewController: githubVC)
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancelAction))
githubVC.navigationItem.leftBarButtonItem = cancelButton
let refreshButton = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(self.refreshAction))
githubVC.navigationItem.rightBarButtonItem = refreshButton
let textAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navController.navigationBar.titleTextAttributes = textAttributes
githubVC.navigationItem.title = "github.com"
navController.navigationBar.isTranslucent = false
navController.navigationBar.tintColor = UIColor.white
navController.navigationBar.barTintColor = UIColor.black
navController.modalPresentationStyle = UIModalPresentationStyle.overFullScreen
navController.modalTransitionStyle = .coverVertical
self.present(navController, animated: true, completion: nil)
}
@objc func cancelAction() {
self.dismiss(animated: true, completion: nil)
}
@objc func refreshAction() {
self.webView.reload()
}
Code language: Swift (swift)
After giving access, the webView gets the URL request, using the method decidePolicyFor from the WKNavigationDelegate.
Then, from the URL request, get the authorization code using the .range method and exchange it for the access token.
extension LoginViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
self.RequestForCallbackURL(request: navigationAction.request)
decisionHandler(.allow)
}
func RequestForCallbackURL(request: URLRequest) {
// Get the authorization code string after the '?code=' and before '&state='
let requestURLString = (request.url?.absoluteString)! as String
print(requestURLString)
if requestURLString.hasPrefix(GithubConstants.REDIRECT_URI) {
if requestURLString.contains("code=") {
if let range = requestURLString.range(of: "=") {
let githubCode = requestURLString[range.upperBound...]
if let range = githubCode.range(of: "&state=") {
let githubCodeFinal = githubCode[..<range.lowerBound]
githubRequestForAccessToken(authCode: String(githubCodeFinal))
// Close GitHub Auth ViewController after getting Authorization Code
self.dismiss(animated: true, completion: nil)
}
}
}
}
}
func githubRequestForAccessToken(authCode: String) {
let grantType = "authorization_code"
// Set the POST parameters.
let postParams = "grant_type=" + grantType + "&code=" + authCode + "&client_id=" + GithubConstants.CLIENT_ID + "&client_secret=" + GithubConstants.CLIENT_SECRET
let postData = postParams.data(using: String.Encoding.utf8)
let request = NSMutableURLRequest(url: URL(string: GithubConstants.TOKENURL)!)
request.httpMethod = "POST"
request.httpBody = postData
request.addValue("application/json", forHTTPHeaderField: "Accept")
let session = URLSession(configuration: URLSessionConfiguration.default)
let task: URLSessionDataTask = session.dataTask(with: request as URLRequest) { (data, response, _) -> Void in
let statusCode = (response as! HTTPURLResponse).statusCode
if statusCode == 200 {
let results = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [AnyHashable: Any]
let accessToken = results?["access_token"] as! String
// Get user's id, display name, email, profile pic url
self.fetchGitHubUserProfile(accessToken: accessToken)
}
}
task.resume()
}
}
Code language: Swift (swift)
Finally, use the access token to get the user’s ID, display name, email and profile pic URL using HTTP request.
func fetchGitHubUserProfile(accessToken: String) {
let tokenURLFull = "https://api.github.com/user"
let verify: NSURL = NSURL(string: tokenURLFull)!
let request: NSMutableURLRequest = NSMutableURLRequest(url: verify as URL)
request.addValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, _, error in
if error == nil {
let result = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [AnyHashable: Any]
// AccessToken
print("GitHub Access Token: \(accessToken)")
// GitHub Handle
let githubId: Int! = (result?["id"] as! Int)
print("GitHub Id: \(githubId ?? 0)")
// GitHub Display Name
let githubDisplayName: String! = (result?["login"] as! String)
print("GitHub Display Name: \(githubDisplayName ?? "")")
// GitHub Email
let githubEmail: String! = (result?["email"] as! String)
print("GitHub Email: \(githubEmail ?? "")")
// GitHub Profile Avatar URL
let githubAvatarURL: String! = (result?["avatar_url"] as! String)
print("github Profile Avatar URL: \(githubAvatarURL ?? "")")
DispatchQueue.main.async {
self.performSegue(withIdentifier: "detailseg", sender: self)
}
}
}
task.resume()
}
Code language: Swift (swift)
That’s it!!
You can find the final project here
If you have any questions, please feel free to leave a comment below