> For the complete documentation index, see [llms.txt](https://help.meltingspot.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.meltingspot.io/manage-spot/sso.md).

# Single Sign-On

Permettez à vos utilisateurs d'accéder à votre Spot via votre propre source d'authentification, sans créer de compte MeltingSpot séparé.

MeltingSpot propose **deux modes d'authentification SSO**, activables indépendamment depuis les settings de votre Spot (`Paramètres > Single Sign-On`) :

* Authentification SSO avec URL : MeltingSpot renvoie l'utilisateur vers votre page de login, qui le renvoie ensuite vers MeltingSpot avec un token signé.
* Authentification SSO programmatique : Votre backend ou nos SDKs échangent directement un token signé contre une session, sans redirection navigateur.

{% hint style="info" %}
Les deux modes reposent sur **le même token JWT** et **la même clé secrète SSO** (visible en haut de la page de settings, champ "Authentifiez vos utilisateurs avec la clé SSO ci-dessous"). Seule la façon dont ce token est transmis à MeltingSpot diffère.
{% endhint %}

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

Que vous utilisiez le mode "URL d'autorisation" ou le mode "Programmatique", vous devez générer un **JSON Web Token (JWT)** signé en **HS256** avec la clé secrète SSO de votre Spot.

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>La langue par défaut de l'application pour cet utilisateur. MeltingSpot supporte : <code>en</code>, <code>fr</code>, <code>de</code>, <code>it</code>, <code>es</code>, <code>pt</code>, <code>nl</code>. Si la langue n'est pas spécifiée, la valeur par défaut est <code>en</code>.</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>

## Mode 1 : Authentification SSO avec URL d'authorisation&#x20;

Ce mode délègue le login à votre propre page d'authentification via une redirection navigateur.

Vous avez 2 parcours principaux :&#x20;

{% 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-dessus.
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-dessus)...
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ération du Token**

Voir la section [#generer-un-token-sso](#generer-un-token-sso "mention")

#### **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>

## Mode 2 — Authentification SSO programmatique

Ce mode permet à votre backend ou à notre SDK Coach d'échanger directement un token signé contre une session MeltingSpot, **sans redirection navigateur**.

### Configuration

Dans l'onglet **"Authentification SSO programmatique"** des settings SSO, il n'y a qu'un seul réglage : un switch `Activer l'authentification par programmation (SDK)`

### Utilisation de l'auto-join dans le SDK Coach

Une fois ce mode activé, transmettez le token JWT généré (section "Génération du token") en tant que `authToken` à notre SDK **Coach** et ajoutez la mention `autoJoin` :

{% code overflow="wrap" %}

```
import { Coach } from '@meltingspot/coach';
await Coach.start({  spotId: 'YOUR_SPOT_ID',  authToken: 'CURRENT_USER_SSO_TOKEN', // le même JWT que pour l'authentification simple  autoJoin: true, // rejoint et valide automatiquement le membre sur le Spot});
```

{% endcode %}

Le Coach ré-authentifie l'utilisateur avec ce token, puis appelle en votre nom l'API de jonction automatique en lui repassant le même JWT signé. Le Spot en déduit prénom, nom, email, groupes, domaines et propriétés personnalisées directement depuis les claims du token — inutile de les renvoyer une seconde fois.

{% hint style="info" %}

* `autoJoin` nécessite `authToken` : sans lui, l'option est ignorée (aucune jonction n'est tentée).
* L'auto-join respecte les règles habituelles de transition de statut : un membre déjà **rejeté** ou **désactivé** ne sera pas automatiquement débloqué.
* Si l'auto-join échoue, le démarrage du Coach échoue dans son ensemble et l'authentification est annulée — ce n'est pas un mécanisme "best-effort" silencieux, il doit être traité comme une erreur bloquante côté intégration.
  {% endhint %}

## Bon à savoir !

* **Dois-je activer les deux modes ?**

  Non, ils sont indépendants. Activez "URL d'autorisation" si vous voulez un bouton de connexion SSO classique sur votre site ; activez "Programmatique" si vous intégrez nos SDKs ou appelez notre API directement. Rien n'empêche d'activer les deux en parallèle.
* **Dois-je générer un token différent pour l'auto-join ?**

  Non. C'est exactement le même JWT, avec les mêmes claims et la même clé secrète que pour l'authentification programmatique classique. Seule différence : l'option `autoJoin: true` côté SDK Coach.
* **En mode Programmatique, pourquoi mon utilisateur est authentifié mais pas membre du Spot ?**

  En mode "Programmatique", authentification et jonction au Spot sont deux étapes distinctes (contrairement au mode "URL d'autorisation" où elles sont automatiquement liées). Utilisez `autoJoin: true` sur le Coach SDK, ou gérez la jonction séparément via votre intégration.
* **En mode Programmatique, que se passe-t-il si un membre a déjà été rejeté ou désactivé ?**

  L'auto-join respecte les règles de transition de statut existantes : il ne "débloque" pas un membre rejeté ou désactivé. Le membre doit être dans un état qui autorise l'acceptation pour que l'auto-join aboutisse.
* **Que se passe-t-il quand un utilisateur rejoint un Spot en SSO ?**\
  Quelque soit le mode utilisé, à partir du moment où le token est correct, si le membre 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 ?**

  Quelque soit le mode, 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 ?**\
  En mode "URL d'autorisation" : puisque l'utilisateur repasse par votre page de login avant de rejoindre le Spot, on considère qu'il a déjà été validé par vous — il est donc automatiquement accepté, même si le Spot est privé.

  En mode "Programmatique", ce n'est pas automatique : l'authentification seule ne fait pas rejoindre le Spot. Avec `autoJoin: true`, le membre est ajouté en respectant les règles de transition de statut habituelles (un membre déjà rejeté ou désactivé ne sera pas débloqué) — ce n'est donc pas une acceptation garantie comme en mode redirect.
* **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
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
