Intégrations

Connecter votre application en SSO

Authentifiez les utilisateurs de votre portail depuis votre backend, avec des exemples PHP et Node.js.

Télécharger le Markdown

Mis à jour le

Le SSO pealHQ connecte les utilisateurs de votre application à votre portail de feedback. Votre serveur authentifie l’utilisateur, signe un jeton JWT avec le secret de votre workspace et redirige son navigateur vers pealHQ. Le compte du portail est créé ou mis à jour à la connexion.

Ce guide peut être partagé avec votre équipe technique ou un agent de développement. Les valeurs des exemples sont fictives. Le secret réel reste dans vos réglages et dans les variables d’environnement de votre serveur.

Avant de commencer

  • Vous devez administrer un workspace dont le forfait permet la fonctionnalité SSO personnalisé. Sa disponibilité est affichée dans les réglages.
  • Votre application doit déjà authentifier ses utilisateurs côté serveur.
  • Ce mécanisme utilise un JWT signé en HS256 avec un secret propre au workspace. Il ne s’agit pas d’un connecteur SAML ou OpenID Connect.
  • Le SSO donne accès au portail client et authentifie les membres déjà autorisés de l’équipe pour ce workspace. Il ne crée aucun administrateur et ne modifie aucun rôle. Invitez vos collaborateurs depuis la gestion de l’équipe pealHQ.

Si l’adresse email du compte connecté par SSO correspond à un unique membre déjà présent dans l’équipe de ce workspace, le bouton Administration ouvre directement son administration. Le portail et l’administration partagent la même session : aucun mot de passe ni code de double authentification Peal supplémentaire n’est demandé. Votre application prend en charge l’authentification, y compris son éventuel second facteur. Les commentaires du membre apparaissent comme des réponses de l’équipe ; son profil client conserve ses votes, suggestions et abonnements.

Cet accès reste limité à ce workspace. Il ne connecte pas le membre à son compte central Peal, à d’autres workspaces ou à l’administration plateforme. Une invitation non acceptée ne suffit pas. Le retrait d’un membre supprime immédiatement son accès d’équipe. La déconnexion ferme le portail et l’administration de ce workspace ; elle ne ferme pas une connexion centrale Peal indépendante. Après expiration de la session, une nouvelle connexion SSO est nécessaire pour retrouver les droits d’équipe, même si le portail se souvient du profil client.

Configurer le workspace

  1. Ouvrez Paramètres → Connexion des clients dans l’administration pealHQ.
  2. Sélectionnez SSO (votre backend gère l’identité).
  3. Renseignez l’URL de connexion depuis votre produit, puis enregistrez. C’est la page authentifiée de votre application qui générera un jeton neuf et redirigera le client vers le callback pealHQ. Lorsqu’un client ouvre directement le portail sans session, le bouton « Se connecter à partir de {nom du workspace} » le renvoie vers cette URL.
  4. Copiez l’URL de callback, l’audience (slug du workspace) et le secret partagé affichés.
  5. Enregistrez ces valeurs dans la configuration serveur de votre application. Utilisez un secret distinct pour chaque workspace et environnement.

Exemple de configuration dans votre application :

PEAL_SSO_CALLBACK_URL=https://acme.pealhq.com/sso/callback
PEAL_SSO_AUDIENCE=acme
PEAL_SSO_SECRET=remplacer-par-le-secret-affiche-dans-pealhq

Ces noms de variables sont une convention pour les exemples, pas des variables à ajouter au serveur pealHQ. Utilisez le secret tel qu’il est affiché, sans décodage Base64.

Selon l’environnement, le callback peut aussi ressembler à https://preview.example/w/acme/sso/callback. Copiez toujours l’URL des réglages, sans la reconstruire à partir du domaine. L’audience reste acme dans les deux cas.

Parcours de connexion

  1. L’utilisateur clique sur « Donner mon avis » dans votre application.
  2. Votre backend vérifie sa session et son autorisation d’accéder au workspace concerné.
  3. Votre backend prend l’identifiant, le nom et l’email dans le compte authentifié, puis génère un nouveau JWT valable 60 secondes.
  4. Il répond au navigateur par une redirection HTTP vers URL_DE_CALLBACK?token=JWT_ENCODE.
  5. pealHQ vérifie le jeton, ouvre la session du workspace avec les droits du membre existant, le cas échéant, et redirige vers la destination choisie dans le même workspace, ou vers la liste des demandes par défaut.

La redirection doit être suivie par le navigateur de l’utilisateur, afin qu’il reçoive les cookies de session. Un appel HTTP effectué uniquement par votre serveur ne connecte pas son navigateur. Ouvrez le portail comme une page de premier niveau.

Pour choisir la page d’arrivée, ajoutez le paramètre de requête facultatif redirect à l’URL du callback, à côté de token :

https://acme.pealhq.com/sso/callback?token=JWT_ENCODE&redirect=/changelog
https://acme.pealhq.com/sso/callback?token=JWT_ENCODE&redirect=/roadmap
Valeur de redirectPage d’arrivée après connexion
/changelogLes nouveautés du workspace.
/roadmapLa roadmap du workspace.
/requests, paramètre absent ou autre valeurLa liste des demandes du workspace.

Les valeurs sont exactes, sans domaine, préfixe de workspace, query string ni fragment. En mode chemin, utilisez toujours redirect=/changelog ou redirect=/roadmap : pealHQ ajoute lui-même /w/{slug} à la destination. Les URLs externes et les chemins arbitraires ne sont pas suivis. redirect est un paramètre du callback, pas un champ du JWT ; il ne modifie pas les règles de validation du jeton.

L’expiration du JWT limite le temps disponible pour se connecter ; elle n’est pas la durée de la session du portail. Se déconnecter de votre application ne ferme pas automatiquement la session pealHQ.

Contrat du jeton

Le format est base64url(header).base64url(payload).base64url(signature), sans padding =. La signature est le HMAC SHA-256 binaire de header_encode.payload_encode, calculé avec le secret partagé, puis encodé en Base64URL.

Utilisez exactement cet en-tête, avec les clés dans cet ordre et sans champ supplémentaire :

{"alg":"HS256","typ":"JWT"}

Le vérificateur actuel exige cet ordre (alg, puis typ) et ces deux champs seulement. Si vous utilisez une bibliothèque JWT, vérifiez l’en-tête qu’elle produit ; n’ajoutez pas de kid.

Tous les champs suivants sont obligatoires :

ChampTypeValeur attendue
audChaîneSlug exact du workspace, par exemple acme. Ce n’est ni une URL ni un tableau.
subChaîne non videIdentifiant stable de l’utilisateur dans votre application, par exemple user-482. Convertissez les identifiants numériques en chaîne.
emailChaîneEmail valide et de confiance du compte authentifié.
nameChaîne non videNom affiché dans le portail.
iatEntierDate d’émission Unix en secondes, sans millisecondes.
expEntierDate d’expiration Unix en secondes, postérieure à l’émission et à l’heure de réception.
jtiChaîne non videIdentifiant aléatoire unique pour chaque tentative de connexion, par exemple un UUID.

La durée exp - iat ne doit pas dépasser 120 secondes. Les exemples utilisent 60 secondes. L’heure d’émission peut avoir au plus 5 secondes d’avance sur le serveur pealHQ ; synchronisez les horloges de vos serveurs.

Un jeton ne peut être utilisé qu’une fois. Générez un nouveau jti et un nouveau jeton à chaque clic ou nouvelle tentative. Un jeton validé peut être consommé même si une erreur de rapprochement de comptes survient ensuite.

N’utilisez pas de champ admin ou role pour accorder des droits : seuls sub, email et name servent à identifier le client après validation.

Exemple PHP

Cette fonction fonctionne sans bibliothèque JWT. Appelez-la depuis une route serveur qui a déjà authentifié et autorisé l’utilisateur. Les clés id, email et name viennent de votre base de comptes, jamais de paramètres fournis librement par le navigateur.

<?php

function pealSsoUrl(array $user, string $redirect = '/requests'): string
{
    $callback = getenv('PEAL_SSO_CALLBACK_URL');
    $audience = getenv('PEAL_SSO_AUDIENCE');
    $secret = getenv('PEAL_SSO_SECRET');

    if (! $callback || ! $audience || ! $secret || strlen($secret) < 32) {
        throw new RuntimeException('Configuration SSO pealHQ manquante ou invalide.');
    }

    $encode = static fn (string $value): string =>
        rtrim(strtr(base64_encode($value), '+/', '-_'), '=');

    $now = time();
    $header = $encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT'], JSON_THROW_ON_ERROR));
    $payload = $encode(json_encode([
        'aud' => $audience,
        'sub' => (string) $user['id'],
        'email' => $user['email'],
        'name' => $user['name'],
        'iat' => $now,
        'exp' => $now + 60,
        'jti' => bin2hex(random_bytes(16)),
    ], JSON_THROW_ON_ERROR));
    $signature = $encode(hash_hmac('sha256', "$header.$payload", $secret, true));
    $token = "$header.$payload.$signature";
    $separator = str_contains($callback, '?') ? '&' : '?';

    return $callback.$separator.http_build_query([
        'token' => $token,
        'redirect' => $redirect,
    ], '', '&', PHP_QUERY_RFC3986);
}

Dans votre contrôleur, après vérification de la session, redirigez avec l’URL retournée : return redirect()->away(pealSsoUrl($user)); sous Laravel (adaptez votre modèle en tableau), ou header('Location: '.pealSsoUrl($user), true, 302); exit; en PHP natif. Si votre framework met les variables d’environnement en cache, injectez les valeurs via son système de configuration plutôt que getenv.

Pour ouvrir les nouveautés, appelez pealSsoUrl($user, '/changelog') ; pour la roadmap, pealSsoUrl($user, '/roadmap').

Exemple Node.js

Ce module ESM utilise uniquement node:crypto. Gardez-le côté serveur, dans une route accessible à un utilisateur déjà authentifié et autorisé.

import { createHmac, randomUUID } from 'node:crypto';

export function pealSsoUrl(user, redirect = '/requests') {
    const callback = process.env.PEAL_SSO_CALLBACK_URL;
    const audience = process.env.PEAL_SSO_AUDIENCE;
    const secret = process.env.PEAL_SSO_SECRET;

    if (!callback || !audience || !secret || secret.length < 32) {
        throw new Error('Configuration SSO pealHQ manquante ou invalide.');
    }

    const encode = (value) => Buffer.from(JSON.stringify(value)).toString('base64url');
    const now = Math.floor(Date.now() / 1000);
    const header = encode({ alg: 'HS256', typ: 'JWT' });
    const payload = encode({
        aud: audience,
        sub: String(user.id),
        email: user.email,
        name: user.name,
        iat: now,
        exp: now + 60,
        jti: randomUUID(),
    });
    const signature = createHmac('sha256', secret)
        .update(`${header}.${payload}`)
        .digest('base64url');
    const url = new URL(callback);
    url.searchParams.set('token', `${header}.${payload}.${signature}`);
    url.searchParams.set('redirect', redirect);

    return url.toString();
}

Par exemple, dans un contrôleur Express protégé par votre middleware d’authentification : res.redirect(302, pealSsoUrl(req.user));. Adaptez les noms de propriétés à votre modèle utilisateur. Le secret et la fonction de signature ne doivent jamais être inclus dans le bundle JavaScript envoyé au navigateur.

Utilisez pealSsoUrl(req.user, '/changelog') ou pealSsoUrl(req.user, '/roadmap') pour choisir la page d’arrivée.

Identité et comptes existants

pealHQ recherche d’abord un client portant le même sub dans le workspace, puis un client portant le même email. Un compte existant avec cet email peut être rattaché au SSO s’il n’est pas déjà lié à un autre identifiant SSO. Sinon, un nouveau compte client est créé.

Le nom et l’email sont mis à jour à chaque connexion. Gardez le sub stable lorsque l’email change et ne réutilisez jamais l’identifiant d’un utilisateur supprimé pour une autre personne. Votre serveur doit garantir que l’utilisateur contrôle l’email transmis : ce rapprochement peut donner accès à un compte du portail et aux droits d’un membre d’équipe déjà autorisé dans ce workspace.

Si l’identifiant et l’email désignent deux comptes différents, ou si l’email appartient déjà à un autre identifiant SSO, la connexion est refusée. Corrigez l’identité dans votre application ou faites examiner les comptes concernés avant de réessayer avec un nouveau jeton.

Diagnostiquer une erreur

Le callback est une page de connexion avec redirection, pas une API JSON. Le texte affiché peut être générique ; les libellés ci-dessous correspondent aux causes de rejet côté serveur.

StatutCause possibleVérification
403Missing SSO token.Transmettre le JWT dans le paramètre de requête token.
403SSO is not configured for this organization.Activer le SSO et récupérer le secret du bon workspace.
403Invalid SSO token.Vérifier le secret, HS256, l’ordre des champs de l’en-tête et l’encodage Base64URL sans padding.
403Invalid or expired SSO token.Vérifier l’audience, les champs obligatoires, les types et les dates en secondes. Générer un jeton neuf.
403SSO token has already been used.Recommencer depuis votre application avec un nouveau jti. Ne pas recharger une ancienne URL de callback.
403SSO identity conflicts with an existing account.Vérifier la cohérence entre sub, email et comptes existants.
403Workspace suspenduVérifier l’état du workspace auprès de son administrateur.
404SSO désactivé, workspace inconnu ou URL incorrecteReprendre le callback affiché dans les réglages du workspace actif.
429Trop de tentativesRespecter Retry-After avant une nouvelle tentative avec un jeton neuf. Le callback est limité à 20 requêtes par minute.

Vérifier votre intégration

Dans un workspace de test configuré en SSO :

  1. Connectez un utilisateur de test : il doit arriver sur la liste des demandes avec son identité client.
  2. Reconnectez-le avec un jeton neuf : aucun compte supplémentaire ne doit être créé.
  3. Changez son nom ou son email en conservant le sub : vérifiez la mise à jour du compte.
  4. Réutilisez le même jeton, puis essayez un jeton expiré, une mauvaise audience et un mauvais secret : chacun doit être refusé.
  5. Vérifiez qu’un utilisateur non authentifié dans votre application ne peut pas obtenir de jeton, et qu’aucun champ d’identité n’est accepté directement depuis une saisie utilisateur non vérifiée.
  6. Avec un client ordinaire, vérifiez l’absence du bouton Administration et le refus des actions administrateur. Avec un membre déjà autorisé, vérifiez l’accès direct à l’administration du workspace et les commentaires d’équipe, sans accès aux autres workspaces ni à l’administration plateforme.
  7. Déconnectez-vous depuis le portail puis depuis l’administration : les deux accès du workspace doivent être fermés.

Protéger et renouveler le secret

Utilisez HTTPS. Stockez le secret dans la configuration privée de votre backend. Un JWT signé est lisible : il n’est pas chiffré. Évitez de journaliser les URLs contenant token et ne partagez ni secret ni jeton réel dans un ticket ou un outil de diagnostic tiers. Pour un agent, utilisez des valeurs fictives et injectez le secret au déploiement.

Le bouton Régénérer le secret invalide immédiatement les jetons signés avec l’ancien secret. Mettez à jour votre backend au même moment ; aucune période de chevauchement n’est prévue. La rotation du secret ne ferme pas les sessions du workspace déjà ouvertes.

Pour un autre guide ou sa version destinée aux agents, consultez l’index de la documentation et l’index Markdown.