Last updated on: May 27, 2023
Today, I’ll show you how to implement the ‘Sign In with LinkedIn’ button into your iOS app.
We will use the 3-legged OAuth method to get the authorization code and exchange it later for the access token. Using the access token, we’ll be able to get the user’s information and email address, making two HTTP GET requests, one to get the user’s id, first name, last name, and profile pic URL, and one to get the email address.
Contents
Getting the App Credentials
First, create a LinkedIn account if you don’t already have one.
Go to https://www.linkedin.com/developers and click on the Create app button.
Fill in the required fields.
To create an app, you need to add your company’s page.
If you’re not a company or you want to avoid this step, add a random company. It doesn’t cause any problems.
Next, go to the Auth tab of your app and add your redirect URL.
Creating the ‘Sign In with LinkedIn’ Button
First, create an UIButton
in your ViewController and make an Outlet and an Action connection to your swift file. In this example, we called it linkedInLoginBtn
and linkedInLoginBtnAction
.
Create a new swift file to put the Constants.
Paste the following code inside.
struct LinkedInConstants {
static let CLIENT_ID = "MY_CLIENT_ID"
static let CLIENT_SECRET = "MY_CLIENT_SECRET"
static let REDIRECT_URI = "MY_REDIRECT_URI"
static let SCOPE = "r_liteprofile%20r_emailaddress" //Get lite profile info and e-mail address
static let AUTHURL = "https://www.linkedin.com/oauth/v2/authorization"
static let TOKENURL = "https://www.linkedin.com/oauth/v2/accessToken"
}
Code language: Swift (swift)
Replace the Client ID, Client Secret and Redirect URI with yours.
Create a new swift file with the name LinkedInEmailModel and paste the following code.
// MARK: - LinkedInEmailModel
struct LinkedInEmailModel: Codable {
let elements: [Element]
}
// MARK: - Element
struct Element: Codable {
let elementHandle: Handle
let handle: String
enum CodingKeys: String, CodingKey {
case elementHandle = "handle~"
case handle
}
}
// MARK: - Handle
struct Handle: Codable {
let emailAddress: String
}
Code language: Swift (swift)
Create another one with the name LinkedInProfileModel.
// MARK: - LinkedInProfileModel
struct LinkedInProfileModel: Codable {
let firstName, lastName: StName
let profilePicture: ProfilePicture
let id: String
}
// MARK: - StName
struct StName: Codable {
let localized: Localized
}
// MARK: - Localized
struct Localized: Codable {
let enUS: String
enum CodingKeys: String, CodingKey {
case enUS = "en_US"
}
}
// MARK: - ProfilePicture
struct ProfilePicture: Codable {
let displayImage: DisplayImage
enum CodingKeys: String, CodingKey {
case displayImage = "displayImage~"
}
}
// MARK: - DisplayImage
struct DisplayImage: Codable {
let elements: [ProfilePicElement]
}
// MARK: - Element
struct ProfilePicElement: Codable {
let identifiers: [ProfilePicIdentifier]
}
// MARK: - Identifier
struct ProfilePicIdentifier: Codable {
let identifier: String
}
Code language: Swift (swift)
You’ll need these models later to get the user’s profile info and email address from the HTTP requests.
Next, create a ViewController with a WebView.
The user puts in the email and password and gives access to the app.
@IBAction func linkedInLoginBtnAction(_ sender: UIButton) {
linkedInAuthVC()
}
var webView = WKWebView()
func linkedInAuthVC() {
// Create linkedIn Auth ViewController
let linkedInVC = UIViewController()
// Create WebView
let webView = WKWebView()
webView.navigationDelegate = self
linkedInVC.view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: linkedInVC.view.topAnchor),
webView.leadingAnchor.constraint(equalTo: linkedInVC.view.leadingAnchor),
webView.bottomAnchor.constraint(equalTo: linkedInVC.view.bottomAnchor),
webView.trailingAnchor.constraint(equalTo: linkedInVC.view.trailingAnchor)
])
let state = "linkedin\(Int(NSDate().timeIntervalSince1970))"
let authURLFull = LinkedInConstants.AUTHURL + "?response_type=code&client_id=" + LinkedInConstants.CLIENT_ID + "&scope=" + LinkedInConstants.SCOPE + "&state=" + state + "&redirect_uri=" + LinkedInConstants.REDIRECT_URI
let urlRequest = URLRequest.init(url: URL.init(string: authURLFull)!)
webView.load(urlRequest)
// Create Navigation Controller
let navController = UINavigationController(rootViewController: linkedInVC)
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(self.cancelAction))
linkedInVC.navigationItem.leftBarButtonItem = cancelButton
let refreshButton = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(self.refreshAction))
linkedInVC.navigationItem.rightBarButtonItem = refreshButton
let textAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
navController.navigationBar.titleTextAttributes = textAttributes
linkedInVC.navigationItem.title = "linkedin.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) {
RequestForCallbackURL(request: navigationAction.request)
//Close the View Controller after getting the authorization code
if let urlStr = navigationAction.request.url?.absoluteString {
if urlStr.contains("?code=") {
self.dismiss(animated: true, completion: nil)
}
}
decisionHandler(.allow)
}
func RequestForCallbackURL(request: URLRequest) {
// Get the authorization code string after the '?code=' and before '&state='
let requestURLString = (request.url?.absoluteString)! as String
if requestURLString.hasPrefix(LinkedInConstants.REDIRECT_URI) {
if requestURLString.contains("?code=") {
if let range = requestURLString.range(of: "=") {
let linkedinCode = requestURLString[range.upperBound...]
if let range = linkedinCode.range(of: "&state=") {
let linkedinCodeFinal = linkedinCode[..<range.lowerBound]
handleAuth(linkedInAuthorizationCode: String(linkedinCodeFinal))
}
}
}
}
}
func handleAuth(linkedInAuthorizationCode: String) {
linkedinRequestForAccessToken(authCode: linkedInAuthorizationCode)
}
func linkedinRequestForAccessToken(authCode: String) {
let grantType = "authorization_code"
// Set the POST parameters.
let postParams = "grant_type=" + grantType + "&code=" + authCode + "&redirect_uri=" + LinkedInConstants.REDIRECT_URI + "&client_id=" + LinkedInConstants.CLIENT_ID + "&client_secret=" + LinkedInConstants.CLIENT_SECRET
let postData = postParams.data(using: String.Encoding.utf8)
let request = NSMutableURLRequest(url: URL(string: LinkedInConstants.TOKENURL)!)
request.httpMethod = "POST"
request.httpBody = postData
request.addValue("application/x-www-form-urlencoded;", forHTTPHeaderField: "Content-Type")
let session = URLSession(configuration: URLSessionConfiguration.default)
let task: URLSessionDataTask = session.dataTask(with: request as URLRequest) { (data, response, error) -> 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
print("accessToken is: \(accessToken)")
let expiresIn = results?["expires_in"] as! Int
print("expires in: \(expiresIn)")
// Get user's id, first name, last name, profile pic url
self.fetchLinkedInUserProfile(accessToken: accessToken)
}
}
task.resume()
}
}
Code language: Swift (swift)
Finally, use the access token to get the user’s ID, first name, last name, email, and profile pic URL using an HTTP request.
func fetchLinkedInUserProfile(accessToken: String) {
let tokenURLFull = "https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))&oauth2_access_token=\(accessToken)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let verify: NSURL = NSURL(string: tokenURLFull!)!
let request: NSMutableURLRequest = NSMutableURLRequest(url: verify as URL)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
if error == nil {
let linkedInProfileModel = try? JSONDecoder().decode(LinkedInProfileModel.self, from: data!)
//AccessToken
print("LinkedIn Access Token: \(accessToken)")
// LinkedIn Id
let linkedinId: String! = linkedInProfileModel?.id
print("LinkedIn Id: \(linkedinId ?? "")")
// LinkedIn First Name
let linkedinFirstName: String! = linkedInProfileModel?.firstName.localized.enUS
print("LinkedIn First Name: \(linkedinFirstName ?? "")")
// LinkedIn Last Name
let linkedinLastName: String! = linkedInProfileModel?.lastName.localized.enUS
print("LinkedIn Last Name: \(linkedinLastName ?? "")")
// LinkedIn Profile Picture URL
let linkedinProfilePic: String!
/*
Change row of the 'elements' array to get diffrent size of the profile url
elements[0] = 100x100
elements[1] = 200x200
elements[2] = 400x400
elements[3] = 800x800
*/
if let pictureUrls = linkedInProfileModel?.profilePicture.displayImage.elements[2].identifiers[0].identifier {
linkedinProfilePic = pictureUrls
} else {
linkedinProfilePic = "Not exists"
}
print("LinkedIn Profile Avatar URL: \(linkedinProfilePic ?? "")")
// Get user's email address
self.fetchLinkedInEmailAddress(accessToken: accessToken)
}
}
task.resume()
}
func fetchLinkedInEmailAddress(accessToken: String) {
let tokenURLFull = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))&oauth2_access_token=\(accessToken)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
let verify: NSURL = NSURL(string: tokenURLFull!)!
let request: NSMutableURLRequest = NSMutableURLRequest(url: verify as URL)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
if error == nil {
let linkedInEmailModel = try? JSONDecoder().decode(LinkedInEmailModel.self, from: data!)
// LinkedIn Email
let linkedinEmail: String! = linkedInEmailModel?.elements[0].elementHandle.emailAddress
print("LinkedIn Email: \(linkedinEmail ?? "")")
DispatchQueue.main.async {
self.performSegue(withIdentifier: "detailseg", sender: self)
}
}
}
task.resume()
}
Code language: Swift (swift)
That’s it! Done!
You can find the final project here
If you have any questions, please feel free to leave a comment below