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.