Single Sign-On SAML em Delphi com Entra ID, Okta e AD FS

· Componentes
Single Sign-On SAML em Delphi com Entra ID, Okta e AD FS

Mais cedo ou mais tarde, um cliente grande faz a pergunta: nossa equipe pode fazer login na sua aplicação com a conta corporativa? Eles não estão falando de mais um nome de usuário e senha. Estão falando do login do Microsoft Entra ID, Okta ou AD FS que já usam para tudo o mais, com a própria política de senhas, o próprio segundo fator e um único lugar para desativar uma conta no dia em que alguém sai da empresa.

A resposta que a equipe de identidade deles espera é SAML 2.0. Na visão geral dos novos componentes de login, SAML ganhou apenas um parágrafo. Este post é o fluxo completo: o que TsgcSAMLServiceProvider faz, o código de uma página de login e de um Assertion Consumer Service, como registrar sua aplicação nos identity providers mais comuns, e como testar tudo isso hoje mesmo sem ter conta em lugar nenhum.

Como funciona o login SAML

Três partes participam. Sua aplicação é o service provider (SP). O diretório do cliente é o identity provider (IdP). O navegador transporta as mensagens entre os dois, de modo que seu servidor e o IdP nunca conversam diretamente.

  1. O usuário abre a URL de login. Sua aplicação monta um AuthnRequest e redireciona o navegador para o IdP.
  2. O IdP autentica o usuário, seguindo as regras de senha, MFA ou acesso condicional que a empresa tiver definido.
  3. O IdP responde com um SAMLResponse assinado, e o navegador o envia via POST para a URL do seu Assertion Consumer Service (ACS).
  4. Sua aplicação valida a resposta e cria sua própria sessão para o usuário que ela indica.

O passo quatro é onde as implementações de SAML erram, e é exatamente a parte que o componente faz por você.

O service provider, passo a passo

TsgcSAMLServiceProvider não é um servidor HTTP. Ele monta e verifica as mensagens SAML, e você o chama a partir do request handler do servidor que sua aplicação já possui, por exemplo um TsgcWebSocketHTTPServer ou um TsgcHTTPServer.

Com EntityID, AssertionConsumerServiceURL e LoadIdPMetadata definidos uma vez na inicialização, a página de login e o ACS cabem em um único request handler:

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 e CreateUserSession representam seu próprio código: um GUID, uma lista thread safe indexada por RelayState que entrega cada id de solicitação apenas uma vez, e o cookie de sessão da sua aplicação. Os atributos chegam como linhas Name=Value, então oResult.Attributes.Values['email'] lê um pelo nome. O Entra ID os nomeia com claim URIs como http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.

O RelayState não está coberto pela assinatura do IdP. Use-o como chave para encontrar sua própria solicitação pendente, nunca como uma URL para a qual você redireciona sem verificar.

Registre sua aplicação no identity provider

GetMetadata retorna os metadados do service provider: seu entity ID e sua URL de ACS com o binding HTTP-POST. Sirva-os em uma URL como /saml/metadata, ou salve-os em um arquivo, e entregue-os ao IdP. Todo identity provider pede os mesmos dois valores, o entity ID do SP e a URL do ACS, então as notas abaixo tratam principalmente de onde cada console guarda esses dados. Em todos eles, deixe a criptografia de asserções desativada.

Qualquer que seja o IdP, o último passo é o mesmo: passe os metadados dele para LoadIdPMetadata. Quando o documento descreve várias entidades, o segundo parâmetro escolhe a sua.

O que ProcessResponse verifica

Uma resposta SAML é um documento XML assinado, e a maioria das vulnerabilidades SAML conhecidas são formas de fazer um service provider ler algo diferente do que foi assinado. Uma resposta só é aceita quando todas estas verificações passam:

O parser também recusa declarações DOCTYPE, portanto não há entidades externas, e limita o tamanho e a profundidade de aninhamento do documento. O issuer precisa ser o IdP que você configurou. A primeira verificação que falhar interrompe a validação, e seu motivo fica em ErrorMessage: registre-o em log, e mostre ao usuário uma página simples de “falha ao entrar”.

Teste sem ter conta

Você não precisa de um tenant Entra ID para ver o SAML funcionando. Mock SAML é um identity provider de teste gratuito em mocksaml.com. Ele aceita qualquer service provider e pega a audience e a URL de ACS do AuthnRequest, então não há nada para registrar.

A demo Demos\26.Authentication\03.SAML_ServiceProvider é um service provider completo sobre um TsgcWebSocketHTTPServer, com os endpoints /login, /acs e /metadata em http://localhost:8090:

  1. Compile a demo e mantenha libcrypto-3.dll e libssl-3.dll junto ao executável. Elas estão na pasta da demo, e o OpenSSL verifica as assinaturas RSA.
  2. Clique em Load IdP metadata. A fonte padrão é a URL de metadados do mocksaml.com.
  3. Clique em Start, depois em Open Browser, e siga o link de login.
  4. No mocksaml.com, digite qualquer nome de usuário do domínio example.com e qualquer senha.
  5. O navegador volta para o ACS, e a página mostra o NameID, o SessionIndex e os atributos id, email, firstName e lastName.

Quando isso funcionar, abra http://localhost:8090/metadata, registre-o no seu IdP real, carregue os metadados do IdP na demo e faça login novamente. Para AD FS, execute a demo primeiro com SSL, porque o AD FS só aceita https.

Limites atuais

Documentação

Onde conseguir

TsgcSAMLServiceProvider está incluído nas edições Enterprise e All-Access do sgcWebSockets, para Delphi e C++ Builder, e o mesmo componente faz parte do sgcWebSockets .NET. Se você só precisa de autenticação, o pacote sgcAuth o oferece junto com os demais componentes de login. A unit é sgcAuth_SAML_SP, e nada muda em uma aplicação existente até você colocar o componente em um formulário.

Leia também

Assista ao vídeo

Há um vídeo curto, “SAML single sign-on in Delphi with Entra ID, Okta and AD FS”, no canal da eSeGeCe. Ele mostra o código na IDE e um login ao vivo com a demo contra o mocksaml.com.

Perguntas, feedback ou ajuda para conectar seu identity provider? Fale conosco. Você receberá uma resposta das pessoas que escreveram o código.