Last updated on: May 27, 2023
Today, I’ll show you how to implement the ‘Sign In with LinkedIn’ button into your Android app.
We’re going to 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
Let’s get started!
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, just add a random company. It doesn’t cause any problem.
Next, go to the Auth tab of your app and add your redirect URL.
Adding the dependencies
In this tutorial, we’re going to use two dependencies (Coroutines and Serialization) that will help us do HTTP POST requests and parse JSON easier.
In the build.gradle file of your project (ex. build.gradle (Project: LinkedInSignInExample)) add the following classpath
dependencies {
// ...
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
// ...
}
Code language: Kotlin (kotlin)
…and on the build.gradle file of your app module (ex. build.gradle (Module: app)) add:
apply plugin: 'kotlinx-serialization'
// ...
dependencies {
// ...
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.13.0'
// ...
}
Code language: Kotlin (kotlin)
Creating the ‘Sign In with LinkedIn’ Button
First, add a Button to your XML file. In this example, we have a button in the middle of the activity_main.xml.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context=".MainActivity">
<Button
android:id="@+id/linkedin_login_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/rounded_corners"
android:drawablePadding="8dp"
android:drawableStart="@drawable/linkedin_icon"
android:drawableLeft="@drawable/linkedin_icon"
android:padding="8dp"
android:text="Sign In with LinkedIn"
android:textAllCaps="false"
android:textColor="@android:color/white" />
</RelativeLayout>
Code language: HTML, XML (xml)
Create a new Kotlin object file by right-clicking on your app’s package name on the left side and go New > Kotlin File/Class.
Give it the name LinkedInConstants, choose Object and press OK.
Inside the new file, copy and paste the following code and replace the CLIENT_ID, CLIENT_SECRET and REDIRECT_URI with yours.
object LinkedInConstants {
val CLIENT_ID = "MY_CLIENT_ID"
val CLIENT_SECRET = "MY_CLIENT_SECRET"
val REDIRECT_URI = "MY_REDIRECT_URI"
val SCOPE = "r_liteprofile%20r_emailaddress"
val AUTHURL = "https://www.linkedin.com/oauth/v2/authorization"
val TOKENURL = "https://www.linkedin.com/oauth/v2/accessToken"
}
Code language: Kotlin (kotlin)
In your AndroidManifest.xml file, add internet permission.
<uses-permission android:name="android.permission.INTERNET" />
Code language: HTML, XML (xml)
In your Kotlin class of your Activity (In this example is the MainActivity), add a listener to your button, and create a Dialog with a WebView inside.
lateinit var linkedinAuthURLFull: String
lateinit var linkedIndialog: Dialog
lateinit var linkedinCode: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val state = "linkedin" + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
linkedinAuthURLFull =
LinkedInConstants.AUTHURL + "?response_type=code&client_id=" + LinkedInConstants.CLIENT_ID + "&scope=" + LinkedInConstants.SCOPE + "&state=" + state + "&redirect_uri=" + LinkedInConstants.REDIRECT_URI
linkedin_login_btn.setOnClickListener {
setupLinkedinWebviewDialog(linkedinAuthURLFull)
}
}
// Show LinkedIn login page in a dialog
@SuppressLint("SetJavaScriptEnabled")
fun setupLinkedinWebviewDialog(url: String) {
linkedIndialog = Dialog(this)
val webView = WebView(this)
webView.isVerticalScrollBarEnabled = false
webView.isHorizontalScrollBarEnabled = false
webView.webViewClient = LinkedInWebViewClient()
webView.settings.javaScriptEnabled = true
webView.loadUrl(url)
linkedIndialog.setContentView(webView)
linkedIndialog.show()
}
Code language: Kotlin (kotlin)
This WebView uses a custom WebViewClient, named LinkedInWebViewClient.
This helps to ‘catch’ the URL with the authorization code after the user has given access to your app.
// A client to know about WebView navigations
// For API 21 and above
@Suppress("OverridingDeprecatedMember")
inner class LinkedInWebViewClient : WebViewClient() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
if (request?.url.toString().startsWith(LinkedInConstants.REDIRECT_URI)) {
handleUrl(request?.url.toString())
// Close the dialog after getting the authorization code
if (request?.url.toString().contains("?code=")) {
linkedIndialog.dismiss()
}
return true
}
return false
}
// For API 19 and below
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
if (url.startsWith(LinkedInConstants.REDIRECT_URI)) {
handleUrl(url)
// Close the dialog after getting the authorization code
if (url.contains("?code=")) {
linkedIndialog.dismiss()
}
return true
}
return false
}
// Check webview url for access token code or error
private fun handleUrl(url: String) {
val uri = Uri.parse(url)
if (url.contains("code")) {
linkedinCode = uri.getQueryParameter("code") ?: ""
linkedInRequestForAccessToken()
} else if (url.contains("error")) {
val error = uri.getQueryParameter("error") ?: ""
Log.e("Error: ", error)
}
}
}
Code language: Kotlin (kotlin)
After getting the authorization code, you need to exchange it for an access token by making an HTTP POST request to https://www.linkedin.com/oauth/v2/accessToken
fun linkedInRequestForAccessToken() {
GlobalScope.launch(Dispatchers.Default) {
val grantType = "authorization_code"
val postParams =
"grant_type=" + grantType + "&code=" + linkedinCode + "&redirect_uri=" + LinkedInConstants.REDIRECT_URI + "&client_id=" + LinkedInConstants.CLIENT_ID + "&client_secret=" + LinkedInConstants.CLIENT_SECRET
val url = URL(LinkedInConstants.TOKENURL)
val httpsURLConnection = withContext(Dispatchers.IO) {url.openConnection() as HttpsURLConnection}
httpsURLConnection.requestMethod = "POST"
httpsURLConnection.setRequestProperty(
"Content-Type",
"application/x-www-form-urlencoded"
)
httpsURLConnection.doInput = true
httpsURLConnection.doOutput = true
val outputStreamWriter = OutputStreamWriter(httpsURLConnection.outputStream)
withContext(Dispatchers.IO) {
outputStreamWriter.write(postParams)
outputStreamWriter.flush()
}
val response = httpsURLConnection.inputStream.bufferedReader()
.use { it.readText() } // defaults to UTF-8
val jsonObject = JSONTokener(response).nextValue() as JSONObject
val accessToken = jsonObject.getString("access_token") //The access token
Log.d("accessToken is: ", accessToken)
val expiresIn = jsonObject.getInt("expires_in") //When the access token expires
Log.d("expires in: ", expiresIn.toString())
withContext(Dispatchers.Main) {
// Get user's id, first name, last name, profile pic url
fetchlinkedInUserProfile(accessToken)
}
}
}
Code language: Kotlin (kotlin)
After having the access token, you can get user’s info (Id, First Name, Last Name and Profile pic URL) and email address with two HTTP GET requests.
But before that, you need to create the Models.
Create a new Kotlin file in your project with a name LinkedInProfileModel and paste the following inside.
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class LinkedInProfileModel (
val firstName: StName,
val lastName: StName,
val profilePicture: ProfilePicture,
val id: String
)
@Serializable
data class StName (
val localized: Localized
)
@Serializable
data class Localized (
@SerialName("en_US")
val enUS: String
)
@Serializable
data class ProfilePicture (
@SerialName("displayImage~")
val displayImage: DisplayImage
)
@Serializable
data class DisplayImage (
val elements: List<Element>
)
@Serializable
data class Element (
val identifiers: List<Identifier>
)
@Serializable
data class Identifier (
val identifier: String
)
Code language: Kotlin (kotlin)
Do the same for the email, giving the name LinkedInEmailModel.
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class LinkedInEmailModel(
val elements: List<ElementEmail>
)
@Serializable
data class ElementEmail(
@SerialName("handle~")
val elementHandle: Handle,
val handle: String
)
@Serializable
data class Handle(
val emailAddress: String
)
Code language: Kotlin (kotlin)
Now, get the User’s info
fun fetchlinkedInUserProfile(token: String) {
GlobalScope.launch(Dispatchers.Default) {
val tokenURLFull =
"https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))&oauth2_access_token=$token"
val url = URL(tokenURLFull)
val httpsURLConnection = withContext(Dispatchers.IO) {url.openConnection() as HttpsURLConnection}
httpsURLConnection.requestMethod = "GET"
httpsURLConnection.doInput = true
httpsURLConnection.doOutput = false
val response = httpsURLConnection.inputStream.bufferedReader()
.use { it.readText() } // defaults to UTF-8
val linkedInProfileModel =
Json.nonstrict.parse(LinkedInProfileModel.serializer(), response)
withContext(Dispatchers.Main) {
Log.d("LinkedIn Access Token: ", token)
// LinkedIn Id
val linkedinId = linkedInProfileModel.id
Log.d("LinkedIn Id: ", linkedinId)
// LinkedIn First Name
val linkedinFirstName = linkedInProfileModel.firstName.localized.enUS
Log.d("LinkedIn First Name: ", linkedinFirstName)
// LinkedIn Last Name
val linkedinLastName = linkedInProfileModel.lastName.localized.enUS
Log.d("LinkedIn Last Name: ", linkedinLastName)
// LinkedIn Profile Picture URL
/*
Change row of the 'elements' array to get diffrent size of the profile pic
elements[0] = 100x100
elements[1] = 200x200
elements[2] = 400x400
elements[3] = 800x800
*/
val linkedinProfilePic =
linkedInProfileModel.profilePicture.displayImage.elements.get(2)
.identifiers.get(0).identifier
Log.d("LinkedIn Profile URL: ", linkedinProfilePic)
// Get user's email address
fetchLinkedInEmailAddress(token)
}
}
}
Code language: Kotlin (kotlin)
And the user’s email address
fun fetchLinkedInEmailAddress(token: String) {
val tokenURLFull =
"https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))&oauth2_access_token=$token"
GlobalScope.launch(Dispatchers.Default) {
val url = URL(tokenURLFull)
val httpsURLConnection = withContext(Dispatchers.IO) {url.openConnection() as HttpsURLConnection}
httpsURLConnection.requestMethod = "GET"
httpsURLConnection.doInput = true
httpsURLConnection.doOutput = false
val response = httpsURLConnection.inputStream.bufferedReader()
.use { it.readText() } // defaults to UTF-8
val linkedInProfileModel =
Json.nonstrict.parse(LinkedInEmailModel.serializer(), response)
withContext(Dispatchers.Main) {
// LinkedIn Email
val linkedinEmail = linkedInProfileModel.elements.get(0).elementHandle.emailAddress
Log.d("LinkedIn Email: ", linkedinEmail)
}
}
}
Code language: Kotlin (kotlin)
Done!!
You can find the final project here
If you have any questions, please feel free to leave a comment below