Entra ID・Okta・AD FS で実現する Delphi の SAML シングルサインオン

· コンポーネント
Entra ID・Okta・AD FS で実現する Delphi の SAML シングルサインオン

大口顧客からは遅かれ早かれこう聞かれます。自社のスタッフが会社のアカウントでこのアプリケーションにサインインできますか、と。彼らが求めているのは、また別のユーザー名とパスワードではありません。既に他のすべてで使っている Microsoft Entra ID、Okta、AD FS のログインであり、そこには自社のパスワードポリシー、自社の二要素認証、そして誰かが退職した当日にアカウントを止められる一元管理が含まれます。

顧客の ID 管理チームが期待する答えは SAML 2.0 です。新しいログインコンポーネントの概要では、SAML はわずか一段落しか扱いませんでした。この記事ではフロー全体を扱います。TsgcSAMLServiceProvider が何をするか、ログインページと Assertion Consumer Service のコード、主要な identity provider にアプリケーションを登録する方法、そしてどこにもアカウントを持たずに今日これらすべてをテストする方法です。

SAML サインインの仕組み

登場するのは三者です。あなたのアプリケーションは service provider(SP)です。顧客のディレクトリは identity provider(IdP)です。ブラウザが両者間でメッセージを運ぶため、あなたのサーバーと IdP が直接やり取りすることはありません。

  1. ユーザーがログイン URL を開きます。アプリケーションは AuthnRequest を構築し、ブラウザを IdP へリダイレクトします。
  2. IdP は、会社が定めたパスワード、MFA、条件付きアクセスのルールに従ってユーザーをサインインさせます。
  3. IdP は署名済みの SAMLResponse で応答し、ブラウザはそれをあなたの Assertion Consumer Service(ACS)URL へ POST します。
  4. アプリケーションはレスポンスを検証し、その中で指定されたユーザーのために独自のセッションを作成します。

ステップ 4 こそが SAML 実装が誤りやすい箇所であり、まさにこのコンポーネントが代わりに処理してくれる部分です。

Service Provider をステップごとに見る

TsgcSAMLServiceProvider は HTTP サーバーではありません。SAML メッセージの構築とチェックを行うもので、アプリケーションが既に持っているサーバー、たとえば TsgcWebSocketHTTPServerTsgcHTTPServer のリクエストハンドラーから呼び出します。

起動時に一度 EntityIDAssertionConsumerServiceURLLoadIdPMetadata を設定しておけば、ログインページと ACS は一つのリクエストハンドラーに収まります。

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;

NewRelayStateAddPendingRequestTakePendingRequestCreateUserSession は、あなた自身が実装するコードを表しています。すなわち GUID、RelayState をキーとして各リクエスト id を一度だけ払い出す thread safe なリスト、そしてアプリケーションのセッションクッキーです。属性は Name=Value 形式の行として届くため、oResult.Attributes.Values['email'] のように名前で読み取れます。Entra ID は http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress のような claim URI で属性名を付けます。

RelayState は IdP の署名の対象には含まれません。これは自分の保留中リクエストを見つけるためのキーとしてのみ使用し、確認せずにリダイレクトする URL としては決して使わないでください。

Identity Provider にアプリケーションを登録する

GetMetadata は service provider のメタデータを返します。つまり entity ID と、HTTP-POST binding を用いた ACS URL です。これを /saml/metadata のような URL で公開するか、ファイルに保存して IdP に渡します。どの identity provider も SP の entity ID と ACS URL という同じ二つの値を求めるため、以下の説明は主に各コンソールがそれらをどこで保持しているかについてのものです。いずれの場合も、アサーションの暗号化は無効のままにしてください。

どの IdP であっても最後のステップは同じです。そのメタデータを LoadIdPMetadata に渡します。文書が複数のエンティティを記述している場合、第二引数で自分のものを選択します。

ProcessResponse が検証すること

SAML レスポンスは署名済みの XML 文書であり、よく知られた SAML の脆弱性の多くは、service provider に署名された内容とは異なるものを読ませる手口です。レスポンスは、次のすべての検証に合格した場合にのみ受理されます。

パーサーは DOCTYPE 宣言も拒否するため外部エンティティは存在せず、文書のサイズとネストの深さも制限します。issuer は設定した IdP でなければなりません。最初に失敗した検証で検証処理は停止し、その理由は ErrorMessage に記録されます。これをログに残し、ユーザーにはシンプルな「サインインに失敗しました」というページを表示してください。

アカウントなしで試す

SAML の動作を確認するのに Entra ID のテナントは不要です。Mock SAML は mocksaml.com にある無料のテスト用 identity provider です。任意の service provider を受け入れ、AuthnRequest から audience と ACS URL をそのまま取得するため、登録するものは何もありません。

デモ Demos\26.Authentication\03.SAML_ServiceProviderTsgcWebSocketHTTPServer 上に構築された完全な service provider で、http://localhost:8090 上に /login/acs/metadata の各エンドポイントを提供します。

  1. デモをビルドし、libcrypto-3.dll と libssl-3.dll を実行ファイルの隣に置いてください。これらはデモのフォルダーにあり、OpenSSL が RSA 署名の検証に使用します。
  2. Load IdP metadata をクリックします。既定のソースは mocksaml.com のメタデータ URL です。
  3. Start をクリックし、続けて Open Browser をクリックして、サインインリンクをたどります。
  4. mocksaml.com で、example.com ドメインの任意のユーザー名と任意のパスワードを入力します。
  5. ブラウザが ACS に戻り、ページに NameID、SessionIndex、そして id、email、firstName、lastName の各属性が表示されます。

これが動作したら、http://localhost:8090/metadata を開いて実際の IdP に登録し、デモで IdP のメタデータを読み込んで、もう一度サインインしてみてください。AD FS の場合、AD FS は https しか受け付けないため、まず SSL 有効の状態でデモを実行してください。

現在の制限

ドキュメント

入手方法

TsgcSAMLServiceProvider は、Delphi および C++ Builder 向け sgcWebSockets の Enterprise エディションと All-Access エディションに含まれており、同じコンポーネントは sgcWebSockets .NET の一部でもあります。認証機能だけが必要な場合は、sgcAuth パックに、他のログインコンポーネントとともに含まれています。ユニット名は sgcAuth_SAML_SP で、コンポーネントをフォームに配置するまで、既存のアプリケーションには何の変化もありません。

次に読む

動画で見る

eSeGeCe チャンネルに “SAML single sign-on in Delphi with Entra ID, Okta and AD FS” という短い動画があります。IDE 上のコードと、mocksaml.com を相手にしたデモでのライブサインインの様子を紹介しています。

ご質問、ご意見、あるいは identity provider との接続にお困りですか。お問い合わせください。このコードを書いた本人から返信いたします。