Appearance
Frontend — Login social (federação de identidade)
Contrato HTTP do login social para o framework frontend (@gryd-ui/core). A referência completa, com diagrama de sequência e configuração do backend, está em Federated Login.
1. O que mudou
POST /api/v1/auth/social-login não existe mais. Ele recebia um access token do provedor vindo do cliente, e isso não tem como ser seguro: um token emitido para outro aplicativo é indistinguível de um emitido para este, então o servidor precisava acreditar no que o cliente entregasse.
O fluxo agora é conduzido pelo backend — quem fala com o provedor é a API, e o navegador nunca carrega uma asserção que o servidor não pediu.
Código a remover no repositório do front
packages/auth/src/domain/requests/SocialLoginCredentials.ts e as referências em AuthRepository e useAuth ainda chamam o endpoint removido. O contrato de auth vive em packages/core/src/domain/auth/.
2. Os três endpoints
| Endpoint | Quem chama | Retorno |
|---|---|---|
GET /api/v1/auth/federation/{connectionKey}/start | O navegador, como navegação de topo | 302 para o provedor |
GET/POST /api/v1/auth/federation/{connectionKey}/callback | O provedor, devolvendo o navegador | 302 para o returnUrl com um código de uso único |
POST /api/v1/auth/federation/exchange | A SPA, por XHR | O mesmo body de POST /auth/login |
connectionKey é google, microsoft ou apple — o que o deployment tiver habilitado. Um provedor desabilitado responde 404, igual a um que nunca existiu.
O front só chama o primeiro e o terceiro. O callback é entre o provedor e a API.
3. Implementação
ts
// 1. Manda o navegador para o provedor. NAVEGAÇÃO, nunca fetch().
// A resposta é um redirect para outra origem e traz um cookie que o callback exige:
// um XHR seguiria o redirect até um erro de CORS e o navegador descartaria o cookie.
export function startSocialLogin(provider: 'google' | 'microsoft' | 'apple', returnUrl: string) {
window.location.assign(
`/api/v1/auth/federation/${provider}/start?returnUrl=${encodeURIComponent(returnUrl)}`
);
}
// 2. Ao aterrissar no returnUrl, troca o código pela sessão.
export async function completeSocialLogin(): Promise<AuthenticationResponse | null> {
const code = new URLSearchParams(window.location.search).get('federation_code');
if (!code) return null;
const session = await http.post<AuthenticationResponse>(
'/api/v1/auth/federation/exchange',
{ code }
);
// Tira o código da URL antes que ele entre no histórico.
history.replaceState({}, '', window.location.pathname);
return session;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
4. A sessão que volta
POST /federation/exchange responde exatamente o body de POST /auth/login — mesma factory de resposta no backend. Não existe branch novo no front: o mesmo store, o mesmo tratamento de token, os mesmos caminhos de seleção de tenant e de MFA.
Em particular:
- Não há campo
refreshToken. O refresh vive no cookieHttpOnly, federado ou não, e ele já foi gravado na resposta do callback — que é navegação de topo e por isso não depende da configuração de credentials do CORS da SPA. O/exchangenão grava cookie nenhum. tokenTypepode voltarGlobal(seleção de tenant pendente) ouMfaPending, exatamente como no login nativo. Trate igual.
5. Erros
Corpo ProblemDetails, como todo o resto da API. Ramifique pelo code das extensions, nunca pelo detail (que é localizado).
| Status | code | O que mostrar |
|---|---|---|
400 | FEDERATION_SESSION_INVALID | "Não foi possível concluir o login. Tente novamente." |
400 | FEDERATION_CALLBACK_FAILED | idem |
400 | FEDERATION_RETURN_URL_NOT_ALLOWED | Erro de configuração do deployment, não do usuário |
400 | FEDERATION_LINK_VERIFICATION_REQUIRED | "Já existe uma conta com esse e-mail. Entre com sua senha e vincule o provedor nas configurações." |
403 | AUTH_RISK_FORBIDDEN | Bloqueio por política de risco |
404 | FEDERATION_CONNECTION_NOT_FOUND | Provedor não configurado ou desabilitado — não ofereça o botão |
429 | AUTH_RATE_LIMIT_EXCEEDED | Respeite o Retry-After |
502 | FEDERATION_CHALLENGE_FAILED | Provedor indisponível — falha de upstream, não do usuário |
FEDERATION_LINK_VERIFICATION_REQUIRED é o único que carrega instrução acionável. Os outros são deliberadamente pouco informativos: toda recusa do callback responde igual, para que ninguém descubra qual controle disparou nem se um dado e-mail tem conta.
5.1. A recusa do callback volta por redirect
O callback é sempre navegação de topo — nunca XHR. Responder ProblemDetails ali deixaria o usuário numa página de JSON cru na origem da API, sem SPA carregada. Por isso a recusa redireciona:
302 → {DefaultReturnUrl}?federation_error=FEDERATION_SESSION_INVALID1
O parâmetro é federation_error, e ele e o federation_code são mutuamente exclusivos: um redirect reporta um desfecho ou uma falha, nunca os dois. É isso que mantém a regra original de pé — uma recusa continua sem entregar à SPA um federation_code que ela não deveria ter; entregar um erro é o oposto de entregar uma credencial. A propriedade anti-enumeração também se mantém: o código é o mesmo genérico que iria no JSON.
O destino é o DefaultReturnUrl configurado, não o returnUrl daquela tentativa — quando o desafio não resolve, o backend não tem de onde tirar um destino que não venha do próprio request. Se não houver DefaultReturnUrl allowlistado, a resposta volta a ser ProblemDetails.
Os valores que chegam em federation_error são os code da tabela acima que o callback pode produzir: FEDERATION_SESSION_INVALID, FEDERATION_CALLBACK_FAILED, FEDERATION_LINK_VERIFICATION_REQUIRED e AUTH_RISK_FORBIDDEN (recusa por política de risco — o mesmo code que o login nativo já devolve nessa situação).
Isso vale só para o callback. O /start e o /exchange continuam respondendo ProblemDetails com a tabela acima, porque a SPA está no ar para tratá-los.
6. Detalhes que o front não precisa implementar
O state, o nonce, o PKCE, o cookie de correlação que amarra o state ao navegador e a validação do id_token são todos do backend. Não há nada a guardar em localStorage, nada a comparar no retorno e nenhum token do provedor passando pelo cliente.
A única higiene do lado do front é a do passo 2: tirar o federation_code da URL depois de trocá-lo. Ele é de uso único e expira em 60 segundos, mas não há razão para deixá-lo no histórico.