O primeiro cliente grande que assinar um contrato vai pedir login único com o provedor de identidade dele, e muitas vezes isso significa SAML 2.0. O padrão é de 2005, transporta XML por redirecionamentos do navegador e envios de formulário, e as mensagens de erro vão do vago ao silencioso. A maioria dos bugs de SAML, inclusive falhas de segurança graves, vem de não saber exatamente qual mensagem vai para onde e o que o provedor de serviço precisa verificar.
Este post percorre o fluxo iniciado pelo SP mensagem por mensagem com um diagrama de sequência, depois a variante iniciada pelo IdP e seus riscos, a anatomia de uma resposta SAML, a checklist de validação para o seu app, metadados e rotação de certificados, e como o SAML se compara ao OpenID Connect.
As três partes
| Parte | Em termos de SAML | Exemplos |
|---|---|---|
| O navegador do usuário | Agente do usuário | Qualquer navegador |
| A sua aplicação | Provedor de serviço (SP) | O seu app SaaS |
| O sistema de login do cliente | Provedor de identidade (IdP) | Microsoft Entra ID, Okta, Google Workspace, AD FS |
No perfil comum Web Browser SSO, o SP e o IdP nunca falam diretamente um com o outro. Toda mensagem passa pelo navegador, como um redirecionamento ou um formulário enviado automaticamente. O que eles compartilham é configuração, trocada uma vez como metadados: o entity ID de cada lado, a URL de login do IdP, a URL do Assertion Consumer Service (ACS) do SP e o certificado de assinatura do IdP.
O fluxo iniciado pelo SP, passo a passo
O usuário começa no seu app:
- O usuário abre
https://app.example.com/dashboardsem sessão. - O seu app cria um
AuthnRequestcom um ID único, o entity ID dele e a URL ACS, guarda o ID no estado de login pendente e redireciona o navegador para o IdP. Com o binding HTTP-Redirect, a requisição vai na query string comoSAMLRequest(comprimida com deflate e codificada em base64), ao lado de um valorRelayStateque lembra para onde o usuário queria ir. - O IdP autentica o usuário: senha e MFA, ou uma sessão do IdP já existente, caso em que o usuário não vê nada.
- O IdP devolve uma página HTML com um formulário que envia por POST uma
SAMLResponseassinada e o mesmoRelayStatepara a sua URL ACS. Esse é o binding HTTP-POST, e um pequeno script envia o formulário automaticamente. - O seu app valida a resposta, cria o próprio cookie de sessão e redireciona o usuário para a página guardada em
RelayState.
Dali em diante, é a sessão do próprio app que decide quem é o usuário. O SAML só volta a entrar em cena quando essa sessão expira.
A especificação de bindings limita o RelayState a 80 bytes, então guarde a URL de retorno real no servidor e envie uma chave curta.
O que uma resposta SAML contém
Reduzida às partes que o seu app lê, uma resposta fica assim:
<samlp:Response ID="_r81c" InResponseTo="_req42" Destination="https://app.example.com/saml/acs">
<saml:Assertion ID="_a77e">
<saml:Issuer>https://idp.customer.com/entity</saml:Issuer>
<ds:Signature>…</ds:Signature>
<saml:Subject>
<saml:NameID>ana@customer.com</saml:NameID>
<saml:SubjectConfirmationData InResponseTo="_req42"
Recipient="https://app.example.com/saml/acs" NotOnOrAfter="2026-11-02T09:05:00Z"/>
</saml:Subject>
<saml:Conditions NotBefore="2026-11-02T08:59:30Z" NotOnOrAfter="2026-11-02T09:05:00Z">
<saml:AudienceRestriction><saml:Audience>https://app.example.com</saml:Audience></saml:AudienceRestriction>
</saml:Conditions>
<saml:AttributeStatement>…email, name, groups…</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
A asserção é a declaração assinada: quem é o usuário (NameID), quem afirma isso (Issuer), para quem (Audience), por quanto tempo e em resposta a qual requisição (InResponseTo).
A checklist de validação
O seu endpoint ACS é onde a segurança do SAML dá certo ou falha. Ele precisa:
- Verificar a assinatura com o certificado dos metadados do IdP, nunca com um certificado embutido na própria mensagem.
- Ler só o que a assinatura cobre. Ataques de XML signature wrapping inserem uma segunda asserção, sem assinatura, na esperança de que o código verifique um elemento e leia outro. Pesquisadores encontraram essa falha em 11 de 14 grandes frameworks SAML em 2012, e ela continua voltando: o ruby-saml corrigiu em 2024 um bypass de assinatura com CVSS 10.0 (CVE-2024-45409) e em 2025 dois bypasses por diferença entre parsers (CVE-2025-25291 e CVE-2025-25292).
- Verificar o
Issuerem relação ao entity ID do IdP desse cliente. - Verificar o
Audienceem relação ao seu próprio entity ID. - Verificar
DestinationeRecipientem relação à sua URL ACS. - Verificar
NotBeforeeNotOnOrAftercom uma tolerância de um ou dois minutos para diferença de relógio. - Casar o
InResponseTocom um ID deAuthnRequestque você emitiu para esse navegador e ainda não usou. - Rejeitar replays guardando os IDs das asserções até elas expirarem.
- Tratar o
RelayStatecomo não confiável. Redirecione só para caminhos relativos ou para uma lista de permissões, ou você terá um open redirect.
A lição de uma década de vulnerabilidades em SAML: use uma biblioteca mantida, fixe a versão, aplique os patches rápido e nunca escreva a sua própria verificação de assinatura XML.
SSO iniciado pelo IdP e por que ele é mais arriscado
No fluxo iniciado pelo IdP, o usuário começa no IdP, normalmente clicando no bloco do seu app em um painel como o portal da Okta ou o Microsoft My Apps. O IdP envia por POST uma resposta não solicitada para a sua URL ACS. Não houve AuthnRequest, então não há InResponseTo para casar.
| Iniciado pelo SP | Iniciado pelo IdP | |
|---|---|---|
| Começa em | O seu app | O portal do IdP |
AuthnRequest |
Sim | Não |
Verificação do InResponseTo |
Sim | Impossível |
| Proteção contra CSRF de login | Embutida | Exige cuidado extra |
| Deep links | Pelo RelayState |
Costuma cair em uma página padrão |
Sem o InResponseTo, um atacante que obtém uma resposta válida, ou que faz o navegador de uma vítima enviar uma, consegue logar a vítima na conta do atacante ou repetir uma resposta roubada dentro da janela de validade. Se os clientes precisam de blocos no painel, o padrão mais seguro é um bloco que abre a URL de login do seu app, que então inicia um fluxo normal iniciado pelo SP. Se você precisar aceitar respostas não solicitadas, mantenha a validade curta, exija IDs de asserção de uso único e libere isso por cliente, e não de forma global.
Metadados, certificados e ambientes multi-tenant
O IdP de cada cliente é uma relação de confiança separada. Guarde por tenant: o entity ID do IdP, a URL de login, os certificados de assinatura e o mapeamento de atributos. O diagrama de arquitetura SaaS multi-tenant mostra onde essa configuração por tenant costuma ficar.
Certificados de assinatura expiram, e um certificado expirado ou trocado é a causa mais comum de um cliente inteiro de repente não conseguir entrar. Aceite dois certificados por IdP durante uma troca, leia a URL de metadados do IdP periodicamente, se ele publicar uma, e dispare alertas semanas antes do vencimento.
O SAML autentica; ele não mantém a sua lista de usuários sincronizada. Criar contas no primeiro login é fácil, mas remover o acesso quando alguém sai exige provisionamento, normalmente SCIM 2.0 (RFC 7644). Sem isso, um funcionário desativado continua com qualquer sessão que o seu app já tenha emitido.
SAML ou OpenID Connect?
| SAML 2.0 | OpenID Connect | |
|---|---|---|
| Formato | Asserções XML com assinaturas XML | JSON, com ID tokens JWT |
| Transporte | Redirecionamentos do navegador e envios de formulário | Redirecionamentos mais uma requisição de token por back-channel |
| Apps mobile e SPAs | Desajeitado | Projetado para eles |
| Uso típico | SSO corporativo em apps SaaS | Login de consumidores, APIs, SSO corporativo moderno |
Apps novos podem começar com OpenID Connect, mas os compradores corporativos vão continuar pedindo SAML, por isso a maioria dos produtos B2B suporta os dois. O diagrama de fluxo do OAuth 2.0 cobre o protocolo sobre o qual o OpenID Connect é construído.
O diagrama de SAML SSO
Conteúdo do diagrama
- User
- Browser
- Service provider (your app)
- Identity provider
- no IdP session yet
- 1. Open the app
- 2. GET /dashboard (no session)
- 3. 302 to IdP: SAMLRequest + RelayState
- 4. GET /sso?SAMLRequest=…&RelayState=… (HTTP-Redirect)
- 5. Login page
- 6. Password + MFA
- 7. HTML form: signed SAMLResponse + RelayState
- 8. POST /saml/acs (HTTP-POST, auto-submit)
- 9. 302 /dashboard + session cookie
- 10. GET /dashboard (cookie)
- 11. 200 OK
Como ler:
- Quatro participantes. O usuário, o navegador, o seu app como provedor de serviço e o provedor de identidade. O tempo corre para baixo.
- O redirecionamento leva o
AuthnRequeste oRelayStatedo seu app até o IdP, pelo navegador. - O fragmento opcional é a etapa de login. Com uma sessão do IdP já existente, ele é pulado e o usuário só vê um redirecionamento rápido.
- O envio do formulário entrega a
SAMLResponseassinada à URL ACS. O seu app a valida e define o próprio cookie de sessão. - O bloco Markdown lista as verificações que o endpoint ACS precisa fazer.
Desenhar fluxos de SSO que ajudam a depurar
- Desenhe o navegador como um participante próprio. O SAML é um protocolo de front-channel; esconder o navegador esconde onde as mensagens podem ser interceptadas ou repetidas.
- Escreva o binding em cada seta: redirecionamento ou POST, e o nome do parâmetro. A maioria dos erros de configuração é uma URL ou um binding errado.
- Coloque os IDs nas mensagens. Mostrar que o
InResponseTorepete o ID da requisição explica por que ele é verificado. - Desenhe o fluxo iniciado pelo IdP separadamente, com o risco anotado, em vez de como um ramo do diagrama principal.
O exemplo de diagrama de sequência cobre participantes, fragmentos e rótulos com mais detalhe, e o diagrama de arquitetura zero trust mostra onde o provedor de identidade fica em um modelo de acesso mais amplo.
Monte no Archboard
- Entre com Google ou GitHub e crie um diagrama.
- Adicione um Ator do pack Sequência para o usuário e depois três nós Linha de vida: Browser, Service provider e Identity provider.
- Desenhe as mensagens de cima para baixo como arestas entre as linhas de vida: a primeira requisição, o redirecionamento com
SAMLRequesteRelayState, a requisição ao IdP, o formulário comSAMLResponse, o POST na URL ACS e o redirecionamento final com o cookie de sessão. - Adicione um Fragmento combinado do pack Sequência com o operador
optem volta da etapa de login e MFA. - Adicione um bloco Markdown do pack Básico com a checklist de validação.
O exemplo usa 6 nós de diagrama e fica dentro do plano Free.
Perguntas frequentes
Qual é a diferença entre SSO iniciado pelo SP e iniciado pelo IdP? O login iniciado pelo SP começa no seu app, que envia um AuthnRequest ao IdP. O login iniciado pelo IdP começa no portal do IdP, que envia por POST uma resposta não solicitada ao seu app. O segundo não tem uma requisição para casar com a resposta, o que enfraquece a proteção contra replay e CSRF de login.
O que é uma URL ACS? A URL do Assertion Consumer Service é o endpoint do seu app que recebe a resposta SAML por HTTP POST. Ela é configurada no IdP e precisa bater com os valores Destination e Recipient de cada resposta.
Para que serve o RelayState? Ele carrega o lugar que o usuário queria acessar, para que o seu app o leve até lá depois do login. É limitado a 80 bytes e é uma entrada não confiável, então use-o como chave para um estado guardado no servidor ou valide-o contra uma lista de permissões.
O SAML está ultrapassado? Ele é mais antigo que o OpenID Connect e mais difícil de implementar com segurança, mas continua sendo o padrão que muitos provedores de identidade e compradores corporativos esperam. A maioria dos produtos B2B suporta os dois.
O SAML remove usuários quando eles saem da empresa? Não. O SAML só autentica no login. O desprovisionamento precisa de SCIM ou de outra integração de provisionamento, além de durações de sessão razoáveis no seu app.