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:
| Doc | Conteúdo |
|---|---|
| Visão Geral (este documento) | Arquitetura em camadas e papel de cada peça |
| Login e Callback OAuth | PKCE, navegador externo, deep link de retorno e troca do código por tokens |
| Refresh de Token | Os dois gatilhos de renovação e o lock que evita chamadas concorrentes |
| Logout | O que é limpo e a chamada de encerramento de sessão no Keycloak |
| Guardas de Rota e Inicialização | Como o app decide para onde navegar ao abrir e como as rotas protegidas reagem à ausência de sessão |
| Contexto de Organização | Como 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
| Arquivo | Responsabilidade |
|---|---|
services/auth/auth.config.ts | Monta 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.ts | PKCE, troca de código por tokens, refresh, logout e verificação de sessão ativa no Keycloak |
services/auth/auth.storage.ts | Persistência em MMKV dos tokens e do contexto de organização, com hooks reativos useAuthTokens e useAuthContextsTokens |
providers/auth/auth.provider.tsx | Contexto 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.tsx | Guarda 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.ts | Tela de login e seu controller |
services/api.service.ts | Cliente Axios com interceptors que injetam os headers de autenticação e tratam o 401 |
services/organization/organization.service.ts + storage/organization/organization.storage.ts | Busca, seleção e persistência do contexto de organização |
app/index.tsx | Ponto 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.