Pular para o conteúdo principal

Autenticação — Visão Geral

Última atualização: 14 de julho de 2026

Visão Geral

O force-mobile autentica o vendedor através do Keycloak da Vidya, usando o fluxo OAuth com PKCE e um navegador externo ao app. Depois que o login é concluído, o usuário ainda precisa de um segundo passo antes de conseguir operar: escolher ou confirmar em qual organização ele está trabalhando, já que o mesmo usuário pode ter acesso a mais de uma. Esses dois momentos, autenticação e contexto de organização, são tratados como coisas separadas no código, e essa separação é uma das regras mais importantes do fluxo.

Esta seção documenta o fluxo em seis partes:

DocConteúdo
Visão Geral (este documento)Arquitetura em camadas e papel de cada peça
Login e Callback OAuthPKCE, navegador externo, deep link de retorno e troca do código por tokens
Refresh de TokenOs dois gatilhos de renovação e o lock que evita chamadas concorrentes
LogoutO que é limpo e a chamada de encerramento de sessão no Keycloak
Guardas de Rota e InicializaçãoComo o app decide para onde navegar ao abrir e como as rotas protegidas reagem à ausência de sessão
Contexto de OrganizaçãoComo o token de organização é obtido e por que ele nunca deve ser sobrescrito silenciosamente

Arquitetura em camadas

A autenticação segue o mesmo princípio adotado no resto do app: a tela nunca fala diretamente com o Keycloak ou com o storage, ela só consome o que o provider expõe. Toda a lógica de negócio fica concentrada em serviços, e o storage funciona como uma fonte de verdade reativa que notifica quem estiver observando quando os dados mudam.

Essa arquitetura explica por que quase toda alteração em autenticação passa por poucos arquivos centrais, mesmo que o efeito apareça em vários lugares da UI. Se o token mudar no storage, qualquer tela que dependa dele é atualizada automaticamente através dos hooks reativos, sem que ninguém precise chamar um refresh manual de estado.

Arquivos-chave

ArquivoResponsabilidade
services/auth/auth.config.tsMonta AUTH_CONFIG a partir de app.json > expo.extra: endpoints do Keycloak, client id e secret, redirect URI, escopos e constantes de tempo
services/auth/auth.service.tsPKCE, troca de código por tokens, refresh, logout e verificação de sessão ativa no Keycloak
services/auth/auth.storage.tsPersistência em MMKV dos tokens e do contexto de organização, com hooks reativos useAuthTokens e useAuthContextsTokens
providers/auth/auth.provider.tsxContexto React que expõe isAuthenticated, signIn, signOut, trata o deep link de retorno do Keycloak e mantém a validação periódica da sessão
features/auth/tabs-auth-guard.tsxGuarda que redireciona para o login quando as tabs são acessadas sem sessão válida
app/auth/login.auth.tsx + features/auth/login.auth.controller.tsTela de login e seu controller
services/api.service.tsCliente Axios com interceptors que injetam os headers de autenticação e tratam o 401
services/organization/organization.service.ts + storage/organization/organization.storage.tsBusca, seleção e persistência do contexto de organização
app/index.tsxPonto de entrada do app, decide a rota inicial com base no estado de autenticação

Configuração vem de um único lugar

Nenhuma URL, client id, secret ou redirect URI do Keycloak deve aparecer hardcoded em outro arquivo. Tudo passa por AUTH_CONFIG, montado a partir de expo.extra e validado no boot do app:

// services/auth/auth.config.ts
const KEYCLOAK_ISSUER = required("KEYCLOAK_ISSUER", extra.KEYCLOAK_ISSUER);

export const AUTH_CONFIG = {
KEYCLOAK_ISSUER,
KEYCLOAK_CLIENT_ID: required("KEYCLOAK_CLIENT_ID", extra.KEYCLOAK_CLIENT_ID),
API_URL: required("API_URL", extra.API_URL),
API_MIDDLEWARE_URL: required("API_MIDDLEWARE_URL", extra.API_MIDDLEWARE_URL),
API_AI_SOFIA_URL: required("API_AI_SOFIA_URL", extra.API_AI_SOFIA_URL),
KEYCLOAK_CLIENT_SECRET: required("KEYCLOAK_CLIENT_SECRET", extra.KEYCLOAK_CLIENT_SECRET),

KEYCLOAK_REDIRECT_URI: "com.vidyacode.force:///",

KEYCLOAK_AUTHORIZATION_ENDPOINT: `${KEYCLOAK_ISSUER}/protocol/openid-connect/auth`,
KEYCLOAK_TOKEN_ENDPOINT: `${KEYCLOAK_ISSUER}/protocol/openid-connect/token`,
KEYCLOAK_INTROSPECTION_ENDPOINT: `${KEYCLOAK_ISSUER}/protocol/openid-connect/token/introspect`,
KEYCLOAK_END_SESSION_ENDPOINT: `${KEYCLOAK_ISSUER}/protocol/openid-connect/logout`,
KEYCLOAK_USERINFO_ENDPOINT: `${KEYCLOAK_ISSUER}/protocol/openid-connect/userinfo`,

SCOPES: ["openid", "profile", "email", "offline_access"],

TOKEN_EXPIRY_SKEW_SECONDS: 30,
SESSION_VALIDATION_INTERVAL_MS: 4 * 60 * 1000,
} as const;

Se algum desses valores estiver ausente, required() lança um erro explicando exatamente qual configuração falta e onde ela deveria ter sido definida, o que evita que o app suba silenciosamente com autenticação quebrada.

Vale reparar em duas constantes que aparecem várias vezes ao longo deste fluxo: TOKEN_EXPIRY_SKEW_SECONDS, usada para considerar o token expirado um pouco antes do prazo real e evitar uma corrida contra o relógio do servidor, e SESSION_VALIDATION_INTERVAL_MS, que define de quanto em quanto tempo o app confirma com o Keycloak que a sessão ainda é válida enquanto o usuário está logado.

Duas identidades, dois tokens

O ponto mais importante para entender esse fluxo antes de mexer em qualquer parte dele é que existem dois tokens com propósitos diferentes. O token principal, obtido no login, prova que o usuário está autenticado no Keycloak e vira o cabeçalho Authorization. O token de contexto de organização é obtido depois, numa troca separada, e vira o cabeçalho X-Contextual-Token, usado para indicar em qual organização as chamadas devem operar. Um rascunho de venda, uma sincronização ou qualquer chamada autenticada dependem dos dois ao mesmo tempo, mas eles são renovados e limpos por caminhos distintos, documentados nos próximos capítulos.