Menu ▾ ▴

autenticacao

Mobius One

Home > Autenticação

Autenticação

Basic e JWT desde a 1.0 · Digest e OAuth2: 2.0 🚧 · Delphi e Lazarus · Nível: intermediário

Em uma frase: fazer o servidor exigir login e o cliente enviá-lo sozinho.

Você vai precisar: um servidor com rotas (Rotas). Login sem HTTPS
manda a senha legível pela rede: em produção, use HTTPS.

Qual escolher

Tipo Como funciona Use quando
Basic usuário e senha em todo pedido sistema interno, sob HTTPS, o mais simples
JWT o cliente troca usuário e senha por um token e envia o token em cada pedido o mais comum em APIs e apps
Digest 2.0 🚧 a senha nunca viaja, só um resumo dela equipamentos e clientes que já falam Digest
OAuth2 2.0 🚧 um servidor de autorização emite tokens para vários sistemas integração entre empresas, login de terceiros

Em todos, o servidor tem um componente TRALServer... (paleta RAL - Server) e
o cliente o par TRALClient... (paleta RAL - Client). Ligue o do servidor em
Servidor.Authentication e o do cliente em Cliente.Authentication.

Uma rota que deve ficar pública mesmo assim: SkipAuth([amGET]) (ou
SkipAuthMethods). Ver Rotas.

Basic

Servidor, um usuário fixo:

RALServerBasicAuth1.UserName := 'admin';
RALServerBasicAuth1.Password := 'segredo';
Servidor.Authentication := RALServerBasicAuth1;

Servidor, conferindo no banco (OnValidate):

procedure TForm1.BasicValidate(ARequest: TRALRequest; AResponse: TRALResponse;
  var AResult: boolean);
var
  vUser: TRALAuthBasic;
begin
  vUser := ARequest.Authorization.AsAuthBasic;
  AResult := ConfereSenha(vUser.UserName, vUser.Password); // a sua função
end;

Cliente:

RALClientBasicAuth1.UserName := 'admin';
RALClientBasicAuth1.Password := 'segredo';
Cliente.Authentication := RALClientBasicAuth1;

AuthDialog := True no servidor faz o navegador abrir a janelinha de login.

JWT

O cliente envia um JSON para a rota /gettoken (POST). O servidor confere e
devolve {"token":"..."}. Daí em diante o cliente manda o token em cada pedido e
pede outro sozinho quando ele vence.

Servidor:

RALServerJWTAuth1.SignSecretKey := 'uma-chave-longa-e-secreta'; // obrigatória
RALServerJWTAuth1.ExpirationSecs := 1800;                      // 30 min (padrão)
Servidor.Authentication := RALServerJWTAuth1;

// OnGetToken: quem pode receber um token
procedure TForm1.JWTGetToken(ARequest: TRALRequest; AResponse: TRALResponse;
  AParams: TRALJWTParams; var AResult: boolean);
var
  vJson: TRALJSONObject;
begin
  vJson := TRALJSONObject(TRALJSON.ParseJSON(ARequest.RequestText)); // unit RALJson
  try
    AResult := (vJson <> nil) and
      ConfereSenha(vJson.Get('usuario').AsString, vJson.Get('senha').AsString);
    if AResult then
      AParams.AddClaim('usuario', vJson.Get('usuario').AsString); // vai dentro do token
  finally
    vJson.Free;
  end;
end;

Cliente:

RALClientJWTAuth1.Payload.AddClaim('usuario', 'ana');  // o JSON enviado a /gettoken
RALClientJWTAuth1.AutoGetToken := True;                 // já é o padrão: pede e renova sozinho
Cliente.Authentication := RALClientJWTAuth1;

Na rota, quem está logado: ARequest.Authorization.AsAuthBearer.Payload.GetClaim('usuario').

Propriedade (servidor) O que faz
SignSecretKey a chave que assina o token. Vazia = erro 500. Longa e fora do código-fonte
ExpirationSecs validade do token em segundos
Algorithm tjaHSHA256 (padrão), 384, 512; RS256/ES256 com chave própria: 2.0 🚧
AuthRoute a rota do token (padrão /gettoken)
UseCookie 1.1: guarda o token num cookie raltoken (para páginas web)
CookieSameSite 1.2 🚧: o SameSite desse cookie
OnValidate regra extra a cada pedido (ex.: usuário bloqueado depois de logado)
OnRenewToken 1.2 🚧: decidir a renovação e mudar as claims

[!WARNING]
Não ponha a senha em AddClaim no servidor: o conteúdo do token é legível por
qualquer um (só a assinatura é protegida).

Digest 2.0 🚧

[!IMPORTANT]
Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.

// servidor: um usuário fixo, ou OnGetCredential para buscar no banco
RALServerDigest1.UserName := 'admin';
RALServerDigest1.Password := 'segredo';
Servidor.Authentication := RALServerDigest1;

// cliente
RALClientDigest1.UserName := 'admin';
RALClientDigest1.Password := 'segredo';
Cliente.Authentication := RALClientDigest1;
Propriedade (servidor) Padrão O que faz
Algorithms SHA-256 e MD5 algoritmos oferecidos (também SHA-512-256)
Realm RAL o nome que o cliente vê
NonceLifetime 300 s validade de cada desafio
ReplayProtection ligado recusa o mesmo pedido repetido
OnGetCredential — devolve a senha (ou o hash) de um usuário

OAuth2 2.0 🚧

[!IMPORTANT]
Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.

O TRALServerOAuth2 faz dois papéis, separáveis:

Papel Liga com O que faz
emitir tokens (servidor de autorização) Issuing := True (padrão) cria /oauth/token, /oauth/authorize, /oauth/introspect, /oauth/revoke, /oauth/jwks e /.well-known/oauth-authorization-server
conferir tokens (servidor de recursos) Validation ovLocal (os próprios), ovJWKS (de outro emissor, por JWKSURL), ovIntrospection (pergunta ao emissor, IntrospectionURL)

Fluxos (Grants): client_credentials (sistema com sistema), authorization_code
com PKCE obrigatório (usuário que faz login numa página), refresh_token. Senha
direta e fluxo implícito não existem, de propósito.

Servidor emitindo tokens para sistemas:

RALServerOAuth2_1.SignSecretKey := 'uma-chave-longa-e-secreta';
RALServerOAuth2_1.OnValidateClient := ValidaCliente; // confere ClientID e ClientSecret
Servidor.Authentication := RALServerOAuth2_1;

Cliente:

RALClientOAuth2_1.TokenURL := '/oauth/token';
RALClientOAuth2_1.ClientID := 'erp';
RALClientOAuth2_1.ClientSecret := 'segredo-do-erp';
RALClientOAuth2_1.Scope := 'pedidos';
Cliente.Authentication := RALClientOAuth2_1;

Escopo por rota (RouteScopes, uma linha rota=escopos) responde 403 a quem não
tem o escopo. Para login de usuário num app de desktop, TRALOAuth2Loopback recebe
o retorno do navegador em 127.0.0.1.

OAuth 1.0a

Removido na 2.0 🚧: nunca funcionou e não é o antecessor do OAuth2.

Erros comuns

O que acontece Causa O que fazer
401 em tudo o cliente não tem Authentication, ou a senha não bate confira o par cliente/servidor
401 numa rota pública a autenticação vale para todas as rotas SkipAuth([...]) na rota
JWT: 500 SignSecretKey chave vazia preencha SignSecretKey
JWT: o servidor não emite token OnGetToken não foi atribuído, ou deixou AResult = False atribua e confira as credenciais ali
403 depois de várias tentativas proteção contra força bruta bloqueou o IP espere ExpirationTime, ou libere o IP; ver Segurança
WWW-Authenticate: Bearer error="invalid_token" token vencido, assinatura errada ou recusado em OnValidate a descrição do erro diz qual

Veja também


Related

Wiki: _Sidebar
Wiki: atualizacao
Wiki: broker mqtt
Wiki: cliente
Wiki: dbware
Wiki: https
Wiki: mqtt seguranca
Wiki: pedido resposta
Wiki: plugins
Wiki: problemas comuns
Wiki: recursos
Wiki: roadmap
Wiki: rotas
Wiki: seguranca
Wiki: stored procedures