Accueil Le blog ebiznext
Signin Linkedin avec Scala (OAuth2)

Une des premières tâches que j’ai eu à faire en tant que développeur Scala a été d’implémenter un web service pour l’inscription via LinkedIn.

Petit aparté pour le déploiement: j’ai eu besoin de la librairie suivante pour que ca marche sur le serveur d’intégration (à ajouter dans le build.sbt):

libraryDependencies ++= Seq(
"ch.megard" %% "akka-http-cors" % "0.1.10"
)

En effet, ca ne marchait pas lors du déploiement sur le serveur à cause d’un problème de CORS (Cross-Origin Ressource Sharing): le front était déployé sur un port différent de celui du back.

En gros, il existe des requêtes HTTP de type Cross-site, permettant d’obtenir des ressources depuis différents domaines: une ressource sur le serveur A qui a besoin d’une ressource sur le serveur B (plus de documentation ici).

Ainsi, cette librairie fournie une fonction cors() qui permet d’ajouter un header « Access-Control-Allow-Origin » avec la valeur voulue (par défaut, « * »).

Place au code:

pathPrefix("linkedin")
  import ch.megard.akka.http.cors.CorsDirectives._
  import com.monsite.config.Implicits._
  import akka.http.scaladsl.model.headers

  cors() {

    def service: OAuth20Service = new ServiceBuilder()
      .apiKey(Settings.LinkedIn.ConsumerKey)
      .apiSecret(Settings.LinkedIn.ConsumerSecret)
      .scope(Settings.LinkedIn.Scope)
      .callback(Settings.LinkedIn.Callback)
      .state(Settings.LinkedIn.State)
      .build(LinkedInApi20.instance())

    path("signin") {
      get {
        val authURL = service.getAuthorizationUrl()
        redirect(authURL, StatusCodes.TemporaryRedirect)
      }
    } ~
      path("callback") {
        get {
          parameters('code, 'state) { (code, state) ⇒
            if (state != Settings.LinkedIn.State) throw new ForbiddenMonsiteException("the state is not valid: possible attack")

            val accessToken: OAuth2AccessToken = service.getAccessToken(code)

            val request: OAuthRequest = new OAuthRequest(Verb.GET, Settings.LinkedIn.ResourceUrl, service.getConfig)
            service.signRequest(accessToken, request)
            val response: Response = request.send

            if (response.getCode == StatusCodes.OK.intValue) {
              complete(response.getBody.trim.replaceAll("[\n]", ""))
            } else {
              complete(int2StatusCode(response.getCode))
            }
          }
        }
      }
  }

On commence par initialiser le service, avec certains paramètres:

  • ConsumerKey: ou client_id. C’est l’identifiant de notre application lors de l’enregistrement sur LinkedIn
  • ConsumerSecret: ou client_secret. C’est aussi un token fournit par LinkedIn, il ne doit être connu de personne (sauf des devs)

Oauth Values

  • Scope: les ressources auxquelles on souhaite accéder (nom, prénom, …)
  • Callback: l’URL de redirection après l’authentification (dans notre cas: http://localhost:9000/linkedin/callback). Elle doit être ajoutée aux URL de redirection permises dans la configuration de l’application sur LinkedIn.
    Attention: il ne faut pas oublier le http:// ou https:// devant l’URI de callback, sinon ca ne marchera pas.
  • State: une String difficilement devinable, stockées sur notre serveur

Bref, tous ces paramètres vont permettre l’obtention tout d’abord de l’URL d’autorisation (authUrl). Elle ressemble à ca:

https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=ConsumerKey&redirect_uri=/linkedin/callback&state=difficileADeviner&scope=r_basicprofile%20r_emailadress

Une fois qu’on l’a, on est redirigé vers Callback, avec en paramètre un code et un state renvoyés par le serveur d’autorisation LinkedIn:

/linkedin/callback?code=codeDonneParLinkedin&state=difficileADeviner

On doit vérifier que le state renvoyé par LinkedIn est bien égal au State stocké sur le serveur, sinon cela voudrait dire qu’on est potentiellement victime d’une attaque CSRF. C’est fait pour assurer que c’est bien notre utilisateur qui donne l’autorisation, et pas un script malicieux.

Le code, quant à lui, est échanger avec un token d’accès (accessToken). Cet accessToken va permettre ensuite de signer la requête. Dans notre cas, la requête est un GET sur les informations du profil LinkedIn (ResourceUrl):

https://api.linkedin.com/v1/people/~:(id,first-name,last-name,public-profile-url,picture-urls?format=json

Ici, je récupère les infos de bases: nom, prénom email… et les infos sur job actuel (positions), récupérés au format json. C’est un exemple, mais on peut faire ce qu’on veut !