Integração SSO — Especificação Técnica
Plataforma Semente
1. Visão Geral
1.1 Objetivo
Este documento especifica como um sistema parceiro deve se integrar ao mecanismo de Single Sign-On (SSO) da Plataforma Semente. O objetivo é permitir que um usuário já autenticado no sistema do parceiro (por exemplo, o sistema de gestão escolar) acesse a Plataforma Semente sem precisar realizar um novo login manual.
A integração é feita através da geração de um token assinado (JWT) pelo parceiro, enviado a um endpoint HTTP da Semente, que valida o token e autentica o usuário correspondente.
1.2 Fluxo Resumido
- O usuário se autentica normalmente no sistema do parceiro.
- O sistema do parceiro gera um token JWT assinado, identificando o usuário.
- O parceiro envia esse token para o endpoint de SSO da Semente.
- A Semente valida o token e autentica o usuário na Plataforma Semente.
- A Semente retorna uma sessão de acesso válida.
1.3 Diagrama do Fluxo
O diagrama abaixo ilustra a sequência completa da integração, desde o login do usuário no sistema do parceiro até o acesso à Plataforma Semente:
Caso qualquer validação do passo 4 falhe (assinatura, audience/issuer, expiração ou reuso do jti), a Semente responde com erro diretamente ao parceiro, e nenhum usuário é autenticado.
2. Pré-requisitos
2.1 Dados fornecidos pela Semente
Antes de iniciar a implementação, a Semente fornecerá ao parceiro, por canal seguro, os seguintes dados:
- Client ID: identificador único do parceiro.
- Secret: chave de 256 bits, codificada em Base64, usada para assinar o token (HMAC-SHA256).
2.2 Sobre a Codificação em Base64
O secret é gerado pela Semente como uma sequência de bytes aleatórios e entregue ao parceiro já codificado em Base64 — essa codificação existe apenas para que um valor binário possa ser transmitido e copiado como texto (e-mail, painel, etc.).
O parceiro não precisa gerar nem codificar nada em Base64. O único passo necessário do lado do parceiro é decodificar a string recebida de volta para bytes brutos antes de usá-la como chave de assinatura HMAC — exatamente como mostrado nos exemplos da seção 5 (Convert.FromBase64String, base64.b64decode, Base64.getDecoder().decode, entre outros).
3. Especificação do Token (JWT)
O token utilizado é um JSON Web Token (JWT), assinado com o algoritmo HMAC-SHA256 (HS256). É um padrão aberto, com bibliotecas disponíveis para praticamente qualquer linguagem de programação.
3.1 Claims Obrigatórias
| Claim | Obrigatório | Descrição |
|---|---|---|
| sub | Sim | Login do usuário na Plataforma Semente (o mesmo identificador já usado hoje no login direto). |
| iss | Sim | Client ID do parceiro, fornecido pela Semente no momento do provisionamento. |
| aud | Sim | Valor fixo "plataforma-semente". Deve ser copiado literalmente (case-sensitive). |
| jti | Sim | Identificador único do token (UUID v4). Usado para impedir reuso do mesmo token. |
| iat | Sim | Data/hora de emissão do token (UTC). |
| nbf | Sim | Data/hora a partir da qual o token é válido (UTC). Deve ser igual a iat. |
| exp | Sim | Data/hora de expiração do token (UTC). Deve ser iat + 5 minutos. |
3.2 Algoritmo e Chave
- Algoritmo de assinatura: HS256 (HMAC-SHA256). Nenhum outro algoritmo é aceito.
- Chave de assinatura: o secret fornecido pela Semente, decodificado de Base64 para bytes brutos antes de assinar.
3.3 Regras de Validade
- O token deve ter validade de exatamente 5 minutos entre
iateexp. - Cada token deve conter um
jtiúnico (recomenda-se UUID v4). Um token com o mesmojtinão pode ser reenviado — a segunda tentativa será rejeitada, mesmo dentro da janela de validade. - O relógio do sistema do parceiro deve estar sincronizado (NTP). Diferenças de horário podem causar rejeição indevida do token.
4. Endpoint de Integração
4.1 URL e Método
O parceiro deve enviar o token via requisição HTTP POST para:
POST https://integracao.sementeeducacao.com.br/api/v0/accounts/sso_custom
4.2 Formato da Requisição
O corpo da requisição deve ser um JSON contendo o token no campo token:
POST /api/v0/accounts/sso_custom HTTP/1.1
Host: integracao.sementeeducacao.com.br
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2FvLnNpbHZhIiwi..."
}
4.3 Respostas
Em caso de sucesso, a Semente retorna a sessão de acesso:
HTTP/1.1 200 OK
Content-Type: application/json
{
"sessionToken": "...",
"expiresIn": 3600
}
Em caso de falha de validação:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "invalid_token",
"message": "Token expirado ou já utilizado"
}
Códigos de retorno possíveis:
| HTTP | Situação | Descrição |
|---|---|---|
| 200 | Sucesso | Token válido. A resposta contém a sessão de acesso à Plataforma Semente. |
| 401 | Não autorizado | Assinatura inválida, client_id desconhecido, token expirado, ou token já utilizado (replay). |
| 400 | Requisição inválida | Corpo da requisição malformado ou claims obrigatórias ausentes. |
5. Exemplos de Geração do JWT
A seguir, exemplos de geração do token nas linguagens mais comuns. Todos produzem um token equivalente, seguindo a especificação da seção 3.
5.1 C# (.NET)
Bibliotecas: System.IdentityModel.Tokens.Jwt e Microsoft.IdentityModel.Tokens (NuGet).
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
// client_id e secret são fornecidos pela Semente no provisionamento
string clientId = "SEU_CLIENT_ID";
string secretBase64 = "SEU_SECRET_BASE64";
string login = "joao.silva";
var key = new SymmetricSecurityKey(Convert.FromBase64String(secretBase64));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var now = DateTime.UtcNow;
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, login),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(
issuer: clientId,
audience: "plataforma-semente",
claims: claims,
notBefore: now,
expires: now.AddMinutes(5),
signingCredentials: credentials
);
string jwt = new JwtSecurityTokenHandler().WriteToken(token);
5.2 Python
Biblioteca: PyJWT (pip install pyjwt).
import jwt
import uuid
import base64
from datetime import datetime, timedelta, timezone
# client_id e secret são fornecidos pela Semente no provisionamento
client_id = "SEU_CLIENT_ID"
secret = base64.b64decode("SEU_SECRET_BASE64")
login = "joao.silva"
now = datetime.now(timezone.utc)
payload = {
"sub": login,
"iss": client_id,
"aud": "plataforma-semente",
"jti": str(uuid.uuid4()),
"iat": now,
"nbf": now,
"exp": now + timedelta(minutes=5),
}
jwt_token = jwt.encode(payload, secret, algorithm="HS256")
5.3 Java
Biblioteca: io.jsonwebtoken:jjwt (jjwt-api, jjwt-impl, jjwt-jackson).
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
// client_id e secret são fornecidos pela Semente no provisionamento
String clientId = "SEU_CLIENT_ID";
byte[] secretBytes = Base64.getDecoder().decode("SEU_SECRET_BASE64");
SecretKey key = Keys.hmacShaKeyFor(secretBytes);
String login = "joao.silva";
Date now = new Date();
Date exp = new Date(now.getTime() + 5 * 60 * 1000);
String jwt = Jwts.builder()
.setSubject(login)
.setIssuer(clientId)
.setAudience("plataforma-semente")
.setId(UUID.randomUUID().toString())
.setIssuedAt(now)
.setNotBefore(now)
.setExpiration(exp)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
5.4 Node.js
Biblioteca: jsonwebtoken (npm install jsonwebtoken).
const jwt = require("jsonwebtoken");
const { randomUUID } = require("crypto");
// client_id e secret são fornecidos pela Semente no provisionamento
const clientId = "SEU_CLIENT_ID";
const secret = Buffer.from("SEU_SECRET_BASE64", "base64");
const login = "joao.silva";
const now = Math.floor(Date.now() / 1000);
const token = jwt.sign(
{
sub: login,
iss: clientId,
aud: "plataforma-semente",
jti: randomUUID(),
iat: now,
nbf: now,
exp: now + 5 * 60,
},
secret,
{ algorithm: "HS256" }
);
5.5 PHP
Biblioteca: firebase/php-jwt (composer require firebase/php-jwt).
<?php
require 'vendor/autoload.php';
use Firebase\JWT\JWT;
// client_id e secret são fornecidos pela Semente no provisionamento
$clientId = "SEU_CLIENT_ID";
$secret = base64_decode("SEU_SECRET_BASE64");
$login = "joao.silva";
$now = time();
$payload = [
"sub" => $login,
"iss" => $clientId,
"aud" => "plataforma-semente",
"jti" => bin2hex(random_bytes(16)),
"iat" => $now,
"nbf" => $now,
"exp" => $now + (5 * 60),
];
$jwt = JWT::encode($payload, $secret, "HS256");