👤 Implémenter un système d'authentification via LinkedIn avec React & NestJS
Tags: javascript, react, nestjs, oauth2
Published: 2023-10-17 07:05:38

L'implémentation d'un système d'authentification via **LinkedIn** peut présenter de nombreux avantages pour votre plateforme.

Dans un premier temps, il permet de vous prémunir contre les bots & les logiciels automatisés 🤖. En effet, la création d'un compte **LinkedIn** requiert le renseignement d'un numéro de téléphone. On peut dès lors considérer qu'un utilisateur authentifié via **LinkedIn** auprès de votre plateforme est une personne réelle 🙍‍♂️.

Mais l'authentification via **LinkedIn** permet surtout l'obtention de données ayant trait à l'identité réelle de l'utilisateur se connectant à votre plateforme. Il s'agit donc d'une solution pertinente pour obtenir le profil professionnel d'un internaute.

Je vous propose, dans cet article, de découvrir comment implémenter une solution d'authentification via **LinkedIn** en créant : une application [React](https://fr.legacy.reactjs.org/) ainsi qu'une API [NestJS](https://nestjs.com/).

**Source Github 👇**
[https://github.com/MessageGit/oauth-sign-in-with-linkedin](https://github.com/MessageGit/oauth-sign-in-with-linkedin). 

---

## ⚠️ Pré-requis

Pour implémenter un système d'authentification via **LinkedIn**, vous devez tout d'abord :

- **Être détenteur d'un compte utilisateur LinkedIn valide**
_(Si vous n'avez pas de compte LinkedIn, créez-en un en [cliquant ici](https://www.linkedin.com/signup?_l=fr))._

## 

- **Être propriétaire d'une page professionnelle LinkedIn (à l'effigie de votre plateforme)**
_(Si vous n'avez pas de page professionnelle, créez-en une en [cliquant ici](https://www.linkedin.com/company/setup/new/))_.

## 

- **Créer une application LinkedIn (nécessite de satisfaire les deux pré-requis précédents)**.
_Pour cela, rendez-vous sur la [page de création d'une nouvelle application](https://www.linkedin.com/developers/apps/new) puis complétez le formulaire en renseignant les informations suivantes : nom de l'application, URL de votre page professionnelle, upload d'un logo, acceptation des CGU_.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/buv6u71jw5kbzz4pu6v0.png)

[...] Votre nouvelle application, une fois créée, devrait apparaître dans le tabeau de bord « développeurs » de votre compte LinkedIn, [cliquez-ici](https://www.linkedin.com/developers/apps) pour y accéder.

Il ne vous reste plus qu'à cliquer sur votre nouvelle application pour accéder à son panel de gestion 🎉
_(Voir exemple ci-dessous)_

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t58u1kuambur3txjocyh.png)

---

## ⚙️ Configuration préalable via LinkedIn Developers

Le fonctionnement de la connexion OAuth 2 via **LinkedIn** ne diffère pas grandement des autres services qui l'intègrent (comme par exemple la connexion via **Google**/**Facebook**). 

Un système de « scopes » est présent et permet à l'utilisateur de réguler l'accord des permissions léguées à l'application, point positif en faveur de l'aspect sécuritaire de la plateforme.

> _ℹ️ Si la présence de « scopes » semble être standard, rappelons tout de même que certain(e)s réseaux sociaux/applications n'intègrent pas ce système précautionneux voué à limiter l'exploitation des ressources par une application tierce. C'est le cas de la messagerie sécurisée **Telegram** notamment._

Par défaut, votre application (fraîchement créée dans le chapitre précédent) n'intègre pas la connexion via **LinkedIn**, il faudra l'activer manuellement en vous rendant dans la rubrique « **Products** » de votre application (toujours dans le panel de gestion de votre application **LinkedIn developers**). 

Cliquez ensuite sur le bouton « **Request access** » lié au produit nommé « **Sign In with LinkedIn** ».

Une fois le produit activé, deux nouveaux scopes feront leur apparition dans la section « **_OAuth 2.0 scopes_** » de la rubrique « **_Auth_** » : `r_emailaddress` ainsi que `r_liteprofile`.

C'est dans cette même rubrique que vont se manifester de nombreuses informations essentielles à l'implémentation de la solution d'authentification via LinkedIn. 

La section « **_Application credentials_** » va répertorier le **Client ID** & le **Client Secret**, deux informations primordiales permettant d'identifier votre application lorsque vos futurs utilisateurs accéderont au portail d'authentification via **LinkedIn**.

La section « **OAuth 2.0 settings** » quant à elle va permettre d'autoriser des URLs de redirection pour votre application. Une fois l'authentification de vos utilisateurs validée, **LinkedIn** les redirigera vers l'URL demandée (sous réserve que cette dernière soit autorisée dans cette même section). 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5mfw4a0otv76ah5rjf3.png)

Une fois la configuration de l'application via le panel **LinkedIn developers** terminée, il est temps de passer à l'implémentation technique du système d'authentification !

---

## 📸 Développement de la web application React

Avant de débuter l'initialisation de notre application **React**, il me semble judicieux de résumer les opérations à entreprendre via l'application front pour procéder à l'authentification d'un utilisateur **LinkedIn**.

### 👤 Aspect procédural de l'authentification client

**1ère étape de l'authentification 👇**
> Rediriger le client vers le portail d'authentification **LinkedIn** en y apportant des informations telles que : l'URI de redirection souhaité (dans l'éventualité où l'authentification de notre client est un succès), les scopes requis par notre application, l'identifiant même de notre application, etc..

**2ème étape de l'authentification 👇**
> Partons du principe que notre client soit parvenu à se connecter à **LinkedIn** : ce dernier est dès lors redirigé à l'URI que nous avons fournit à LinkedIn lorsque nous l'avons renvoyé vers le portail d'authentification. Sa redirection vers notre URI personnalisée est accompagnée d'un [query params](https://fr.wikipedia.org/wiki/Cha%C3%AEne_de_requ%C3%AAte) « code », il s'agit là d'un « authorization code » que nous enverrons à notre API pour obtenir un droit d'exploitation des ressources de notre client (sous réserve que l'exploitation soit faite par notre API exclusivement).

[...] Nous verrons dans le chapitre suivant comment échanger le code d'autorisation, résultant de la connexion de notre client à **LinkedIn** puis comment obtenir l'autorisation d'exploitation des ressources **LinkedIn** de notre client.

### 🌐 Initialisation de la web application React

Maintenant que nous avons étudié l'aspect procédural de l'authentification côté client, tâchons de l'implémenter via une application **React**.

Commençons par initialiser une nouvelle application **React** en saisissant la commande suivante dans un terminal : `npx create-react-app linkedin-oauth-app`.

Nous idéaliserons une architecture/arborescence d'application très minimaliste et assez peu élaborée telle que la suivante :

```
    > linkedin-oauth-app
        > node_modules
        > public
        > src
            > api
            > assets
            App.tsx
            index.css
            index.js
        .env
        .env.example
        .gitignore
        package.json
        README.md
        tsconfig.json
```

### 🗒️ Déclaration des variables d'environnement

Déclarons sans plus attendre les variables d'environnement indispensables au fonctionnement de notre système d'authentification, et plus globalement, au fonctionnement de notre application : 

```
    # MAIN
    REACT_APP_API_URL=
    
    # LINKEDIN
    REACT_APP_LINKEDIN_CLIENT_ID=
    REACT_APP_LINKEDIN_SCOPES=
    REACT_APP_LINKEDIN_REDIRECT_URI=
```

[...] Notre fichier `.env` _(registre des variables d'environnement)_ sera identique au fichier `.env.example` _(ci-dessus)_, à la seule différence que celui-ci intégrera des valeurs. 

**🤔 Pourquoi un fichier `.env` et `.env.example` ?**
> ℹ️ Le principe de cette mesure sécuritaire est simple. Pour celles & ceux qui n'en ont pas connaissance : le fichier `.env`, référencé dans notre fichier `.gitignore`, peut inclure une infinité d'informations sensibles selon la complexité de l'application, il n'a donc pas vocation à intégrer notre répertoire Github.
Notre fichier `.env.example` est, quant à lui, le modèle des variables d'environnement. Il sera respectivement publié sur notre répertoire Github à titre d'exemple et n'intégrera donc aucune valeur.

Vous pouvez d'ores et déjà incrémenter la clé `REACT_APP_LINKEDIN_SCOPES` par la valeur suivante : `r_liteprofile,r_emailaddress`. Cette dernière correspond à la solution « **Sign-In with LinkedIn** ».

Vous retrouverez la valeur de la clé `REACT_APP_LINKEDIN_CLIENT_ID` et `REACT_APP_LINKEDIN_REDIRECT_URI` dans le panel [LinkedIn Developers](https://www.linkedin.com/developers/apps) de votre application, rubrique « **Auth** ».

La clé `REACT_APP_API_URL` correspondra quant à elle à l'URL de votre API, mais nous ne l'avons pas encore créée, nous y reviendrons donc ultérieurement 😜

Dans notre fichier `App.tsx`, nous importerons les variables d'environnement correspondantes à **LinkedIn** sous forme d'object Javascript : 

```
import React from 'react';

const App = () => {
    /* Load LinkedIn credentials from dotenv */
    const LINKEDIN_ENV = {
        CLIENT_ID: process.env['REACT_APP_LINKEDIN_CLIENT_ID'],
        SCOPES: process.env['REACT_APP_LINKEDIN_SCOPES'],
        REDIRECT_URI: process.env['REACT_APP_LINKEDIN_REDIRECT_URI'],
    }
    
    // (...)
}
```

### 📤 Implémentation technique et redirection client

A titre d'exemple, un simple bouton « **_Se connecter via LinkedIn_** » permettant la redirection du client au portail d'authentification LinkedIn fera l'affaire. Inutile de configurer plusieurs pages ou d'aborder un développement plus complexe, l'enjeu de cet exemple étant de saisir le fonctionnement de l'authentification.

Choisissez un jolie [logo LinkedIn](https://upload.wikimedia.org/wikipedia/commons/thumb/8/81/LinkedIn_icon.svg/1200px-LinkedIn_icon.svg.png), il intégrera notre bouton personnalisé « **_Se connecter via LinkedIn_** » et nous permettra de rediriger notre client à l'adresse suivante :

`https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${LINKEDIN_ENV.CLIENT_ID}&scope=${LINKEDIN_ENV.SCOPES}&redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}`

Décomposons cette adresse pour l'analyser & la comprendre :

`https://www.linkedin.com/oauth/v2/authorization` correspond à l'URL du portail d'authentification.

`?response_type=code` précise que nous attendons un code d'authorization.

`&client_id=${LINKEDIN_ENV.CLIENT_ID}` permet d'identifier notre application auprès des serveurs **LinkedIn**.

`&scope=${LINKEDIN_ENV.SCOPES}` permet de spécifier les scopes requis par notre application.

`&redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}` permet de préciser l'URI à laquelle **LinkedIn** doit rediriger le client connecté. 

```
import React from 'react';

import LinkedInIcon from './assets/linkedin_icon.png';

const App = () => {

  /* Load LinkedIn credentials from dotenv */
  const LINKEDIN_ENV = {
    CLIENT_ID: process.env['REACT_APP_LINKEDIN_CLIENT_ID'],
    SCOPES: process.env['REACT_APP_LINKEDIN_SCOPES'],
    REDIRECT_URI: process.env['REACT_APP_LINKEDIN_REDIRECT_URI'],
  }

  /* Handle the client redirection to LinkedIn authentication portal */
  const onSignInLinkedIn = () => {
    window.location.replace(`https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${LINKEDIN_ENV.CLIENT_ID}&scope=${LINKEDIN_ENV.SCOPES}&redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}`);
  }

  return (
    <div className="App">

        <button className="LinkedIn_Button" onClick={onSignInLinkedIn}>
          <img src={LinkedInIcon} alt="Logo LinkedIn" className="LinkedIn_Icon" />
          <span>Se connecter via LinkedIn</span>
        </button>
          
    </div>
  );
}

export default App;
```

A ce stade, le bouton « **_Se connecter via LinkedIn_** » nous redirige vers le portail d'authentification **LinkedIn** :

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sjdzijtutbfp46gj2mhn.png)

Si une erreur survient, vérifiez que les variables d'environnement saisies correspondent bien aux credentials fournis pour votre application, que le produit « **_Sign-In with LinkedIn_** » soit bien activé, que les scopes spécifiés soient valides & que l'URI de redirection transmise via l'URI du portail d'authentification soit autorisée via le panel de gestion de votre application depuis [LinkedIn developers](https://www.linkedin.com/developers/apps).

> _ℹ️ N'oubliez pas de re-démarrer votre application React une fois les variables d'environnement modifiées. Celles-ci ne se synchronisent pas automatiquement lors de leur modification._

Si la configuration de votre application est correcte & que le formulaire d'authentification s'affiche sans problèmes, essayez alors de vous connecter :

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rkbc1lle7uvjfir6rrul.png)

L'application requiert dès lors, comme prévu, les scopes préalablement configurés tels que :

- **Les informations basiques du profil utilisateur** _(= Utiliser votre nom et photo)_
- **L'adresse e-mail de l'utilisateur** _(= Utiliser l'adresse e-mail principale associée à votre compte LinkedIn)_

[...] Il ne vous reste plus qu'à autoriser l'exploitation de ces scopes par l'application pour être redirigé à l'URI transmise au préalable, accompagné d'un « **_authorization code_** » qui se présente sous forme de [query params](https://en.wikipedia.org/wiki/Query_string), tel que :

`?code=AQVumAQMn0w5BWznTRD0nnMRDA_fBYNzBBHe3yxmOxMQvm9nW0yw8b7QrUNffvuEkSYlYOXU26FGL7vvm7W0-oe8pqyDFXj5w6JxW3A0bhCLPIXQsYMZfP_9QwRrJDPDdjZIYhPILu0xfGEWaM44Isy6FxYTtzMLf1HxkXdqGUVoJjR_TGPEpC-c3Dcj-oqJue6I7NgsurIZltYFuQw`

> _ℹ️ Notez qu'en procédant à cette authentification, vous léguez des permissions d'exploitation des ressources liées à votre profil LinkedIn, à une application tierce. Pour révoquer ces permissions, vous pouvez vous rendre dans les préférences de votre profil LinkedIn puis dans : **Confidentialité des données > Autres applications > Services autorisés > Nom de l'application > Supprimer**_.

### 📥 Récupération du code d'autorisation et transmission à l'API
Actuellement, nous avons un bouton permettant de rediriger le client vers le portail d'authentification LinkedIn. Si la connexion d'un utilisateur est valide, il est redirigée vers notre application React, avec un code d'autorisation.

L'enjeu est maintenant de récupérer ce code d'autorisation pour le transmettre à notre API.

Toujours dans notre fichier `App.tsx`, tâchons sans plus tarder de le récupérer : 

```
    /* Check if a Linkedin authorization code is provided */
    const authorizationCode = new URLSearchParams(window.location.search).get('code');
```

La constante `authorizationCode` intègre désormais le code d'autorisation retourné par LinkedIn. 

A défaut, cette dernière sera respectivement `undefined`. Il ne nous reste plus qu'à traiter ce code dès lorsque notre `App.tsx` est initialisé, dans l'hypothèse que ce dernier ne soit pas `undefined`. Nous utiliserons le hook **React** `useEffect` pour se faire :  

```
import React, {useEffect} from 'react';

import LinkedInIcon from './assets/linkedin_icon.png';

const App = () => {
    
    /* Load LinkedIn credentials from dotenv */
    const LINKEDIN_ENV = {
        CLIENT_ID: process.env['REACT_APP_LINKEDIN_CLIENT_ID'],
        SCOPES: process.env['REACT_APP_LINKEDIN_SCOPES'],
        REDIRECT_URI: process.env['REACT_APP_LINKEDIN_REDIRECT_URI'],
    }
    
    /* Handle the client redirection to LinkedIn authentication portal */
    const onSignInLinkedIn = () => {
        window.location.replace(`https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${LINKEDIN_ENV.CLIENT_ID}&scope=${LINKEDIN_ENV.SCOPES}&redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}`);
    }
  
    /* Check if a Linkedin authorization code is provided */
    const authorizationCode = new URLSearchParams(window.location.search).get('code');
    
    useEffect(() => {
        if (authorizationCode) {
          // TODO: Send authorization code to server here
        }
    }, [authorizationCode]);
    
    return (
        <div className="App">
    
            <button className="LinkedIn_Button" onClick={onSignInLinkedIn}>
              <img src={LinkedInIcon} alt="Logo LinkedIn" className="LinkedIn_Icon" />
              <span>Se connecter via LinkedIn</span>
            </button>
              
        </div>
    );
    
}

export default App;
```

Dans notre hook `useEffect`, le code d'autorisation peut enfin être exploité (sous réserve qu'il soit définit).

Installons à présent la librairie [axios](https://www.npmjs.com/package/axios), librairie très réputée permettant d'émettre des requêtes. Nous pourrions également utiliser n'importe quelle autre librairie de ce type, voir utiliser simplement l'API [fetch](https://developer.mozilla.org/fr/docs/Web/API/fetch) intégrée nativement dans Javascript et permettant de d'exploiter des requêtes AJAX.

Une fois la librairie installée, nous créerons une instance **axios** qui se basera sur la variable d'environnement `REACT_APP_API_URL` préalablement déclarée : 

```
    import axios from 'axios';

    const baseURL = process.env['REACT_APP_API_URL'];
    export default axios.create({ baseURL, withCredentials: true });
```

Une fois que notre API sera créée et exposée, nous n'aurons qu'à incrémenter notre variable d'environnement `REACT_APP_API_URL` par l'URL de notre API et utiliser l'instance déclarée ci-dessus pour échanger avec cette dernière.

Revenons dans notre fichier `App.tsx`, importons notre instance **axios** puis requêtons le futur end-point de notre API auquel nous enverrons le code d'autorisation reçu :

```
import React, {useEffect} from 'react';
import API from './api/api'; // import of our axios instance

import LinkedInIcon from './assets/linkedin_icon.png';

const App = () => {
    
    /* Load LinkedIn credentials from dotenv */
    const LINKEDIN_ENV = {
        CLIENT_ID: process.env['REACT_APP_LINKEDIN_CLIENT_ID'],
        SCOPES: process.env['REACT_APP_LINKEDIN_SCOPES'],
        REDIRECT_URI: process.env['REACT_APP_LINKEDIN_REDIRECT_URI'],
    }
    
    /* Handle the client redirection to LinkedIn authentication portal */
    const onSignInLinkedIn = () => {
        window.location.replace(`https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${LINKEDIN_ENV.CLIENT_ID}&scope=${LINKEDIN_ENV.SCOPES}&redirect_uri=${LINKEDIN_ENV.REDIRECT_URI}`);
    }
  
    /* Check if a Linkedin authorization code is provided */
    const authorizationCode = new URLSearchParams(window.location.search).get('code');
    
    useEffect(() => {
        if (authorizationCode) {
            API.post('linkedin/auth/login', { authorization_code: authorizationCode })
                .then((res) => console.log('Request received', res))
                .catch((err) => console.log('An error occured', err));
        }
    }, [authorizationCode]);
    
    return (
        <div className="App">
    
            <button className="LinkedIn_Button" onClick={onSignInLinkedIn}>
              <img src={LinkedInIcon} alt="Logo LinkedIn" className="LinkedIn_Icon" />
              <span>Se connecter via LinkedIn</span>
            </button>
              
        </div>
    );
    
}

export default App;
```

Dans l'implémentation de notre requête ci-dessus, nous spéculons à la fois sur :

- l'end-point cible de notre API (nous avons choisi `linkedin/auth/login`)
- la dénomination de la clé passée dans le `body` de notre requête (nous avons choisi `authorization_code`)

**<u>📌 Récapitulons</u>** :

Si l'on part du principe que l'URL de notre futur API est `http://localhost:8080/` et que la variable d'environnement `REACT_APP_API_URL` est respectivement incrémentée par cette valeur/url, notre **application React** émettra ici une requête `POST` à l'adresse `http://localhost:8080/linkedin/auth/login` avec une clé `authorization_code` en `body` incluant la valeur du code d'autorisation retourné par **LinkedIn** pour notre utilisateur.

---

## 🏗️ Développement de l'API avec NestJS

Maintenant que notre application web est fonctionnelle et capable de transmettre le code d'autorisation client à l'API, conçevoir l'API il va falloir pour exploiter les données de l'utilisateur LinkedIn en toute sécurité.

Nous utiliserons NestJS, un framework Typescript basé sur Node.js mais vous pouvez évidement choisir un framework ou une technologie plus à votre aise selon vos habitudes. 

Ci-dessous, nous verrons comment entreprendre l'implémentation via NestJS.

### 🧰 Initialisation du serveur & configuration préalable

Une fois NestJS installé, initialisez un nouveau projet via la commande : `nest new linkedin-oauth-api`.

Encore une fois, nous privilégierons une arborescence de projet simpliste/minimaliste et une architecture en microservices, NestJS étant idéal pour ce type d'architecture. 

```
    > linkedin-oauth-api
        > dist
        > node_modules
        > src
            > config
            > microservices
            app.module.ts
            main.ts
        .env
        .env.example
        .eslintrc.js
        .gitignore
        .prettierrc
        nest-cli.json
        package.json
        tsconfig.build.json
        tsconfig.json
```

Au même titre que notre application web, configurons les fichiers relatifs aux variables d'environnement `.env` et `.env.example` : 

```
    PORT=8080
    NODE_ENV=development
    
    LINKEDIN_API_CLIENT_ID=
    LINKEDIN_API_CLIENT_SECRET=
    LINKEDIN_API_REDIRECT_URI=
```

Il ne tient plus qu'à vous de le compléter selon les credentials fournis pour votre application LinekdIn.

> _ℹ️ Rappel : retrouvez les credentials liées à votre application via le panel [LinkedIn developers](https://www.linkedin.com/developers/apps), au sein de la rubrique « **Auth** »_.

Pour lire les variables d'environnement avec NestJS, nous aurons besoin d'installer une librairie conçue par les soins des développeurs du framework, à savoir : `@nestjs/config`, utilisez donc `npm i --save @nestjs/config` si ce n'est pas déjà fait.

Initialisez ensuite le fichier `root > src > config > config.ts` en y ajoutant le code suivant : 

```
interface LinkedInConfig {
    clientId: string;
    clientSecret: string;
    redirectUri: string;
}

interface ConfigProps {
    port: number;
    linkedIn: LinkedInConfig;
}

export const config = (): ConfigProps => ({
    port: parseInt(process.env.PORT, 10) || 8080,
    linkedIn: {
        clientId: process.env.LINKEDIN_API_CLIENT_ID,
        clientSecret: process.env.LINKEDIN_API_CLIENT_SECRET,
        redirectUri: process.env.LINKEDIN_API_REDIRECT_URI,
    }
});
```

Même si nous avons ajouté la clé `NODE_ENV` par convention, aux fichiers relatifs aux variables d'environnement, nous n'en ferons pas grand chose dans ce tutoriel.

Une fois le fichier de config configuré et les types ajoutés, il faudra dorénavant l'importer dans le fichier `app.module.ts` comme suit :

```
import { Module } from '@nestjs/common';

/* NestJS Config */
import { ConfigModule } from '@nestjs/config'; // import of our @nestjs/config lib.
import { config } from './config/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [config]
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
```

[...] Le plus haut fichier de votre projet `main.ts` devra intégrer le code suivant : 

```
import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config'; // import of our @nestjs/config lib.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.enableCors({
    origin: true,
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
    credentials: true,
  });

  const configService = app.get(ConfigService);
  const port = configService.get<number>('port'); // get port from dotenv file

  await app.listen(port, '0.0.0.0');
  console.log(`\n\n\x1b[34m🚀 Server is now available at the \x1b[37m${await app.getUrl()}\x1b[34m address!\x1b[0m`);
}

bootstrap();
```

🎉 Notre API peut maintenant être démarrée en mode développement via la commande : `npm run start:dev`.

### ⚙️ Initialisation de notre nouveau microservice

Maintenant que notre API est exposée via `http://localhost:8080/`, il nous faut créer la route permettant l'échange du code d'autorisation reçu par notre web-application contre un jeton d'accès permettant l'exploitation des ressources utilisateurs. Créons alors le microservice `linkedin`.

```
    > microservices
        > linkedin
            > api
                linkedin.api.ts // next chapter
            > controllers
                linkedin.controller.ts
            > objects
                linkedin-user-profile.object.ts // next chapter
                linkedin-user-token.object.ts // next chapter
            > services
                linkedin.service.ts
            linkedin.module.ts
```

**1️⃣ Déclaration du service linkedin**

Le fichier `src > microservices > linkedin > services > linkedin.service.ts` nous permettra d'implémenter l'intégralité des méthodes liées à LinkedIn.

```
import { Injectable } from '@nestjs/common';

@Injectable()
export class LinkedInService {
    constructor() {}

    async login(authorizationCode: string): Promise<any> {
        console.log('> Authorization code received :', authorizationCode);
        // ...
    }
}
```

**2️⃣ Déclaration du controlleur linkedin**

Le fichier `src > microservices > linkedin > controllers > linkedin.controller.ts` permettra de définir la route d'accès, gérer la réception des données fournis dans le corps de la requête et s'assurera de transmettre les informations aux méthodes de notre service préalablement déclaré.

```
import { Body, Controller, Post } from '@nestjs/common';

import { LinkedInService } from 'src/microservices/linkedin/services/linkedin.service';

@Controller('linkedin')
export class LinkedInController {
  constructor(private readonly linkedInService: LinkedInService) {}

  @Post('auth/login')
  async login(@Body('authorization_code') authorizationCode: string): Promise<any> {
      return await this.linkedInService.login(authorizationCode);
  }

}
```

**3️⃣ Déclaration du module linkedin**

Le fichier `src > microservices > linkedin > linkedin.module.ts` fera office de porte d'accès à notre controlleur puis à notre service, il assurera l'import global des fichiers préalablement déclarés et permettra d'y greffer des modules / services complémentaires, il est une base rattaché au corps de notre serveur.

```
import { Module } from '@nestjs/common';

import { LinkedInController } from 'src/microservices/linkedin/controllers/linkedin.controller';
import { LinkedInService } from 'src/microservices/linkedin/services/linkedin.service';

@Module({
  imports: [],
  controllers: [LinkedInController],
  providers: [LinkedInService],
})
export class LinkedInModule {}
```

**4️⃣ Import de notre module auprès du module global**

Notre fichier `app.module.ts` est l'un des fichiers des plus haut-niveau dans l'architecture de notre application, il est en tout cas le module parent à notre serveur. C'est auprès de ce dernier qu'il nous faudra s'assurer de l'import de notre nouveau module linkedin.

```
import { Module } from '@nestjs/common';

/* NestJS Config */
import { ConfigModule } from '@nestjs/config';
import { config } from './config/config';

/* Imported modules */
import { LinkedInModule } from './microservices/linkedin/linkedin.module'; // import of LinkedIn module

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [config]
    }),
    LinkedInModule, // import of LinkedIn module
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
```

A ce stade, la connexion **LinkedIn** via notre **web-application React** doit être capable de provoquer l'impression du code d'autorisation via notre serveur 🎉

### 🔐 Obtention d'un jeton d'accès depuis un code d'autorisation

Maintenant que nous reçevons le code d'autorisation au sein de la méthode `login(...)` de notre service **LinkedIn**, tâchons de l'échanger à **LinkedIn** contre un jeton d'accès.

Pour commencer, nous créerons le **SDK LinkedIn**. Celui-ci nous permettra de faire appel à des méthodes qui, quant à elles, se chargeront de formuler les requêtes adéquates auprès des serveurs de **LinkedIn**. Il s'agit ni plus ni moins que de déclarer une instance **axios** _(comme nous l'avons fait avec **React**)_ et de déclarer des méthodes invoquant cette instance, en ciblant des routes spécifiques.

```
import { ConfigService } from "@nestjs/config";
import axios from "axios";

const linkedInAuthentication = () => {
    const axiosInstance = axios.create({
        baseURL: 'https://www.linkedin.com/oauth/v2',
        withCredentials: true,
    });
    return axiosInstance;
}
export const loginLinkedIn = async (code: string) => /* Get accessToken from an authorization code */
    await linkedInAuthentication().get('/accessToken', {
        params: {
            grant_type: 'authorization_code',
            client_id: new ConfigService().get('LINKEDIN_API_CLIENT_ID'),
            client_secret: new ConfigService().get('LINKEDIN_API_CLIENT_SECRET'),
            code,
            redirect_uri: new ConfigService().get('LINKEDIN_API_REDIRECT_URI'),
        },
    });
export const checkSessionValidity = async (accessToken: string) => {/* Introspect and verify an accessToken */
    const body = {
        client_id: new ConfigService().get('LINKEDIN_API_CLIENT_ID'),
        client_secret: new ConfigService().get('LINKEDIN_API_CLIENT_SECRET'),
        token: accessToken,
    };
    return await linkedInAuthentication().post('/introspectToken', new URLSearchParams(body), {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    });
}
```

La méthode `checkSessionValidity` ci-dessus est d'une importance toute particulière. 

En effet, nombreu.x.ses sont les développeu.r.ses à vérifier l'état de validité d'un jeton d'accès en essayant d'obtenir des ressources utilisateurs. Le problème de ce procédé est le suivant : l'obtention de ressources liées à un utilisateur augmente le quota d'exploitation de votre application pour une route spécifique. 

Si à priori, en phase de test et/ou lors d'une utilisation de votre application à faible traffic, aucun problème ne survient, l'application montrera vite des signes de faiblesse et rendra des fonctionnalités obsolètes lors de son évolution.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vhdhqqsdu3sggdjhsa9j.png)

La route cible de la méthode `checkSessionValidity` n'a, quant à elle, aucun impact sur le quota d'utilisation des services par votre application. Elle peut être sollicitée par un `guard`, `decorator` mais encore un `middleware` pour attester ou non de la validité d'un jeton d'accès utilisateur.

> _ℹ️ Les plateformes comme **LinkedIn, Facebook, Google** etc.. se prémunissent de la surexploitation de leurs serveurs en instaurant ces limitations à quota réinitialisable_.

[...] Après avoir installé la librairie `class-validator` en utilisant `npm i --save class-validator`, il nous faudra créer l'objet `LinkedInUserTokenObject`, cet object incluera le jeton d'accès utilisateur ainsi que sa date d'expiration.

```
import { IsNumber, IsString } from "class-validator";

export class LinkedInUserTokenObject {
    @IsString()
    accessToken: string;

    @IsNumber()
    expireIn: number;
}
```

Revenons maintenant à la méthode `login(...)` de notre service `linkedin.service.ts` : 

```
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { loginLinkedIn } from 'src/microservices/linkedin/api/linkedin.api';

import { LinkedInUserTokenObject } from '../objects/linkedin-user-token.object';

@Injectable()
export class LinkedInService {
    constructor() {}

    async login(authorizationCode: string): Promise<LinkedInUserTokenObject> {
        try {
            const response = await loginLinkedIn(authorizationCode);
            const { access_token: accessToken, expires_in: expireIn } = response.data;
            return { accessToken, expireIn }
        } catch (err) {
            throw new UnauthorizedException('Unable to login as LinkedIn user from the provided authorization code!');
        }
    }

}
```

Puis à notre controlleur `linkedin.controller.ts` : 

```
import { Body, Controller, Post } from '@nestjs/common';

import { LinkedInService } from 'src/microservices/linkedin/services/linkedin.service';
import { LinkedInUserTokenObject } from '../objects/linkedin-user-token.object';

@Controller('linkedin')
export class LinkedInController {
    constructor(private readonly linkedInService: LinkedInService) {}

    @Post('auth/login')
    async login(@Body('authorization_code') authorizationCode: string): Promise<LinkedInUserTokenObject> {
        const token: LinkedInUserTokenObject = 
            await this.linkedInService.login(authorizationCode); // Get permissions from LinkedIn server(s)
        return token;
    }

}
```


Si nous inspectons à présent l'onglet "**Réseau/Network**" de notre navigateur, une fois la redirection depuis le portail d'authentification **LinkedIn** effectuée, on peut remarquer que la requête émise avec le code d'autorisation reçoit bel & bien une réponse incluant les propriétés de l'utilisateur : `accessToken` et `expireIn`.

Evidemment, nous retournons ces informations uniquement pour analyser le résultat et savoir notre code fonctionnel.

Nous allons maintenant voir comment obtenir les informations d'un utilisateur depuis ce précieux `accessToken` que nous obtenons.

### 📥 Exploitation des ressources liées à un utilisateur LinkedIn

Pour exploiter les ressources auxquelles notre application est autorisée à accéder, une mise à jour de notre SDK LinkedIn s'impose, nous ajouterons dans notre fichier `src > microservices > linkedin > api > linkedin.api.ts`, x2 nouvelles routes permettant de récupérer :

- l'adresse e-mail de l'utilisateur
- le nom/prénom & image de profil de l'utilisateur

```
import { ConfigService } from "@nestjs/config";
import axios from "axios";

/* (1) LinkedIn API | global endpoint */
const linkedInAPI = () => {
    const axiosInstance = axios.create({
        baseURL: 'https://api.linkedin.com/v2',
        withCredentials: true,
    });
    return axiosInstance;
}
/* [LinkedIn] Get email address */
export const getLinkedInEmailAddress = async (accessToken: string) => 
    linkedInAPI().get(`/emailAddress?q=members&projection=(elements*(handle~))`, {headers: { Authorization: `Bearer ${accessToken}` }});
/* [LinkedIn] Get (first/last)name and profile picture */
export const getLinkedInProfile = async (accessToken: string) => 
    linkedInAPI().get(`/me?projection=(id,firstName,lastName,emailAddress,profilePicture(displayImage~:playableStreams))`, {headers: { Authorization: `Bearer ${accessToken}` }});


/* (2) Authentication */
const linkedInAuthentication = () => {
    const axiosInstance = axios.create({
        baseURL: 'https://www.linkedin.com/oauth/v2',
        withCredentials: true,
    });
    return axiosInstance;
}
export const loginLinkedIn = async (code: string) => /* Get accessToken from an authorization code */
    await linkedInAuthentication().get('/accessToken', {
        params: {
            grant_type: 'authorization_code',
            client_id: new ConfigService().get('LINKEDIN_API_CLIENT_ID'),
            client_secret: new ConfigService().get('LINKEDIN_API_CLIENT_SECRET'),
            code,
            redirect_uri: new ConfigService().get('LINKEDIN_API_REDIRECT_URI'),
        },
    });
export const checkSessionValidity = async (accessToken: string) => {/* Introspect and verify an accessToken */
    const body = {
        client_id: new ConfigService().get('LINKEDIN_API_CLIENT_ID'),
        client_secret: new ConfigService().get('LINKEDIN_API_CLIENT_SECRET'),
        token: accessToken,
    };
    return await linkedInAuthentication().post('/introspectToken', new URLSearchParams(body), {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    });
}
```

Le type de l'object `LinkedInUserProfileObject` ayant pour vocation de répertorier les informations de notre utilisateur doit être créé : 

```
import { IsNumber, IsString } from "class-validator";

export class LinkedInUserProfileObject {
    @IsString()
    firstName: string;

    @IsString()
    lastName: string;

    @IsString()
    email: string;

    @IsString()
    profileImageUrl: string;
}
```

Une nouvelle méthode permettant de récupérer le profil utilisateur depuis l'`accessToken` obtenu doit ensuite être implémentée dans notre fichier `linkedin.service.ts` :

```
import { Injectable, InternalServerErrorException, UnauthorizedException } from '@nestjs/common';
import { getLinkedInEmailAddress, getLinkedInProfile, loginLinkedIn } from 'src/microservices/linkedin/api/linkedin.api';

import { LinkedInUserTokenObject } from '../objects/linkedin-user-token.object';
import { LinkedInUserProfileObject } from '../objects/linkedin-user-profile.object';

@Injectable()
export class LinkedInService {
    constructor() {}

    async login(authorizationCode: string): Promise<LinkedInUserTokenObject> {
        try {
            const response = await loginLinkedIn(authorizationCode);
            const { access_token: accessToken, expires_in: expireIn } = response.data;
            return { accessToken, expireIn }
        } catch (err) {
            throw new UnauthorizedException('Unable to login as LinkedIn user from the provided authorization code!');
        }
    }

    async getProfile(accessToken: string): Promise<LinkedInUserProfileObject> {
        /* (1) Get email of LinkedIn client */
        const req1 = await getLinkedInEmailAddress(accessToken);
        const email = req1?.data?.elements[0]['handle~'].emailAddress;

        /* (2) Get (first/last)name and profile image of LinkedIn client */
        const req2 = await getLinkedInProfile(accessToken);
        const firstName = req2?.data?.firstName?.localized?.fr_FR;
        const lastName = req2?.data?.lastName?.localized?.fr_FR;
        const profileImageUrl = req2?.data?.profilePicture['displayImage~']?.elements[0]?.identifiers[0]?.identifier;
    
        if (!firstName || !lastName || !profileImageUrl || !email) throw new InternalServerErrorException('An error occured while parsing the retrieved profile from LinkedIn!');
        return {
            firstName,
            lastName,
            email,
            profileImageUrl,
        }
    }
}
```

Il ne nous reste plus qu'à modifier notre fichier `linkedin.controller.ts` pour retourner les informations de l'utilisateur plutôt que de retourner le jeton d'accès client : 

```
import { Body, Controller, Post } from '@nestjs/common';

import { LinkedInService } from 'src/microservices/linkedin/services/linkedin.service';
import { LinkedInUserTokenObject } from '../objects/linkedin-user-token.object';
import { LinkedInUserProfileObject } from '../objects/linkedin-user-profile.object';

@Controller('linkedin')
export class LinkedInController {
  constructor(private readonly linkedInService: LinkedInService) {}

  @Post('auth/login')
  async login(@Body('authorization_code') authorizationCode: string): Promise<any> {
    const token: LinkedInUserTokenObject = 
        await this.linkedInService.login(authorizationCode); // Get permissions from LinkedIn server(s)
    const profile: LinkedInUserProfileObject = 
        await this.linkedInService.getProfile(token.accessToken); // Get profile from LinkedIn server(s)
    return profile;
  }

}
```

**Félicitations 🎉**

Les informations de l'utilisateur ayant procédé à l'authentification **LinkedIn** sont dorénavant renvoyées au client.

Il ne tient plus qu'à vous d'utiliser ces informations selon vos ambitions, vous pourriez par exemple :

_- Sauvegarder les informations liées à cet utilisateur en base de données dans le cadre d'une connexion/inscription via LinkedIn._
_- Garder en cache ou retourner les informations de l'utilisateur et les sauvegarder en cache front-end (via cookies http-only ou local storage, etc..)_
_- etc.._

---

## 🎁 Bonus

**-> (Source) Retrouvez l'intégralité du code qui constitue ce tutoriel via [ce répertoire Github](https://github.com/MessageGit/oauth-sign-in-with-linkedin).**