Como Usar Single Sign-On: SAML 2.0, OIDC, LDAP e Federação de Identidades em Produção Já leu

O que é Single Sign-On (SSO) e por que você deve dominar isso Single Sign-On é um mecanismo de autenticação que permite ao usuário fazer login uma única vez e acessar múltiplas aplicações sem precisar autenticar-se novamente. Imagine que você entra em um portal corporativo uma vez; a partir daí, pode acessar email, documentos, sistemas internos e ferramentas terceirizadas sem inserir suas credenciais novamente. Isso não é apenas conveniência — é segurança, produtividade e conformidade regulatória em um só lugar. Como profissional que trabalha em ambientes empresariais há anos, posso afirmar que dominar SSO é essencial para arquitetos, desenvolvedores backend e especialistas em segurança. Você não implementa mais autenticação isolada; você integra com infraestruturas de identidade existentes. A diferença? Enquanto uma aplicação convencional guarda senhas em seu banco de dados, com SSO você delega a responsabilidade para um provedor de identidades confiável. Isso reduz vetores de ataque, simplifica gestão de acessos e cumpre requisitos de governança. Entendendo os Protocolos: SAML

O que é Single Sign-On (SSO) e por que você deve dominar isso

Single Sign-On é um mecanismo de autenticação que permite ao usuário fazer login uma única vez e acessar múltiplas aplicações sem precisar autenticar-se novamente. Imagine que você entra em um portal corporativo uma vez; a partir daí, pode acessar email, documentos, sistemas internos e ferramentas terceirizadas sem inserir suas credenciais novamente. Isso não é apenas conveniência — é segurança, produtividade e conformidade regulatória em um só lugar.

Como profissional que trabalha em ambientes empresariais há anos, posso afirmar que dominar SSO é essencial para arquitetos, desenvolvedores backend e especialistas em segurança. Você não implementa mais autenticação isolada; você integra com infraestruturas de identidade existentes. A diferença? Enquanto uma aplicação convencional guarda senhas em seu banco de dados, com SSO você delega a responsabilidade para um provedor de identidades confiável. Isso reduz vetores de ataque, simplifica gestão de acessos e cumpre requisitos de governança.

Entendendo os Protocolos: SAML 2.0, OIDC e LDAP

SAML 2.0: O Protocolo de Federação em Nível Empresarial

SAML (Security Assertion Markup Language) 2.0 é um protocolo baseado em XML que permite a troca segura de informações de autenticação e autorização entre provedores de identidades (IdP) e provedores de serviço (SP). Ele é o padrão mais consolidado em ambientes corporativos, especialmente em empresas com múltiplas subsidiárias ou parceiros.

O fluxo SAML funciona assim: você acessa uma aplicação (SP), que o redireciona para o provedor de identidades. Lá, você autentica. O IdP cria uma asserção — um documento XML assinado e cifrado contendo seu identificador e atributos (nome, email, departamento). Esta asserção é enviada de volta para a aplicação, que a valida e cria uma sessão. O ponto crítico: tudo é baseado em assinatura digital. Ninguém forja uma asserção sem a chave privada do IdP.

# Exemplo: Consumir uma asserção SAML 2.0 em Python com python3-saml
from saml2 import ATTRIBUTES
from saml2.client import Saml2Client
from saml2.config import Config

# Configuração do cliente SAML (Service Provider)
settings = {
    'debug': True,
    'sp': {
        'entityID': 'https://aplicacao.exemplo.com/saml/metadata/',
        'assertionConsumerService': {
            'url': 'https://aplicacao.exemplo.com/saml/acs/',
            'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
        },
        'singleLogoutService': {
            'url': 'https://aplicacao.exemplo.com/saml/sls/',
            'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
        },
        'x509cert': 'seu_certificado_publico_aqui',
        'privateKey': 'sua_chave_privada_aqui'
    },
    'idp': {
        'entityID': 'https://idp.exemplo.com/metadata/',
        'singleSignOnService': {
            'url': 'https://idp.exemplo.com/sso/',
            'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
        },
        'x509cert': 'certificado_publico_do_idp'
    }
}

# Criar cliente SAML
saml_client = Saml2Client(config=Config(settings=settings))

# Gerar requisição de autenticação
request_id, info = saml_client.prepare_for_authenticate(
    relay_state='state_da_aplicacao'
)

# O usuário será redirecionado para: info['url']
print(f"Redirecionar usuário para: {info['url']}")

# Após autenticação no IdP, processar resposta
def processar_resposta_saml(saml_response):
    """
    saml_response é o formulário POST recebido do IdP
    """
    authn_response = saml_client.parse_response_doc(
        saml_response,
        entity_id=settings['idp']['entityID']
    )

    if authn_response.is_authenticated():
        user_id = authn_response.get_subject()
        attributes = authn_response.get_attributes()

        return {
            'user_id': user_id,
            'email': attributes.get('email', [''])[0],
            'nome': attributes.get('name', [''])[0],
            'grupos': attributes.get('groups', [])
        }
    return None

OpenID Connect (OIDC): SSO Moderno Baseado em OAuth 2.0

OpenID Connect é uma camada de identidade sobre OAuth 2.0, projetada para o mundo das APIs e aplicações modernas. Enquanto SAML é verbose e baseado em XML, OIDC usa JSON e HTTP, sendo mais leve e adequado para aplicações web, mobile e single-page applications (SPAs).

A diferença fundamental: OAuth 2.0 trata de autorização (permissões), OIDC adiciona autenticação (quem você é). O fluxo OIDC é simples: a aplicação redireciona para o provedor de identidades com um client_id, o usuário autentica, e o IdP retorna um ID token (JWT) contendo identidade e um access token para chamar APIs. É tudo JSON, tudo stateless, perfeito para microsserviços.

// Exemplo: Implementar OIDC em uma aplicação Node.js com passport-openidconnect
const express = require('express');
const passport = require('passport');
const { Strategy: OpenIDConnectStrategy } = require('passport-openidconnect');
const session = require('express-session');

const app = express();

// Configurar estratégia OIDC
passport.use('oidc', new OpenIDConnectStrategy({
    issuer: 'https://idp.exemplo.com',
    authorizationURL: 'https://idp.exemplo.com/authorize',
    tokenURL: 'https://idp.exemplo.com/token',
    userInfoURL: 'https://idp.exemplo.com/userinfo',
    clientID: 'sua_client_id_aqui',
    clientSecret: 'sua_client_secret_aqui',
    callbackURL: 'http://localhost:3000/oauth2/callback'
}, function verify(issuer, sub, profile, done) {
    // sub = subject (identificador único do usuário)
    // profile contém claims do token (email, nome, etc)
    return done(null, {
        id: sub,
        email: profile.email,
        name: profile.name,
        issuer: issuer
    });
}));

passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));

// Middleware de sessão
app.use(session({
    secret: 'seu_secret_de_sessao',
    resave: false,
    saveUninitialized: true
}));

app.use(passport.initialize());
app.use(passport.session());

// Rota de login: redireciona para IdP
app.get('/login', passport.authenticate('oidc'));

// Rota de callback: IdP redireciona aqui após autenticação
app.get('/oauth2/callback', 
    passport.authenticate('oidc', { failureRedirect: '/login' }),
    (req, res) => {
        res.redirect('/');
    }
);

// Rota protegida
app.get('/dashboard', (req, res) => {
    if (!req.isAuthenticated()) {
        return res.redirect('/login');
    }
    res.json({
        message: 'Bem-vindo',
        user: req.user
    });
});

app.listen(3000, () => console.log('Servidor rodando na porta 3000'));

LDAP: Autenticação Contra Diretórios Corporativos

LDAP (Lightweight Directory Access Protocol) não é exatamente um protocolo de SSO, mas sim um protocolo para buscar e autenticar usuários contra um diretório centralizado. Em ambientes corporativos, LDAP é frequentemente usado como backend para armazenar identidades, particularmente em redes Active Directory do Windows. Você pode usá-lo para validar credenciais sem implementar um protocolo federado completo.

O fluxo LDAP é direto: sua aplicação se conecta ao servidor LDAP, busca o usuário pelo nome ou email e tenta fazer bind (autenticação) com a senha fornecida. Se o bind funciona, o usuário é autenticado. É simples, mas não oferece SSO real — você precisa autenticar em cada aplicação. Porém, é muito usado como complemento: SAML e OIDC frequentemente usam LDAP como provedor de identidades.

# Exemplo: Autenticação via LDAP em Python
import ldap
import ldap.modlist as modlist

class LDAPAuthenticator:
    def __init__(self, ldap_server, ldap_port=389):
        self.ldap_server = ldap_server
        self.ldap_port = ldap_port
        self.connection = None

    def conectar(self, admin_dn, admin_password):
        """
        Conecta ao servidor LDAP com credenciais administrativas
        """
        try:
            self.connection = ldap.initialize(
                f'ldap://{self.ldap_server}:{self.ldap_port}'
            )
            self.connection.simple_bind_s(admin_dn, admin_password)
            print("Conexão LDAP estabelecida")
            return True
        except ldap.INVALID_CREDENTIALS:
            print("Credenciais de admin inválidas")
            return False

    def autenticar_usuario(self, username, password):
        """
        Autentica um usuário contra LDAP
        """
        try:
            # Buscar o DN do usuário
            search_filter = f'(uid={username})'
            result = self.connection.search_s(
                'ou=users,dc=exemplo,dc=com',
                ldap.SCOPE_SUBTREE,
                search_filter,
                ['mail', 'cn', 'memberOf']
            )

            if not result:
                return None

            user_dn = result[0][0]
            user_attrs = result[0][1]

            # Tentar fazer bind com a senha do usuário
            user_connection = ldap.initialize(
                f'ldap://{self.ldap_server}:{self.ldap_port}'
            )
            user_connection.simple_bind_s(user_dn, password)

            # Se chegou aqui, autenticação bem-sucedida
            return {
                'username': username,
                'dn': user_dn,
                'email': user_attrs.get(b'mail', [b''])[0].decode(),
                'nome': user_attrs.get(b'cn', [b''])[0].decode(),
                'grupos': [g.decode() for g in user_attrs.get(b'memberOf', [])]
            }

        except ldap.INVALID_CREDENTIALS:
            print(f"Autenticação falhou para {username}")
            return None
        except ldap.LDAPError as e:
            print(f"Erro LDAP: {e}")
            return None

# Uso
ldap_auth = LDAPAuthenticator('ldap.exemplo.com')
if ldap_auth.conectar('cn=admin,dc=exemplo,dc=com', 'senha_admin'):
    usuario = ldap_auth.autenticar_usuario('joao.silva', 'senha_joao')
    if usuario:
        print(f"Usuário autenticado: {usuario['nome']} ({usuario['email']})")

Federação de Identidades: Integrando Tudo

O Conceito: Confiança Entre Domínios

Federação de identidades é quando você estabelece uma relação de confiança entre múltiplos domínios ou organizações. Imagine dois bancos diferentes: você autentica em um e, por acordos de segurança, pode acessar serviços do outro sem autenticar novamente. A federação é justamente isso — a capacidade de uma organização confiar nos provedores de identidade de outra.

Em termos práticos, federação significa que seu provedor de identidades (seu banco) assina digitalmente as asserções/tokens de seus usuários, e os serviços parceiros (o outro banco) validam essa assinatura usando a chave pública publicada em um metadado. Não há troca de senhas entre organizações. Não há integração direta com bases de dados. Há apenas confiança criptográfica.

Arquitetura Federada na Prática

Uma arquitetura federada típica envolve: (1) um servidor de identidades central (IdP) que autentica usuários e emite tokens/asserções, (2) múltiplas aplicações (SPs) que confiam nesse IdP, (3) metadados XML ou JSON que publicam configurações de segurança. Você pode ter uma federação simples (um IdP, múltiplas aplicações) ou complexa (múltiplos IdPs, múltiplos SPs, com regras de descoberta).

# Exemplo: Estrutura de diretórios para um IdP com SAML 2.0
idp-saml/
├── certs/
│   ├── idp_private.key        # Chave privada do IdP
│   └── idp_public.crt         # Certificado público
├── metadata.xml               # Metadados do IdP
├── config.yml                 # Configuração do IdP
└── users.db                   # Base de usuários (LDAP, SQL, etc)

# Conteúdo mínimo de metadata.xml
<?xml version="1.0" encoding="UTF-8"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" 
                  entityID="https://idp.exemplo.com/saml">
    <IDPSSODescriptor 
        protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <KeyDescriptor use="signing">
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <X509Data>
                    <X509Certificate>
                        MIICXDCCAcWgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBuMQswCQYD...
                    </X509Certificate>
                </X509Data>
            </KeyInfo>
        </KeyDescriptor>
        <SingleSignOnService 
            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            Location="https://idp.exemplo.com/sso" />
        <SingleLogoutService 
            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
            Location="https://idp.exemplo.com/logout" />
    </IDPSSODescriptor>
</EntityDescriptor>

Integrando LDAP como Backend de um IdP

Em muitos cenários reais, você não constrói um IdP do zero. Você usa uma solução pronta (Keycloak, Okta, Azure AD) que oferece SAML e OIDC. Mas para dominar o tema, é importante entender que essas soluções frequentemente usam LDAP como backend para armazenar usuários. Você autentica contra LDAP, e o IdP emite tokens SAML/OIDC.

# Exemplo: Configuração do Keycloak integrando LDAP
# arquivo: keycloak-ldap-config.yaml

keycloak:
  realm: exemplo
  admin_user: admin@keycloak.local
  admin_password: senha_segura

ldap_provider:
  provider_name: "LDAP Corporativo"
  connection_url: "ldap://ldap.exemplo.com:389"
  users_dn: "ou=users,dc=exemplo,dc=com"
  bind_dn: "cn=admin,dc=exemplo,dc=com"
  bind_credential: "senha_admin"
  user_object_classes: "inetOrgPerson"

  # Mapear atributos LDAP para claims do token
  user_name_ldap_attribute: "uid"
  email_ldap_attribute: "mail"
  first_name_ldap_attribute: "givenName"
  last_name_ldap_attribute: "sn"

  # Sincronização
  sync_registrations: true
  periodic_full_sync: true
  sync_period_minutes: 120

# Após esta configuração, o Keycloak:
# 1. Busca usuários no LDAP
# 2. Autentica contra LDAP
# 3. Emite tokens SAML/OIDC contendo atributos mapeados

Cenário Real: Aplicação com Federação OIDC

Suponha que você construa uma aplicação SaaS que precisa permitir que empresas façam SSO com seus provedores de identidade próprios. A abordagem: sua aplicação suporta OIDC como cliente (você é o SP), mas cada empresa usa seu próprio IdP (múltiplos IdPs). Isso é federação de verdade.

// Exemplo: Aplicação multitenante com suporte a múltiplos OIDC issuers
const express = require('express');
const jwt = require('jsonwebtoken');
const axios = require('axios');

const app = express();

// Registrar configurações de IdPs de diferentes clientes
const idpConfigs = {
    'empresa-a': {
        issuer: 'https://idp.empresa-a.com',
        authorizationURL: 'https://idp.empresa-a.com/oauth/authorize',
        tokenURL: 'https://idp.empresa-a.com/oauth/token',
        userInfoURL: 'https://idp.empresa-a.com/oauth/userinfo',
        clientID: 'app-client-a',
        clientSecret: 'secret-a'
    },
    'empresa-b': {
        issuer: 'https://idp.empresa-b.com',
        authorizationURL: 'https://idp.empresa-b.com/oauth/authorize',
        tokenURL: 'https://idp.empresa-b.com/oauth/token',
        userInfoURL: 'https://idp.empresa-b.com/oauth/userinfo',
        clientID: 'app-client-b',
        clientSecret: 'secret-b'
    }
};

// Rota de login: determina o IdP pelo tenant
app.get('/login/:tenant', (req, res) => {
    const { tenant } = req.params;
    const config = idpConfigs[tenant];

    if (!config) {
        return res.status(404).json({ error: 'Tenant não encontrado' });
    }

    // Gerar state para CSRF protection
    const state = Math.random().toString(36).substring(7);
    req.session.oauth_state = state;
    req.session.oauth_tenant = tenant;

    // Redirecionar para IdP do tenant
    const authUrl = new URL(config.authorizationURL);
    authUrl.searchParams.append('client_id', config.clientID);
    authUrl.searchParams.append('response_type', 'code');
    authUrl.searchParams.append('scope', 'openid email profile');
    authUrl.searchParams.append('redirect_uri', 'http://localhost:3000/callback');
    authUrl.searchParams.append('state', state);

    res.redirect(authUrl.toString());
});

// Callback: processar resposta do IdP
app.get('/callback', async (req, res) => {
    const { code, state } = req.query;
    const { oauth_state, oauth_tenant } = req.session;

    // Validar state
    if (state !== oauth_state) {
        return res.status(403).json({ error: 'CSRF validation failed' });
    }

    const config = idpConfigs[oauth_tenant];

    try {
        // Trocar authorization code por tokens
        const tokenResponse = await axios.post(config.tokenURL, {
            grant_type: 'authorization_code',
            code,
            client_id: config.clientID,
            client_secret: config.clientSecret,
            redirect_uri: 'http://localhost:3000/callback'
        });

        const { id_token, access_token } = tokenResponse.data;

        // Decodificar ID token (sem validar assinatura por simplicidade)
        // Em produção, validar assinatura contra JWKS publicada pelo IdP
        const decoded = jwt.decode(id_token);

        // Criar sessão do usuário
        req.session.user = {
            id: decoded.sub,
            email: decoded.email,
            name: decoded.name,
            tenant: oauth_tenant,
            issuer: config.issuer
        };

        res.redirect('/dashboard');
    } catch (error) {
        res.status(500).json({ error: 'Token exchange failed', details: error.message });
    }
});

// Rota protegida
app.get('/dashboard', (req, res) => {
    if (!req.session.user) {
        return res.redirect('/login');
    }
    res.json({
        message: 'Bem-vindo',
        user: req.session.user
    });
});

app.listen(3000, () => console.log('App federada rodando na porta 3000'));

Comparação Prática e Quando Usar Cada Uma

Aspecto SAML 2.0 OIDC LDAP
Padrão XML, federação JSON, OAuth 2.0 Protocolo de diretório
Caso de Uso Corporativo, B2B SPA, mobile, APIs Autenticação local, backend
Overhead Alto (XML verbose) Baixo (JSON) Mínimo
SSO Real Sim Sim Não (apenas autenticação)
Curva de Aprendizado Íngreme Moderada Leve
Segurança Excelente (assinatura) Excelente (JWT) Boa (depende de TLS)

SAML 2.0 é sua escolha quando trabalha em ambientes corporativos tradicionais, especialmente integrações B2B ou com provedores public cloud que o suportam (como AWS, Google Workspace). É robusto, maduro e padronizado há mais de uma década.

OIDC é a escolha para aplicações modernas: SPAs, mobile apps, microsserviços. É mais leve, mais fácil de implementar, e é o padrão crescente na indústria.

LDAP é complementar. Não implemente como SSO sozinho, mas use como backend para armazenar usuários. Combine com SAML/OIDC para ter o melhor dos dois mundos.

Conclusão

Dominar SSO significa entender que autenticação não é mais uma responsabilidade isolada de cada aplicação. A autenticação é centralizada, federada e confiável. Primeiro aprendizado: SAML e OIDC não são concorrentes diretos, mas ferramentas para contextos diferentes. SAML é maduro para enterprise, OIDC é moderno para qualquer coisa. Segundo aprendizado: LDAP é um detalhe de implementação, não um protocolo de federação. Use-o como backend enquanto que SAML/OIDC são os protocolos de intercâmbio. Terceiro aprendizado: federação repousa em criptografia e metadados. Você não troca senhas entre domínios; você publica certificados, assinando e validando tokens. Isso é segurança real.

Referências


Artigos relacionados