Bullgate Docs

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.

Implementado no código Contrato v0.1 ASP.NET Core · net10.0 React Native SDK · 0.1.0
Antes de integrar Os pacotes estão em versão 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.
Configuração por comportamento Consulte Políticas do Access para entender identifiers, authenticators, providers, limites e permissões antes de montar o environment.

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.

1. AplicativoEnvia credenciais e ações para o BFF do produto. Não recebe token Bullgate.
2. Backend + BFFGuarda o segredo bgic_…, escreve cookies HttpOnly e resolve o perfil local.
3. Bullgate AccessÉ autoridade sobre identity, credenciais, sessão e AccessFlow.
4. Banco isoladoO Access usa sua própria base. O produto não consulta tabelas Bullgate.
Chave de associação Persista 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âmetroTipo / padrãoComo usar
BaseAddress obrigatórioUri? · sem padrãoURL 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óriostring · sem padrãoCredencial server-to-server emitida no bootstrap. Precisa começar com bgic_. Guarde em secret store; o adapter envia como Authorization: Bearer.
RequestTimeoutTimeSpan · 10 segundosLimite de cada chamada ao Access. Deve ser positivo. Timeout é falha operacional e o BFF responde 503 access-unavailable.
CookieNamestring · bullgate.sessionNome do cookie da sessão opaca. Deve ser preenchido e diferente de FlowCookieName.
FlowCookieNamestring · bullgate.flowNome do cookie da capability temporária de AccessFlow. Ele não é uma sessão de produto.
CookieSecurePolicyCookieSecurePolicy · AlwaysControla o atributo Secure. Mantenha Always fora de desenvolvimento local HTTP.
CookieSameSiteSameSiteMode · LaxPolítica SameSite dos dois cookies. Altere apenas quando a topologia de domínios realmente exigir e revise CSRF junto.
Validação no startup URI relativa, credencial sem prefixo 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 null quando não houver perfil local válido.
  • Subject nã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.

  • ProvisionAsync cria ou retoma o perfil.
  • ResolveAsync busca 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);
}
MembroParâmetroSignificado
ResolveAsyncsessionSessão ativa já introspectada. Use IdentityId para lookup; não use Email como vínculo.
ResolveAsynccancellationTokenPropague para todas as chamadas de banco e rede.
ResolveAsyncretornoPrincipal/perfil local ou null. No authentication handler, null invalida o cookie local. No login, gera 409 application-not-provisioned.
ProvisionAsyncregistrationObjeto enviado em application pelo cliente. Valide aqui nome, aceite de termos e outros campos do produto.
ProvisionAsyncretornoBullgateApplicationProvisionResult<TApplication> contendo o perfil público.
ProvisionAsyncrejeiçãoLance BullgateApplicationRejectedException(statusCode, error, field). O adapter revoga a sessão recém-emitida e devolve seu erro ao cliente.

Backend · estado privado

Sessão, autenticação e cookies

O token opaco fica somente no cookie host-only. O aplicativo recebe um envelope tipado, nunca o token ou a capability do flow.

Cookie de sessãoHttpOnly, Secure conforme política, SameSite=Lax por padrão, Path=/, expiração igual à sessão Bullgate e IsEssential=true.
Cookie de flowMesmos atributos, mas Path=/bullgate/access/v1. Guarda uma capability curta e é apagado quando o flow emite sessão Product.
IntrospecçãoO handler introspecta o token no Access. Nesta versão não há cache positivo: bloqueio, revogação e expiração são observados na request seguinte.
active=falseO adapter apaga o cookie e trata a request como não autenticada.
Access indisponívelRede, timeout, JSON inválido ou status operacional viram 503 access-unavailable. O cookie é preservado para retry.
Sessão RegistrationÉ válida para continuar o cadastro, mas não cria um ClaimsPrincipal autenticado para o produto.
Ordem do pipeline UseBullgateAccessFailures() deve envolver o authentication handler; depois vêm UseAuthentication() e UseAuthorization(). Sem essa ordem, falhas operacionais podem não virar o contrato 503 esperado.

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

CampoTipoUso
email obrigatóriostringÉ aparado, validado e normalizado para minúsculas pelo Access.
password obrigatóriostringMínimo de 8 caracteres na versão atual.
application obrigatórioTRegistrationDados 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
CampoTipoUso
idTokenstring?Token de identidade obtido pelo SDK nativo. Quando preenchido, tem precedência sobre accessToken.
accessTokenstring?Alternativa aceita quando idToken não foi enviado. Pelo menos um dos dois tokens é necessário.
applicationTRegistration?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
CampoTipoUso
identityToken obrigatóriostringJWT emitido pelo Sign in with Apple e obtido pelo SDK nativo.
applicationTRegistration?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
CampoTipoUso
requestId obrigatório no HTTPUUIDChave idempotente da criação. O SDK gera automaticamente quando omitida na API TypeScript.
protocolVersionsnumber[]Versões compreendidas pelo cliente. O SDK envia [1]; a lista precisa conter 1.
intentstringNa fase atual, somente continueRegistration. O método do SDK preenche esse valor.
nativeClientId obrigatórioUUIDID 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
CampoTipoUso
requestId obrigatório no HTTPUUIDChave idempotente da ação. O SDK usa action.id por padrão.
expectedRevision obrigatórionumberRevisão do snapshot usado para renderizar a tela. Impede ação sobre estado antigo.
action.id obrigatórioUUIDID emitido no array actions. Nunca gere outro ID para substituir este valor.
action.type obrigatóriostringTipo da ação oferecida. ID e tipo precisam corresponder ao mesmo item do snapshot.
action.inputobject?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"
  }
}
CampoTipoSignificado
state"authenticated" | "registration"authenticated autoriza acesso ao produto. registration permite apenas continuar o cadastro.
identityIdUUIDSubject público, estável e globalmente único do Bullgate. Não é o ID local do perfil.
emailstringE-mail principal normalizado. É dado de exibição/contato, não chave de associação.
sessionExpiresAtISO 8601Expiração absoluta da sessão atual.
applicationTApplication | nullPerfil devolvido pelo host em sessão Product; null quando o estado é registration.
Limitação conhecida do contrato 0.1 Os tipos do SDK React Native também declaram 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âmetroTipoComo usar
baseUrl obrigatóriostring | (() => string)Origem da API do produto. A função é útil quando ambiente/tenant é resolvido em runtime. Barras finais são removidas.
fetchtypeof globalThis.fetchImplementação alternativa para testes ou runtime sem fetch global.
headersRecord<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âmetroTipoComo usar
transport obrigatórioBullgateAccessTransportTransport criado acima ou implementação própria que respeite method, path, body e signal.
options.createRequestId() => stringGerador de UUID para idempotência do início de flow. O padrão usa crypto.getRandomValues quando disponível.

Métodos

MétodoEntradaResultado
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 bodySessão atual ou null somente para HTTP 401.
logout(signal?)sem bodyPromise<void> após resposta 204.
startRegistrationFlow(input, signal?){ nativeClientId, requestId?, protocolVersions? }Envelope com snapshot inicial.
getFlow(flowId, signal?)flowId: stringSnapshot mais recente do flow.
actOnFlow(flow, type, input?, options?)Snapshot, tipo, payload e opçõesPró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

CampoTipoSignificado
protocolVersionnumberVersão negociada. Atualmente 1.
flowIdUUIDIdentificador da jornada; usado em getFlow e nas ações.
revisionnumberVersão monotônica do estado. Deve voltar como expectedRevision.
intentstringObjetivo da jornada. Fase atual: continueRegistration.
statusactive | completed | expiredControla se ainda há interação possível.
expiresAtISO 8601Expiração absoluta do flow, não do código OTP.
stepobject | nullEstado 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

CampoQuando existeUso na UI
typesempre em step ativoSelecione o componente pela união tipada: collectPhone, verifyPhone ou resolvePhoneConflict no comportamento implementado atual.
destinationverifyPhoneTelefone mascarado para confirmação visual. Nunca use como valor completo.
expiresAtverifyPhoneExpiração do challenge OTP atual.
resendAvailableAtverifyPhoneInstante a partir do qual a UI pode oferecer reenvio.
previousEmailHintresolvePhoneConflictDica 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çãoinputComportamento
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.
resendPhoneVerificationsem inputReenvia 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.
skipRegistrationsem inputConclui quando o campo é opcional. Não é oferecida se a policy exige telefone.
Tipos reservados não são ações disponíveis O pacote TypeScript já exporta nomes para recovery da identity anterior, verificação de e-mail anterior e nova senha. O 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.
Guia dedicado ao conflito de telefone Veja detecção, provas, snapshots, transferência atômica e limites da fase 1 em Resolução de identidade.

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.
E-mail principal único A fase 1 não cria e-mails alternativos. Mesmo que uma integração futura permita troca de e-mail, o endereço antigo não deve continuar como login ou alias por efeito colateral.

Invariantes de integração

Regras de segurança que não são opcionais

Segredo só no servidorA credencial bgic_… nunca entra no bundle web, aplicativo, variável EXPO_PUBLIC_*, log ou analytics.
Cookies HttpOnlyNão leia nem replique o token Bullgate no JavaScript. O SDK opera por sessão do BFF.
IdentityId é o subjectAssocie perfil por UUID Bullgate. E-mail e telefone são identifiers mutáveis.
Idempotência por payloadRepetir o mesmo requestId com o mesmo payload reproduz o resultado. Reutilizá-lo com payload diferente é conflito.
Revisão otimistaEnvie a revisão exibida. Em conflito, recarregue o snapshot; não force a ação antiga.
Ação oferecidaNão derive autoridade do nome da tela. Execute apenas IDs e tipos presentes em flow.actions.
Falha operacional ≠ logout503 não prova sessão inválida. Preserve o cookie e permita retry.
Sem auto-linkE-mail igual não prova que duas identities são da mesma pessoa.

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.

Chat Bullgate
Assistente Bullgateprévia

Olá! O assistente ainda está sendo preparado. Enquanto isso, fale comigo:

Marco · Bullgate

Prévia: nenhuma mensagem é enviada por este campo.