LinkedIn login no longer works? Need to upgr to LinkedIn API v2?

Hi Akkie and others,

I’m getting this error when configuring LinkedIn login:

Stack trace:
com.mohiva.play.silhouette.impl.exceptions.ProfileRetrievalException:
 [Silhouette][linkedin] error retrieving profile information. Error code: 0,
 message: Some(This resource is no longer available under v1 APIs),
 requestId: Some(DK7OAMFB7Q), status: Some(410), timestamp: Some(1554542537998)
	at com.mohiva.play.silhouette.impl.providers.oauth2.BaseLinkedInProvider.$anonfun$buildProfile$1(LinkedInProvider.scala:71)
	at scala.concurrent.Future.$anonfun$flatMap$1(Future.scala:307)
	at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41)
	at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64)
	at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55)
	at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:91)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
	at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:85)
	at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:91)
	at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:40)
	at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(ForkJoinExecutorConfigurator.scala:44)
	at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
	at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
	at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
	at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

And when visiting the LinkedIn page for my app, there’s a message about API v1 no longer being supported.

Here’s another OSS project that upgraded from LinkedIn API v1 to v2, maybe the similar changes are needed in Silhouette?

Looking in the source, apparently v1 is indeed in use, in Silhouette, here: https://github.com/mohiva/play-silhouette/blob/f4a57cb427025a6485d91e551eb3d01ab5729e16/silhouette/app/com/mohiva/play/silhouette/impl/providers/oauth2/LinkedInProvider.scala#L154,
note: /v1/people/... instead of /v2/me/....


  /**
   * The LinkedIn constants.
   */
  val ID = "linkedin"
  val API = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,formatted-name,picture-url,email-address)?format=json&oauth2_access_token=%s"

Followup: Yes indeed, it’s API v1 now being deprecated that’s the problem. It’s this request:

trait BaseLinkedInProvider extends OAuth2Provider {
  ...
  override protected def buildProfile(authInfo: OAuth2Info): Future[Profile] = {
    httpLayer.url(urls("api").format(authInfo.accessToken)).get().flatMap { response =>

that LinkedIn replies error to, with this JSON reply:

{
  "errorCode": 0,
  "message": "This resource is no longer available under v1 APIs",
  "requestId":".....",
  "status":410,
  "timestamp":1554566171081
}

After having change the URL to https://api.linkedin.com/v2/me?fields=id,firstName,lastName&oauth2_access_token=%s (and removed the profile pic url field — that just results in a permission error), I’m getting back this LinkedIn profile JSON:

{
  "lastName":{
    "localized":{"en_US":"Mylastname"},
    "preferredLocale":{"country":"US","language":"en"}},
  "firstName":{
    "localized":{"en_US":"Magnus"},
    "preferredLocale":  {"country":"US","language":"en"}},
  "id":"UAgk....."
}

Also the LinkedInProfileParser doesn’t work with the new API v2 (everything except for the id becomes null). Here I wrote a provider and parser that works with API v2:

// Silhouette doesn't yet support LinkedIn API v2 so using this class,
// temporarily.
// Also need this OAuth2Settings setting:
// apiURL = Some("https://api.linkedin.com/v2/me?fields=id,firstName,lastName&oauth2_access_token=%s")
class CustomLinkedInProvider(
  protected val httpLayer: HTTPLayer,
  protected val stateHandler: SocialStateHandler,
  val settings: OAuth2Settings)
  extends BaseLinkedInProvider with CommonSocialProfileBuilder {

  override type Self = CustomLinkedInProvider

  override val profileParser = new LinkedInProfileParserApiV2

  override def withSettings(f: (Settings) => Settings) =
    new CustomLinkedInProvider(httpLayer, stateHandler, f(settings))
}


class LinkedInProfileParserApiV2 extends SocialProfileParser[JsValue, CommonSocialProfile, OAuth2Info] {

  override def parse(json: JsValue, authInfo: OAuth2Info): Future[CommonSocialProfile] =
      Future.successful {
    // The json from API v2 is like:
    // {
    //   "lastName":{
    //     "localized":{"en_US":"MyLastName"},
    //     "preferredLocale":{"country":"US","language":"en"}},
    //   "firstName":{
    //     "localized":{"en_US":"MyFirstName"},
    //     "preferredLocale":  {"country":"US","language":"en"}},
    //   "id":"........"   // "random" chars
    // }

    val userID = (json \ "id").as[String]
    def readName(fieldName: String): Option[String] = {
      (json \ fieldName).asOpt[JsObject] flatMap { jsObj =>
        (jsObj \ "localized").asOpt[JsObject] flatMap { jsObj =>
          jsObj.fields.headOption.map(_._2) flatMap { nameInAnyLocale =>
            nameInAnyLocale match {
              case jsString: JsString => Some(jsString.value)
              case _ => None
            }
          }
        }
      }
    }
    val firstName = readName("firstName")
    val lastName = readName("lastName")
    // These no longer included, in LinkedIn's API v2:
    val fullName = (json \ "formattedName").asOpt[String]
    val avatarURL = (json \ "pictureUrl").asOpt[String]
    val email = (json \ "emailAddress").asOpt[String]

    CommonSocialProfile(
      loginInfo = LoginInfo(LinkedInProvider.ID, userID),
      firstName = firstName,
      lastName = lastName,
      fullName = fullName,
      avatarURL = avatarURL,
      email = email)
  }
}

Ok so everything works right now in my case, … and LinkedIn login nowadays provides neither email address nor any link to the actual LinkedIn profile.

Turns out one can retrieve the email addr via a 2nd request:

val emailRequestUrl =
 "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))" +
  "&oauth2_access_token=" + oauth2AuthInfo.accessToken
val linkedinRequest: ws.WSRequest = wsClient.url(emailRequestUrl)

However, other fields / full / basic profile: https://docs.microsoft.com/en-us/linkedin/shared/references/v2/profile#profile-fields-available-with-linkedin-partner-programs

only available to applications that have applied and been approved for a LinkedIn Partner Program:

1 Like

@kajmagnus Would be awesome if you could provide a PR.

Hi @akkie, I can show you the code, see below. Maybe I can liceense the LinkedIn login related code to you under CC0, if you’d like to copy-paste some parts. (Otherwise I work already 60 h / week and, since I got this working already for me, I feel there’s too little time for me available to prepare an okay looking PR.)

Here’s the code: (from this line, and the rest of the file)