Authentification unique SAML en Delphi avec Entra ID, Okta et AD FS

· Composants
Authentification unique SAML en Delphi avec Entra ID, Okta et AD FS

Tôt ou tard, un grand client pose la question : notre personnel peut-il se connecter à votre application avec son compte d'entreprise ? Il ne s'agit pas d'un nom d'utilisateur et d'un mot de passe de plus. Il s'agit de la connexion Microsoft Entra ID, Okta ou AD FS qu'il utilise déjà pour tout le reste, avec sa propre politique de mots de passe, sa propre double authentification et un seul endroit pour désactiver un compte le jour où quelqu'un quitte l'entreprise.

La réponse attendue par leur équipe identité est SAML 2.0. Dans la présentation des nouveaux composants de connexion, SAML n'avait droit qu'à un seul paragraphe. Cet article couvre le flux complet : ce que fait TsgcSAMLServiceProvider, le code d'une page de connexion et d'un Assertion Consumer Service, comment enregistrer votre application auprès des fournisseurs d'identité courants, et comment tester tout cela dès aujourd'hui sans compte nulle part.

Comment fonctionne la connexion SAML

Trois parties interviennent. Votre application est le service provider (SP). L'annuaire du client est le identity provider (IdP). Le navigateur transporte les messages entre les deux, de sorte que votre serveur et l'IdP ne communiquent jamais directement.

  1. L'utilisateur ouvre votre URL de connexion. Votre application construit un AuthnRequest et redirige le navigateur vers l'IdP.
  2. L'IdP connecte l'utilisateur, avec les règles de mot de passe, de MFA ou d'accès conditionnel que l'entreprise a définies.
  3. L'IdP répond avec un SAMLResponse signé, et le navigateur le poste vers l'URL de votre Assertion Consumer Service (ACS).
  4. Votre application valide la réponse et crée sa propre session pour l'utilisateur qu'elle désigne.

C'est à l'étape quatre que les implémentations SAML se trompent, et c'est exactement la partie que le composant fait pour vous.

Le service provider, étape par étape

TsgcSAMLServiceProvider n'est pas un serveur HTTP. Il construit et vérifie les messages SAML, et vous l'appelez depuis le gestionnaire de requêtes du serveur que votre application possède déjà, par exemple un TsgcWebSocketHTTPServer ou un TsgcHTTPServer.

Avec EntityID, AssertionConsumerServiceURL et LoadIdPMetadata configurés une fois au démarrage, la page de connexion et l'ACS tiennent dans un seul gestionnaire de requêtes :

uses
  sgcAuth_SAML_SP;

procedure TMyApp.OnCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  vRelayState, vRequestID: string;
  oResult: TsgcSAMLResult;
begin
  if ARequestInfo.Document = '/saml/login' then
  begin
    // 1. send the browser to the identity provider
    vRelayState := NewRelayState;
    AResponseInfo.Redirect(FSAML.GetAuthnRequestRedirectURL(vRelayState,
      vRequestID));
    // 2. keep the request id, the response must answer it
    AddPendingRequest(vRelayState, vRequestID);
  end
  else if (ARequestInfo.Document = '/saml/acs') and
    SameText(ARequestInfo.Command, 'POST') then
  begin
    // 3. the browser posts SAMLResponse and RelayState back
    vRelayState := ARequestInfo.Params.Values['RelayState'];
    vRequestID := TakePendingRequest(vRelayState);
    oResult := TsgcSAMLResult.Create;
    try
      if FSAML.ProcessResponse(ARequestInfo.Params.Values['SAMLResponse'],
        vRelayState, vRequestID, oResult) then
      begin
        // 4. signed in: create your own session for this user
        CreateUserSession(AResponseInfo, oResult.NameID, oResult.SessionIndex);
        AResponseInfo.Redirect('/');
      end
      else
        AResponseInfo.ResponseNo := 403; // log oResult.ErrorMessage
    finally
      oResult.Free;
    end;
  end;
end;

NewRelayState, AddPendingRequest, TakePendingRequest et CreateUserSession représentent votre propre code : un GUID, une liste thread safe indexée par RelayState qui délivre chaque id de requête une seule fois, et le cookie de session de votre application. Les attributs arrivent sous forme de lignes Name=Value, donc oResult.Attributes.Values['email'] en lit un par son nom. Entra ID les nomme avec des claim URI comme http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.

Le RelayState n'est pas couvert par la signature de l'IdP. Utilisez-le comme clé pour retrouver votre propre requête en attente, jamais comme une URL vers laquelle vous redirigez sans la vérifier.

Enregistrer votre application auprès du fournisseur d'identité

GetMetadata renvoie les métadonnées du service provider : votre entity ID et votre URL d'ACS avec le binding HTTP-POST. Servez-les à une URL telle que /saml/metadata, ou enregistrez-les dans un fichier, et transmettez-les à l'IdP. Chaque fournisseur d'identité demande les deux mêmes valeurs, l'entity ID du SP et l'URL de l'ACS, donc les notes ci-dessous portent surtout sur l'endroit où chaque console les conserve. Dans tous les cas, laissez le chiffrement des assertions désactivé.

Quel que soit l'IdP, la dernière étape est la même : transmettez ses métadonnées à LoadIdPMetadata. Lorsque le document décrit plusieurs entités, le second paramètre sélectionne la vôtre.

Ce que ProcessResponse vérifie

Une réponse SAML est un document XML signé, et la plupart des vulnérabilités SAML connues sont des façons de faire lire à un service provider autre chose que ce qui a été signé. Une réponse n'est acceptée que si chacun de ces contrôles est validé :

L'analyseur refuse également les déclarations DOCTYPE, il n'y a donc pas d'entités externes, et il limite la taille et la profondeur d'imbrication du document. L'issuer doit être l'IdP que vous avez configuré. Le premier contrôle qui échoue arrête la validation et sa raison se trouve dans ErrorMessage : consignez-la, et affichez à l'utilisateur une simple page “échec de la connexion”.

Testez-le sans compte

Vous n'avez pas besoin d'un tenant Entra ID pour voir SAML fonctionner. Mock SAML est un fournisseur d'identité de test gratuit sur mocksaml.com. Il accepte n'importe quel service provider et reprend l'audience et l'URL d'ACS depuis l'AuthnRequest, il n'y a donc rien à enregistrer.

La démo Demos\26.Authentication\03.SAML_ServiceProvider est un service provider complet sur un TsgcWebSocketHTTPServer, avec les endpoints /login, /acs et /metadata sur http://localhost:8090 :

  1. Compilez la démo et conservez libcrypto-3.dll et libssl-3.dll à côté de l'exécutable. Elles se trouvent dans le dossier de la démo, et OpenSSL vérifie les signatures RSA.
  2. Cliquez sur Load IdP metadata. La source par défaut est l'URL de métadonnées de mocksaml.com.
  3. Cliquez sur Start, puis sur Open Browser, et suivez le lien de connexion.
  4. Sur mocksaml.com, saisissez n'importe quel nom d'utilisateur du domaine example.com et n'importe quel mot de passe.
  5. Le navigateur revient sur l'ACS, et la page affiche le NameID, le SessionIndex et les attributs id, email, firstName et lastName.

Une fois que cela fonctionne, ouvrez http://localhost:8090/metadata, enregistrez-le auprès de votre véritable IdP, chargez les métadonnées de l'IdP dans la démo et connectez-vous à nouveau. Pour AD FS, exécutez d'abord la démo avec SSL, car AD FS n'accepte que https.

Limites actuelles

Documentation

Où l'obtenir

TsgcSAMLServiceProvider est inclus dans les éditions Enterprise et All-Access de sgcWebSockets, pour Delphi et C++ Builder, et le même composant fait partie de sgcWebSockets .NET. Si vous n'avez besoin que de l'authentification, le pack sgcAuth le propose avec les autres composants de connexion. L'unit est sgcAuth_SAML_SP, et rien ne change dans une application existante tant que vous n'avez pas déposé le composant sur une form.

À lire aussi

Voir la vidéo

Une courte vidéo, “SAML single sign-on in Delphi with Entra ID, Okta and AD FS”, est disponible sur la chaîne eSeGeCe. Elle montre le code dans l'IDE et une connexion en direct avec la démo face à mocksaml.com.

Des questions, des retours, ou besoin d'aide pour connecter votre fournisseur d'identité ? Contactez-nous. Vous recevrez une réponse des personnes qui ont écrit le code.