# Single Sign-On

Si vous proposez déjà un espace connecté à vos utilisateurs (sur une application ou site web par exemple), vous allez adorer le Single Sign-On ! Plus besoin pour vos utilisateurs de créer un compte sur votre Spot : vous pouvez leur proposer de rejoindre le Spot en un clic depuis leur espace connecté ou en utilisant les identifiants qu'ils utilisent déjà.&#x20;

Deux parcours utilisateur typiques :

{% tabs %}
{% tab title="Depuis votre application" %}

1. Au sein de votre application, votre utilisateur clique sur un lien "Académie".
2. Au clic sur ce lien, votre application génère un "token" grâce au script présenté ci-dessous.
3. L'utilisateur est redirigé vers votre Spot et transmet au passage le token.
4. Si le token est validé, l'utilisateur est connecté et identifié grâce aux données que vous aurez incorporées dans le token (comme son prénom, son nom et son email).
   {% endtab %}

{% tab title="Depuis votre Spot" %}

1. Si vous activez le SSO, un utilisateur sur votre Spot va pouvoir choisir entre s'inscrire avec son email + mot de passe ou s'inscrire via SSO. S'il choisit le SSO, il est redirigé vers une page de votre application où il devra être identifié (ce qu'on appelle l'URL d'autorisation).
2. Depuis cette page, l'utilisateur doit se connecter s'il n'est pas connecté ou créer son compte s'il n'en a pas. S'il est déjà connecté sur cette page, vous n'aurez pas nécessairement besoin de lui demander de s'identifier à nouveau.
3. Votre application génère un token (cf. le script ci-dessous)...
4. ...et l'envoie à votre Spot tout en redirigeant votre utilisateur sur la page du Spot où il était précédemment.
5. Si le Spot valide le token, alors l'utilisateur est connecté. S'il n'existait pas avant, un nouveau membre est créé grâce aux informations que vous aurez inclues dans le token (comme son prénom, nom et email).

<figure><img src="/files/Rf7EVUxFyDlz1c9QlehA" alt=""><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}

## Implémentation

### 1. Activer le SSO depuis les settings de votre Spot

Pour commencer, vous devez activer le SSO depuis les settings de votre Spot :&#x20;

1. Ajoutez une URL d'Autorisation. Cette URL est celle d'une page vers laquelle est redirigé un utilisateur qui souhaite se connecter à votre Spot depuis votre Spot (Cf le deuxième point du parcours "[Depuis votre Spot](#depuis-votre-spot)").\
   \&#xNAN;*�� Si vous souhaitez effectuer des tests en local, utilisez dans l'URL `127.0.0.1` car les URL `localhost` sont bloquées par notre système.*&#x20;
2. Copiez la clé privée.
3. Activez le SSO lorsque les scripts ci-dessous ont été intégrés à votre app.

<figure><img src="/files/VzQSZBWLMN6i1QDBVqQE" alt=""><figcaption></figcaption></figure>

### 2. **Générer un token SSO**

Pour connecter un utilisateur, il faut créer un token et le transmettre à Spot lors de la tentative de connexion de l'utilisateur.

1. **Installer la librairie JWT**

Installez la librairie qui permet de créer le token en question :

{% tabs %}
{% tab title="Node.js" %}

```javascript
npm install --save jsonwebtoken
```

{% endtab %}

{% tab title="Python" %}
{% code fullWidth="true" %}

```python
pip install PyJWT
```

{% endcode %}
{% endtab %}

{% tab title="PHP" %}

```php
composer require firebase/php-jwt
```

{% endtab %}

{% tab title="C#" %}

```csharp
dotnet add package System.IdentityModel.Tokens.Jwt
dotnet add package Microsoft.IdentityModel.Tokens
dotnet add package Newtonsoft.Json
```

{% endtab %}
{% endtabs %}

2. **Créer le token**

Ensuite, générez le token en intégrant les informations nécessaires à l'identification de l'utilisateur. Vous aurez besoin de la clé privée que vous pouvez trouver dans les settings de votre Spot.

{% tabs %}
{% tab title="Node.js" %}
{% code fullWidth="false" %}

```javascript
const jwt = require('jsonwebtoken');
const privateKey = 'YOUR_PRIVATE_KEY';
function createToken(user) {
  const userData = {
    sub: user.id, // Your own user ID
    firstName: user.firstName,
    lastName: user.lastName,
    email: user.email,
    title: user.title, // optional
    avatarUrl: user.avatarUrl, // optional
    lang: user.lang, // optional
    timezone: user.timezone, // optional
    groups {
      join: ["groupsId",...];
      leave: ["groupsId",...]; 
    }, //optional
    domains {
      set: { default: "https://some-default-url.com/for/this/member", customContext: "https://some-custom-context-url.com/for/this/member",...}
      unset: ['default', 'customContext']
    }, //optional
    customPropertiesValues {
      "customPropertiesSlug1": ["valeurSlug",...];
      "customPropertiesSlug2": valeur;
    }, //optional
  };
  return jwt.sign(userData, privateKey, { algorithm: "HS256" });
}
```

{% endcode %}
{% endtab %}

{% tab title="Python" %}

```python
import jwt

private_key = "YOUR_PRIVATE_KEY"

def create_token(user):
  user_data = {
    'sub': user.id, # Your own user ID
    'firstName': user.firstName,
    'lastName': user.lastName,
    'email': user.email,
    'title': user.title, # optional
    'avatarUrl': user.avatarUrl, # optional
    'lang': user.lang, # optional
    'timezone': user.timezone, # optional
    'groups' {
      'join': ["groupsId",...];
      'leave': ["groupsId",...]; 
    }, #optional
    'domains' {
      'set': { default: "https://some-default-url.com/for/this/member", customContext: "https://some-custom-context-url.com/for/this/member",...}
      'unset': ['default', 'customContext']
    }, #optional
    'customPropertiesValues' {
      "customPropertiesSlug1": ["valeurSlug",...];
      "customPropertiesSlug2": valeur;
    }, #optional
  }
  return jwt.encode(user_data, private_key, algorithm='HS256')
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
use Firebase\JWT\JWT;

$privateKey = 'YOUR_PRIVATE_KEY';

function createToken($user) {
    global $privateKey;

    $userData = [
        'sub'                   => $user['id'],          // Your own user ID
        'firstName'             => $user['firstName'],
        'lastName'              => $user['lastName'],
        'email'                 => $user['email'],
        'title'                 => $user['title'] ?? null,      // optional
        'avatarUrl'             => $user['avatarUrl'] ?? null,  // optional
        'lang'                  => $user['lang'] ?? null,       // optional
        'timezone'              => $user['timezone'] ?? null,   // optional
        'groups'                => [
            'join'  => $user['groups']['join'] ?? [],
            'leave' => $user['groups']['leave'] ?? []
        ], // optional
        'domains'               => [
            'set'   => [
                'default'      => "https://some-default-url.com/for/this/member",
                'customContext'=> "https://some-custom-context-url.com/for/this/member",
                // add other domains if necessary
            ],
            'unset' => ['default', 'customContext']
        ], // optional
        'customPropertiesValues' => [
            "customPropertiesSlug1" => ["valeurSlug"],
            "customPropertiesSlug2" => "valeur"
        ] // optional
    ];

    return JWT::encode($userData, $privateKey, 'HS256');
}
?>
```

{% endtab %}

{% tab title="C#" %}

```csharp
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

public class JwtTokenService
{
    private readonly string _privateKey;

    public JwtTokenService(string privateKey)
    {
        _privateKey = privateKey;
    }

    public string CreateToken(User user)
    {
        var claims = new List<Claim>
        {
            new Claim("sub", user.Id),
            new Claim("firstName", user.FirstName),
            new Claim("lastName", user.LastName),
            new Claim("email", user.Email)
        };

        if (!string.IsNullOrEmpty(user.Title))
            claims.Add(new Claim("title", user.Title));

        if (!string.IsNullOrEmpty(user.AvatarUrl))
            claims.Add(new Claim("avatarUrl", user.AvatarUrl));

        if (!string.IsNullOrEmpty(user.Lang))
            claims.Add(new Claim("lang", user.Lang));

        if (!string.IsNullOrEmpty(user.Timezone))
            claims.Add(new Claim("timezone", user.Timezone));

        if (user.Groups != null)
        {
            if (user.Groups.Join != null && user.Groups.Join.Length > 0)
                claims.Add(new Claim("groups.join", JsonConvert.SerializeObject(user.Groups.Join)));

            if (user.Groups.Leave != null && user.Groups.Leave.Length > 0)
                claims.Add(new Claim("groups.leave", JsonConvert.SerializeObject(user.Groups.Leave)));
        }

        if (user.Domains != null)
        {
            if (user.Domains.Set != null && user.Domains.Set.Count > 0)
                claims.Add(new Claim("domains.set", JsonConvert.SerializeObject(user.Domains.Set)));

            if (user.Domains.Unset != null && user.Domains.Unset.Length > 0)
                claims.Add(new Claim("domains.unset", JsonConvert.SerializeObject(user.Domains.Unset)));
        }
 
        if (user.CustomPropertiesValues != null)
        {
            foreach (var kvp in user.CustomPropertiesValues)
            {
                claims.Add(new Claim($"customPropertiesValues.{kvp.Key}", JsonConvert.SerializeObject(kvp.Value)));
            }
        }

        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.UTF8.GetBytes(_privateKey);
        
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            SigningCredentials = new SigningCredentials(
                new SymmetricSecurityKey(key), 
                SecurityAlgorithms.HmacSha256)
        };

        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
}

public class User
{
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Title { get; set; }
    public string AvatarUrl { get; set; }
    public string Lang { get; set; }
    public string Timezone { get; set; }
    public Groups Groups { get; set; }
    public Domains Domains { get; set; }
    public Dictionary<string, object> CustomPropertiesValues { get; set; }
}

public class Groups
{
    public string[] Join { get; set; }
    public string[] Leave { get; set; }
}

public class Domains
{
    public Dictionary<string, string> Set { get; set; }
    public string[] Unset { get; set; }
}
```

{% endtab %}
{% endtabs %}

Au sein de votre token, vous pouvez utiliser les paramètres suivants :&#x20;

<table><thead><tr><th width="152">Paramètre</th><th width="115">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>sub</code></td><td>string</td><td><p><strong>(requis)</strong> </p><p>L'identifiant de votre utilisateur sur votre application. Ce champ doit être de 255 caractères maximum.</p></td></tr><tr><td><code>firstName</code></td><td>string</td><td><p><strong>(requis)</strong> </p><p>Le prénom de l'utilisateur (max. 255 caractères).</p></td></tr><tr><td><code>lastName</code></td><td>string</td><td><p><strong>(requis)</strong> </p><p>Le nom de l'utilisateur  (max. 255 caractères).</p></td></tr><tr><td><code>email</code></td><td>string</td><td><p><strong>(requis)</strong> </p><p>L'email de l'utilisateur. Assurez-vous d'avoir validé que cet email est légitime  (max. 255 caractères + le format de l'email doit être valide).</p></td></tr><tr><td><code>title</code></td><td>string</td><td>Le titre de l'utilisateur en format text (utilisé généralement pour préciser son job / son entreprise).</td></tr><tr><td><code>avatarUrl</code></td><td>string</td><td>Une URL qui pointe vers la photo de profil de l'utilisateur. Elle doit démarrer par  https:// ou http:// et vous devez vous assurer que l'URL est valide.</td></tr><tr><td><code>lang</code></td><td>string</td><td><p></p><p>La langue par défaut de l'application pour cet utilisateur. MeltingSpot supporte :</p><ul><li><code>en</code> pour l'anglais</li><li><code>fr</code> pour le français</li><li><code>de</code> pour l'allemand</li><li>si la langue n'est pas spécifiée, la valeur par défaut est <code>en</code>. </li></ul></td></tr><tr><td><code>timezone</code></td><td>string</td><td>Le fuseau horaire de l'utilisateur. Si aucun n'est précisé ou s'il n'est pas valide, la valeur par défaut appliquée sera <code>Europe/Paris</code>.</td></tr><tr><td><code>iat</code></td><td>number</td><td>La date à laquelle le token est généré.</td></tr><tr><td><code>exp</code></td><td>number</td><td>La date d'expiration du token. Bien que ce champ ne soit pas requis, nous recommandons qu'il soit fixé à +60s par rapport à l'instant où le token est généré. S'il n'est pas spécifié, le token sera valide indéfiniment, ce qui pourra poser des problèmes de sécurité.</td></tr><tr><td><code>groups:  join</code></td><td>string[]</td><td>Les groupes dans lesquels vous voulez ajouter le membre. Il vous suffit de récupérer l'id des groupes en question depuis le menu de sélection des groupes dans la table audience.</td></tr><tr><td><code>groups: leave</code> </td><td>string[]</td><td>Les groupes dont vous voulez retirer le membre. Il vous suffit de récupérer l'id des groupes en question depuis le menu de sélection des groupes dans la table audience.</td></tr><tr><td><code>customPropertiesValues</code></td><td><em>type de la propriété</em></td><td>Si vous avez des propriétés personnalisées, vous pouvez spécifier la valeur que vont prendre ces propriétés pour le membre.</td></tr><tr><td><code>domains: set</code></td><td>string[]</td><td>Si vous avez réalisé l'embed du Spot sur plusieurs domaines, spécifiez les domaines de redirection du membre. Il sera redirigé vers la clé de domaine <code>default</code> au clic sur un email de notification.</td></tr><tr><td><code>domains: unset</code></td><td>string[]</td><td>Si vous avez réalisé l'embed du Spot sur plusieurs domaines et spécifié des domaines de redirection du membre, vous pouvez les retirer via ce paramètre (URL d'un logiciel pour lequel le membre n'a plus de licence, site web dont l'URL a changé...).<br>Vous devrez réutiliser les clés définies dans l'objet <code>set.</code></td></tr></tbody></table>

### **3. Redirection vers MeltingSpot**

Une fois le token utilisateur créé, vous devez rediriger l'utilisateur vers une URL en passant le token en paramètre. Cette URL vous est fournie en paramètre (`redirectUrl`) lorsque l'utilisateur atterrit sur votre URL d'autorisation. De base, elle ressemble à ceci :

```url
https://go.meltingspot.io/spot/<Your Spot ID>/sso/jwt
```

Vous devez ajouter le token en paramètre de la façon suivante :

```url
https://go.meltingspot.io/spot/<Your Spot ID>/sso/jwt?ms_token=<Your generated SSO Token>
```

Dans la plupart des cas, l'URL de redirection que nous fournissons (`redirectUrl`) contient aussi un paramètre `referrerUrl` qui permet de renvoyer l'utilisateur sur la page où il se trouvait lorsqu'il s'est connecté en mode SSO.

Vous pouvez forcer la valeur de ce paramètre, mais pour être valide, la valeur doit représenter un chemin relatif à `https://go.meltingspot.io` et à votre Spot (ex: `?referrerUrl=/spot/129487c9-6acc-43d9-ab96-182ded763538/lives`).

{% hint style="info" %}
Votre Spot ID est la chaîne de caractère suivant `spot/` dans l'url de votre Spot (ex : `go.meltingspot.io/spot/129487c9-6acc-43d9-ab96-182ded763538` -> le Spot ID est `129487c9-6acc-43d9-ab96-182ded763538`).
{% endhint %}

## Embed / widgets + SSO = ❤️

Une fois l'authentification SSO configurée pour votre Spot, il vous est possible de transmettre le token JWT authentifiant l'utilisateur dans les scripts d'installation de l'embed du Spot ou de widgets. **Cela permettra à chaque utilisateur dans votre espace de pouvoir afficher l'embed ou un widget en étant directement connecté.** A l'affichage de l'embed, vos membres n'auront donc plus à cliquer sur 'Continuer avec SSO' pour s'inscrire ou se connecter 😉

Pour cela, il vous faut ajouter dans les paramètres du script d'installation de l'embed ou du widget le paramètre `authToken`.

<figure><img src="/files/y6Sq4u65DPbf74eYyuKc" alt="authenticate users in embed using SSO" width="563"><figcaption></figcaption></figure>

## Bon à savoir !

* **Que se passe-t-il quand un utilisateur rejoint un Spot en SSO ?**\
  S'il n'existe pas, nous créons un nouvel utilisateur sur la base des informations transmises dans le token et nous le connectons. S'il existe déjà, nous le connectons simplement sans mettre à jour ses informations.
* **Que se passe-t-il pour les utilisateurs actuels lorsque j'active le SSO ?**

  Si vous activez le SSO alors que vous avez déjà des membres inscrits sur votre Spot : aucun souci, ils pourront continuer à utiliser les identifiants actuels (email + mot de passe). Ils pourront d'ailleurs s'identifier via SSO et ajouter cette méthode d'identification à leur profil du moment que l'email associé à leur compte SSO est la même que celle utilisée sur le Spot.
* **Mon Spot est privé, comment sont gérées les inscriptions en mode SSO ?**\
  Puisqu'il a accès à votre application, nous considérons qu'un membre qui rejoint un Spot en mode SSO a déjà été validé par vous. S'il rejoint votre Spot privé via SSO, il est automatiquement accepté. Vous pourrez cependant le désactiver à tout moment.
* **Que se passe-t-il si un utilisateur modifie son email dans mon application ?**\
  Lors de sa prochaine connexion au Spot, nous le considérerons comme un nouvel utilisateur. S'il veut pouvoir accéder à son ancien profil sur le Spot, il devra se connecter via email + mot de passe.
* **Est-ce que je peux décider de la page d'atterrissage qu'ouvre un membre lorsqu'il se connecte en SSO ?** \
  Oui ! Lorsqu'un de vos utilisateurs accède à votre Spot depuis votre application vous pouvez utiliser le paramètre "referrerUrl" pour l'envoyer sur n'importe quelle page de votre Spot.
* **Comment sont gérés les statuts des membres lorsqu'ils se connectent ou rejoignent un Spot via SSO ?** \
  [-> Nous vous disons tout ici !](/manage-spot/audience/membres/members-status.md#member-status-update-rules-on-registration-or-connection)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://help.meltingspot.io/manage-spot/sso.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
