Documentação técnica · integração externa
Bullgate Access
Referência para integrar um backend ASP.NET Core e um aplicativo React Native ao contrato de identidade, sessão e continuação de cadastro do Bullgate.
0.1.0, mas ainda não possuem distribuição pública estável. Use o artefato ou feed indicado pelo operador Bullgate. Esta página descreve o código vigente em 04/09/2026.
Fronteira obrigatória
O cliente nunca chama o Access diretamente
O aplicativo conversa com o backend do próprio produto. O backend mantém a credencial de integração, chama o Bullgate Access e transforma a identity Bullgate em um perfil local.
bgic_…, escreve cookies HttpOnly e resolve o perfil local.IdentityId como referência externa única no perfil do produto. Nunca associe contas por e-mail, telefone ou nome: esses valores podem mudar e não são autoridade de vínculo.
Sequência mínima
Início rápido
Uma integração funcional exige topologia provisionada, adapter no backend, contratos locais e cliente móvel apontando para o BFF.
Provisione o ambiente
Crie workspace, app, realm, environment, integration client e native client. Guarde a credencial emitida em um secret store.
Configure o backend
Registre o client Bullgate, a autenticação por sessão e os endpoints BFF em /bullgate/access/v1.
Implemente o vínculo
Resolva e provisione o perfil local sempre por session.IdentityId, de modo idempotente.
Configure o aplicativo
Crie o transport com a URL da API do produto. Cookies são enviados com credentials: include.
Restaure a sessão
Na abertura, chame session(). Resposta 401 vira null; falha 503 deve permitir nova tentativa.
Renderize o flow
Quando o estado for registration, inicie o AccessFlow e execute somente ações devolvidas no snapshot atual.
Backend · Bullgate.Access.AspNetCore 0.1.0
Configuração do adapter .NET
O adapter registra um HttpClient autenticado, cookies host-only, authentication handler e as nove rotas BFF da fase atual.
Program.cs
builder.Services.AddBullgateAccess(options =>
{
options.BaseAddress = new Uri(
builder.Configuration["Bullgate:Access:BaseUrl"]!);
options.IntegrationCredential =
builder.Configuration["Bullgate:Access:IntegrationCredential"]!;
});
builder.Services
.AddAuthentication(BullgateAccessDefaults.AuthenticationScheme)
.AddBullgateSession();
builder.Services.AddAuthorization();
// Implementações específicas do seu produto.
builder.Services.AddScoped<IBullgatePrincipalResolver, ProductPrincipalResolver>();
builder.Services.AddScoped<
IBullgateAccessApplication<ProductRegistration, ProductProfile>,
ProductAccessApplication>();
// A ordem é significativa.
app.UseBullgateAccessFailures();
app.UseAuthentication();
app.UseAuthorization();
app.MapBullgateAccess<ProductRegistration, ProductProfile>();| Parâmetro | Tipo / padrão | Como usar |
|---|---|---|
BaseAddress obrigatório | Uri? · sem padrão | URL absoluta do serviço Bullgate Access vista pelo backend. Não é a URL pública do BFF e nunca vai para o aplicativo. |
IntegrationCredential obrigatório | string · sem padrão | Credencial server-to-server emitida no bootstrap. Precisa começar com bgic_. Guarde em secret store; o adapter envia como Authorization: Bearer. |
RequestTimeout | TimeSpan · 10 segundos | Limite de cada chamada ao Access. Deve ser positivo. Timeout é falha operacional e o BFF responde 503 access-unavailable. |
CookieName | string · bullgate.session | Nome do cookie da sessão opaca. Deve ser preenchido e diferente de FlowCookieName. |
FlowCookieName | string · bullgate.flow | Nome do cookie da capability temporária de AccessFlow. Ele não é uma sessão de produto. |
CookieSecurePolicy | CookieSecurePolicy · Always | Controla o atributo Secure. Mantenha Always fora de desenvolvimento local HTTP. |
CookieSameSite | SameSiteMode · Lax | Política SameSite dos dois cookies. Altere apenas quando a topologia de domínios realmente exigir e revise CSRF junto. |
bgic_, timeout não positivo, nomes vazios ou cookies com o mesmo nome impedem a aplicação de iniciar.
Backend · responsabilidades do produto
Os dois contratos que você implementa
O adapter não conhece sua tabela de usuários, seu onboarding ou suas regras de negócio. Esses dois contratos conectam uma identity Bullgate ao domínio local.
IBullgatePrincipalResolver
Executado pelo authentication handler depois de uma introspecção ativa com propósito Product. Converte a sessão em subject e claims locais.
- Retorne
nullquando não houver perfil local válido. Subjectnão pode ser vazio.- Claims Bullgate de identity e session são adicionadas pelo adapter.
IBullgateAccessApplication<TRegistration,TApplication>
TRegistration é a entrada específica do cadastro do seu produto. TApplication é o perfil público devolvido ao cliente.
ProvisionAsynccria ou retoma o perfil.ResolveAsyncbusca o perfil já associado.- Ambos recebem a sessão Bullgate validada.
Implementação de referência
public sealed record ProductRegistration(string Name);
public sealed record ProductProfile(Guid Id, string Name);
public sealed class ProductAccessApplication(ProductDb db)
: IBullgateAccessApplication<ProductRegistration, ProductProfile>
{
public async ValueTask<BullgateApplicationProvisionResult<ProductProfile>>
ProvisionAsync(
BullgateSession session,
ProductRegistration registration,
CancellationToken cancellationToken)
{
// UNIQUE(BullgateIdentityId) torna o retry seguro.
var profile = await db.GetOrCreateByBullgateIdentityIdAsync(
session.IdentityId,
registration.Name,
cancellationToken);
return new(profile);
}
public ValueTask<ProductProfile?> ResolveAsync(
BullgateSession session,
CancellationToken cancellationToken) =>
db.FindByBullgateIdentityIdAsync(session.IdentityId, cancellationToken);
}| Membro | Parâmetro | Significado |
|---|---|---|
ResolveAsync | session | Sessão ativa já introspectada. Use IdentityId para lookup; não use Email como vínculo. |
ResolveAsync | cancellationToken | Propague para todas as chamadas de banco e rede. |
ResolveAsync | retorno | Principal/perfil local ou null. No authentication handler, null invalida o cookie local. No login, gera 409 application-not-provisioned. |
ProvisionAsync | registration | Objeto enviado em application pelo cliente. Valide aqui nome, aceite de termos e outros campos do produto. |
ProvisionAsync | retorno | BullgateApplicationProvisionResult<TApplication> contendo o perfil público. |
ProvisionAsync | rejeição | Lance BullgateApplicationRejectedException(statusCode, error, field). O adapter revoga a sessão recém-emitida e devolve seu erro ao cliente. |
API pública do seu BFF
Referência de endpoints
MapBullgateAccess<TRegistration,TApplication>() publica estas rotas sob /bullgate/access/v1. Os formatos abaixo são JSON em camelCase.
POST/bullgate/access/v1/registerCria identity e provisiona o perfil local
Body
| Campo | Tipo | Uso |
|---|---|---|
email obrigatório | string | É aparado, validado e normalizado para minúsculas pelo Access. |
password obrigatório | string | Mínimo de 8 caracteres na versão atual. |
application obrigatório | TRegistration | Dados necessários para criar o perfil do produto; o Bullgate não interpreta esse objeto. |
{
"email": "[email protected]",
"password": "uma-senha-segura",
"application": { "name": "Ana" }
}Se a identity já existir, o BFF tenta login com a mesma senha e retoma o provisionamento. Com senha diferente, devolve a rejeição do login. Sucesso grava o cookie de sessão e responde 200.
POST/bullgate/access/v1/loginAutentica por e-mail e senha
Body: email: string e password: string. Falhas de formato e credencial convergem para 401 invalid-credentials.
{ "email": "[email protected]", "password": "uma-senha-segura" }Depois da autenticação Bullgate, o BFF chama ResolveAsync. Perfil ausente revoga a nova sessão e produz 409 application-not-provisioned.
POST/bullgate/access/v1/googleAutentica ou cadastra com Google
| Campo | Tipo | Uso |
|---|---|---|
idToken | string? | Token de identidade obtido pelo SDK nativo. Quando preenchido, tem precedência sobre accessToken. |
accessToken | string? | Alternativa aceita quando idToken não foi enviado. Pelo menos um dos dois tokens é necessário. |
application | TRegistration? | Opcional para identity já ligada a perfil; obrigatório quando o login social cria uma identity sem perfil local. |
O provider precisa estar habilitado na policy do environment. E-mail igual ao de outra identity retorna 409 email-taken; não existe auto-link por e-mail.
POST/bullgate/access/v1/appleAutentica ou cadastra com Apple
| Campo | Tipo | Uso |
|---|---|---|
identityToken obrigatório | string | JWT emitido pelo Sign in with Apple e obtido pelo SDK nativo. |
application | TRegistration? | Necessário apenas quando ainda não há perfil local para a identity. |
GET/bullgate/access/v1/sessionRestaura a sessão atual
Sem parâmetros e sem body. Usa o cookie bullgate.session. Responde 200 com o envelope de sessão ou 401 quando não há sessão/profil válido. Adiciona Cache-Control: no-store.
POST/bullgate/access/v1/logoutRevoga a sessão corrente
Sem parâmetros e sem body. Revoga o token no Access quando presente, apaga os cookies de sessão e flow e responde 204.
POST/bullgate/access/v1/flowsInicia a continuação de cadastro
| Campo | Tipo | Uso |
|---|---|---|
requestId obrigatório no HTTP | UUID | Chave idempotente da criação. O SDK gera automaticamente quando omitida na API TypeScript. |
protocolVersions | number[] | Versões compreendidas pelo cliente. O SDK envia [1]; a lista precisa conter 1. |
intent | string | Na fase atual, somente continueRegistration. O método do SDK preenche esse valor. |
nativeClientId obrigatório | UUID | ID público do artefato Android/iOS provisionado. Não é package name, bundle id ou secret. |
O BFF lê a sessão Registration do cookie, passa o token ao Access e grava a capability retornada no cookie de flow. Duração atual do flow: 30 minutos.
GET/bullgate/access/v1/flows/{flowId}Recupera o snapshot atual
flowId é o UUID devolvido no snapshot. A capability é lida do cookie HttpOnly; o cliente não a envia no body. Flow ausente, de outro escopo ou com capability inválida retorna 404 flow-not-found.
POST/bullgate/access/v1/flows/{flowId}/actionsExecuta uma ação oferecida pelo snapshot
| Campo | Tipo | Uso |
|---|---|---|
requestId obrigatório no HTTP | UUID | Chave idempotente da ação. O SDK usa action.id por padrão. |
expectedRevision obrigatório | number | Revisão do snapshot usado para renderizar a tela. Impede ação sobre estado antigo. |
action.id obrigatório | UUID | ID emitido no array actions. Nunca gere outro ID para substituir este valor. |
action.type obrigatório | string | Tipo da ação oferecida. ID e tipo precisam corresponder ao mesmo item do snapshot. |
action.input | object? | Payload específico da ação, como { phone }, { code } ou { previousEmail }. |
Contrato de resposta
Envelope de sessão do BFF
Cadastro, login, providers sociais e restauração usam a mesma forma discriminada.
{
"state": "authenticated",
"identityId": "0198f1d0-…",
"email": "[email protected]",
"sessionExpiresAt": "2026-10-04T12:00:00Z",
"application": {
"id": "8d5e…",
"name": "Ana"
}
}| Campo | Tipo | Significado |
|---|---|---|
state | "authenticated" | "registration" | authenticated autoriza acesso ao produto. registration permite apenas continuar o cadastro. |
identityId | UUID | Subject público, estável e globalmente único do Bullgate. Não é o ID local do perfil. |
email | string | E-mail principal normalizado. É dado de exibição/contato, não chave de associação. |
sessionExpiresAt | ISO 8601 | Expiração absoluta da sessão atual. |
application | TApplication | null | Perfil devolvido pelo host em sessão Product; null quando o estado é registration. |
hasPassword, hasGoogle, googleEmail, hasApple e appleEmail. O BFF ASP.NET atual não serializa esses campos no envelope. Não baseie a UI neles até o contrato ser alinhado em uma versão posterior.
Frontend · @bullgate/react-native 0.1.0
Cliente React Native headless
O SDK não desenha telas. Ele oferece um transport HTTP, métodos tipados e o protocolo AccessFlow. Sua aplicação controla componentes, navegação, copy, acessibilidade e analytics.
import {
createBullgateAccessClient,
createBullgateFetchTransport,
BullgateAccessError,
} from "@bullgate/react-native";
const transport = createBullgateFetchTransport({
baseUrl: () => apiBaseUrl,
});
const access = createBullgateAccessClient<
ProductRegistration,
ProductProfile
>(transport);
const session = await access.login({ email, password });
if (session.state === "authenticated") {
openApplication(session.application);
} else {
openRegistrationJourney();
}Parâmetros do transport
| Parâmetro | Tipo | Como usar |
|---|---|---|
baseUrl obrigatório | string | (() => string) | Origem da API do produto. A função é útil quando ambiente/tenant é resolvido em runtime. Barras finais são removidas. |
fetch | typeof globalThis.fetch | Implementação alternativa para testes ou runtime sem fetch global. |
headers | Record<string,string> | Headers constantes do BFF, por exemplo versão do app. Não coloque credencial bgic_ aqui. |
O transport sempre usa credentials: "include", envia Accept: application/json e adiciona Content-Type: application/json quando há body.
Parâmetros do client
| Parâmetro | Tipo | Como usar |
|---|---|---|
transport obrigatório | BullgateAccessTransport | Transport criado acima ou implementação própria que respeite method, path, body e signal. |
options.createRequestId | () => string | Gerador de UUID para idempotência do início de flow. O padrão usa crypto.getRandomValues quando disponível. |
Métodos
| Método | Entrada | Resultado |
|---|---|---|
register(input, signal?) | { email, password, application } | Sessão authenticated ou registration. |
login(input, signal?) | { email, password } | Sessão discriminada. Não trate registration como acesso ao produto. |
google(input, signal?) | { idToken?, accessToken?, application? } | Sessão social ou BullgateAccessError. |
apple(input, signal?) | { identityToken, application? } | Sessão social ou BullgateAccessError. |
session(signal?) | sem body | Sessão atual ou null somente para HTTP 401. |
logout(signal?) | sem body | Promise<void> após resposta 204. |
startRegistrationFlow(input, signal?) | { nativeClientId, requestId?, protocolVersions? } | Envelope com snapshot inicial. |
getFlow(flowId, signal?) | flowId: string | Snapshot mais recente do flow. |
actOnFlow(flow, type, input?, options?) | Snapshot, tipo, payload e opções | Próximo snapshot; pode conter sessão Product quando o flow concluir. |
Protocolo state-driven · versão 1
Como conduzir um AccessFlow
A tela não decide a máquina de estados. Ela renderiza step, apresenta as actions disponíveis e envia uma ação com a mesma revisão recebida.
let current = await access.startRegistrationFlow({
nativeClientId,
});
while (current.flow.status === "active") {
const flow = current.flow;
if (flow.step?.type === "collectPhone") {
current = await access.actOnFlow(
flow,
"requestPhoneVerification",
{ phone: "+5511999999999" },
);
continue;
}
if (flow.step?.type === "verifyPhone") {
current = await access.actOnFlow(
flow,
"confirmPhoneVerification",
{ code: "123456" },
);
continue;
}
// Nunca invente uma ação que não está em flow.actions.
renderStep(flow);
break;
}
if (current.session?.state === "authenticated") {
openApplication(current.session.application);
}Campos do snapshot
| Campo | Tipo | Significado |
|---|---|---|
protocolVersion | number | Versão negociada. Atualmente 1. |
flowId | UUID | Identificador da jornada; usado em getFlow e nas ações. |
revision | number | Versão monotônica do estado. Deve voltar como expectedRevision. |
intent | string | Objetivo da jornada. Fase atual: continueRegistration. |
status | active | completed | expired | Controla se ainda há interação possível. |
expiresAt | ISO 8601 | Expiração absoluta do flow, não do código OTP. |
step | object | null | Estado que a UI deve renderizar. É null em resultado terminal. |
actions | { id, type }[] | Únicas ações válidas naquela revisão. |
feedback | { code, field?, retryAt? } | Erro recuperável ou orientação da última interação. Não é falha HTTP. |
result | { type, outcome?, … } | Resultado terminal da jornada. |
Campos de step
| Campo | Quando existe | Uso na UI |
|---|---|---|
type | sempre em step ativo | Selecione o componente pela união tipada: collectPhone, verifyPhone ou resolvePhoneConflict no comportamento implementado atual. |
destination | verifyPhone | Telefone mascarado para confirmação visual. Nunca use como valor completo. |
expiresAt | verifyPhone | Expiração do challenge OTP atual. |
resendAvailableAt | verifyPhone | Instante a partir do qual a UI pode oferecer reenvio. |
previousEmailHint | resolvePhoneConflict | Dica mascarada do e-mail anterior. Não é um endereço alternativo nem um valor para preenchimento automático. |
AccessFlow · ações implementadas
Ações e payloads
O SDK procura o tipo dentro de flow.actions. Se não estiver disponível, lança BullgateAccessProtocolError antes da chamada HTTP.
| Ação | input | Comportamento |
|---|---|---|
requestPhoneVerification | { phone: string } | Normaliza o telefone para E.164, cria challenge e envia SMS. Telefone inválido volta como feedback invalid-phone. |
submitPhone | { phone: string } | Usada somente quando a policy habilita telefone sem verificação. Telefone já usado por outra identity volta como feedback. |
confirmPhoneVerification | { code: string } | Confere o OTP do challenge ativo. Código ausente, inválido ou tentativas esgotadas aparecem em feedback. |
resendPhoneVerification | sem input | Reenvia para o mesmo destino depois do cooldown. Antes disso, devolve retryAt. |
transferPhoneToCurrentIdentity | { previousEmail: string } | Quando o telefone verificado pertence a outra identity, comprova conhecimento do e-mail anterior e move somente o telefone para a identity atual. |
skipRegistration | sem input | Conclui quando o campo é opcional. Não é oferecida se a policy exige telefone. |
AccessFlowService v1 atual ainda não os executa. Integrações devem sempre confiar no array actions, nunca apenas na união de tipos do SDK.
Contrato vigente
Escopo da fase 1
Documentar o que não existe é parte do contrato: consumidores não devem inferir comportamento futuro a partir de tipos, telas ou dados disponíveis.
Implementado
- Cadastro e login por e-mail/senha.
- Google e Apple quando habilitados.
- Sessão Registration e Product.
- Introspecção e revogação da sessão corrente.
- Continuação de cadastro por telefone.
- OTP, reenvio, conflito e transferência de telefone.
- Provisionamento do perfil local pelo host.
Não implementado nesta fase
- E-mails alternativos.
- Merge automático de identities.
- Auto-link por e-mail entre providers.
- Verificação de e-mail por challenge ou link.
- Login sem senha por magic link ou código de uso único.
- Passkeys e 2FA/TOTP.
- OCR, documento, selfie e prova de idade.
- Rotas públicas de recovery por e-mail/telefone no adapter 0.1.
- Billing, entitlements ou regras do produto.
Invariantes de integração
Regras de segurança que não são opcionais
bgic_… nunca entra no bundle web, aplicativo, variável EXPO_PUBLIC_*, log ou analytics.requestId com o mesmo payload reproduz o resultado. Reutilizá-lo com payload diferente é conflito.flow.actions.503 não prova sessão inválida. Preserve o cookie e permita retry.Roadmap · não é contrato
Próximas capacidades candidatas
Esta seção orienta evolução de produto. Nada abaixo pode ser chamado ou prometido por uma integração da fase 1.
Verificação de e-mail
Challenge por código ou link, com expiração, tentativas, cooldown e provider configurado por policy.
Login sem senha
Magic link ou código de uso único para e-mail já verificado, com resposta neutra e proteção contra replay.
Auto-link social por policy
Vínculo de uma credencial Google ou Apple à identity que já possui o mesmo e-mail, somente quando uma policy explícita permitir e houver provas suficientes de titularidade. Ainda não existe na fase 1; hoje a colisão retorna 409 email-taken.
Passkeys
WebAuthn, múltiplas credenciais por identity, nomeação e revogação.
2FA e step-up
TOTP, códigos de recuperação e prova adicional por ação sensível.
Sessões visíveis
Listagem por dispositivo, revogação seletiva e alerta de mudança crítica.
Recovery forte
Combinação explícita de provas, grant curto e execução idempotente.
Prova civil
Idade, CPF, OCR, autenticidade documental e selfie/liveness via adapters substituíveis.
Enterprise
OIDC, organizações, domínios verificados, SAML e SCIM conforme demanda real.