Este documento apresenta minhas anotações sobre autenticação de usuários para criar um servidor provedor de autenticação baseado no OpenID e o protocolo OAuth 2.0 em node.js.
Inspirado em como as grandes empresas, como Microsoft, Google, Facebook, Twitter realizam a autenticação de usuários e permitem que terceiros interajam com uma aplicação de forma autenticada.
Eu tinha somente a opção de escolher o uso das linguagens C# e JavaScript, com preferência para esse último.
A biblioteca do JavaScript se mostrou muito melhor em termos de documentação, sem estar atrelada à telas prontas e ainda é o pacote mais certificado e com mais opções de uso, conforme abaixo.
Conjunto | Perfil | Node.js (node oidc-provider) | C# (IdentityServer4) |
---|---|---|---|
Servers | Basic OP | ✅ | ✅ |
Servers | Implicit OP | ✅ | ✅ |
Servers | Hybrid OP | ✅ | ✅ |
Servers | Config OP | ✅ | ✅ |
Servers | Dynamic OP | ✅ | |
Servers | Form Post OP | ✅ | |
Servers | 3rd Party-Init OP | ✅ | |
Logout | RP-Initiated OP | ✅ | |
Logout | Session OP | ✅ | |
Logout | Front-Channel OP | ✅ | |
Logout | Back-Channel OP | ✅ | |
FAPI | FAPI R/W OP w/ MTLS | ✅ | |
FAPI | FAPI R/W OP w/ Private Key | ✅ |
Todas as outras linguagens, frameworks, serviços online e na nuvem não oferecem tantos perfis quanto o node oidc-provider
que é a implementação mais completa.
Além disso, ele oferece somente a especificação, sem estar preso à um framework ou telas prontas.
Este documento não é um guia oficial e possui uma linguagem mais direta e informal dos conceitos. É puramente anotações e interpretações minhas sobre a leitura de toda documentação e experiências profissionais.
Além disso esse documento é um estudo para um projeto pessoal, e podem fazer referências à tecnologias utilizadas pelo projeto, não necessárias para o servidor de autenticação ou para entender o conceito.
É focado principalmente na criação de um servidor de autenticação, porém as anotações são para interação com o servidor, não entrando em especificações de como um servidor OP deva funcionar, tornando por base que o node oidc-provider
já tem em sua implementação as validações e regras no lado do servidor.
OBS: muitas vezes é usado a sigla IdP para referir-se ao servidor de autenticação (Authorization Server, Identity Provider ou IdP, OpenID Provider ou OP), ou seja, o lugar onde está sendo executado o node oidc-provider.
- RFC6749 - OAuth 2.0 Authorization Framework
- RFC6750 - Bearer Token Usage
- OpenID Core 1.0
- node oidc-provider
- node openid-client
- RFC7515 - JWS
- Protocolo HTTP
- OAuth 2.0 (recomendável)
- JWT (Header, Payload, Signature)
- Identity (ou Identidade) é um conjunto de atributos relacionados à uma entidade (ISO29115). No mundo real a entidade pessoa pode ter várias identidades: Pai, Funcionário, Marido...
- Uma camada de Identidade provê (5W1H):
- Who: quem é o usuário autenticado
- Where: onde ele está autenticado
- When: quando ele se autenticou
- How: como se autenticou
- What: quais atributos ele pode oferecer
- Why: porquê ele está oferecendo
- OpenID = autenticação (certifica que você é você)
- OAuth 2.0 = autorização (certifica que você acesse só o que você tem permissão)
As anotações abaixo referenciam a especificação OpenID Connect Core 1.0.
Esta é base principal do OpenID, dos fluxos de autenticação.
O fluxo mais comum. Uma aplicação percebe que não há sessão e redireciona o usuário para uma tela de login. Após informado usuário e senha, este redireciona para a aplicação de volta junto com um código temporário. No back-end, o código temporário serve para trocar o código por um token de acesso e de id.
Vantagens:
- Não expõe o token para o navegador (User Agent).
- O cliente recebe publicamente um código, e internamente troca por um token de acesso.
HTTP/1.1 302 Found
Location: https://server.example.com/authorize?
response_type=code
&scope=openid%20profile%20email
&client_id=s6BhdRkqt3
&state=af0ifjsldkj
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
Parâmetro | Valor e descrição |
---|---|
response_type |
code |
scope |
openid |
client_id |
Código da aplicação requisitando um token. |
redirect_uri |
Deve ser https .Deve estar registrado no IdP. |
state |
Evita CSRF e XSRF. Pode ser o hash de um cookie do navegador no cliente. |
nonce |
Deve ser um hash relacionado à sessão (pode ser baseado em cookie HttpOnly com um valor aleatório. |
prompt |
Lista separada por espaço de como o IdP pergunta pro usuário sobre consentimento ou re-autenticação.none não faz nada (usado pra checar se tem permissões e sessão)login (só mostra tela de autenticação)consent (só pede consentimento)select_account (pede para o usuário escolher uma das contas ativas) |
max_age |
Tempo máximo em segundos em que um usuário é considerado autenticado. Quando esse tempo termina, uma requisição nova no endpoint de autorização, irá pedir que o usuário se autentique novamente). |
ui_locales |
Lista separada por espaço e em ordem de preferência dos idiomas para a tela de login. Ex: pt-BR pt en |
id_token_hint |
Envia um token previamente emitido pelo IdP. Retorna sucesso quando já está autenticado, ou um erro caso contrário. Deve ser usado quando o prompt=none é informado.O próprio IdP tem que estar como audiência aud do token. |
login_hint |
Informa um endereço de e-mail ou telefone que será preenchido automaticamente na tela de login. |
HTTP/1.1 302 Found
Location: https://client.example.org/cb?
error=invalid_request
&error_description=Unsupported%20response_type%20value
&state=af0ifjsldkj
Erro | Descrição |
---|---|
interaction_required |
Quando prompt=true porém é preciso exibir uma tela. |
login_required |
Quando prompt=true porém é preciso exibir a tela de login. |
account_selection_required |
Quando prompt=true porém é preciso exibir a tela para selecionar a sessão. |
consent_required |
Quando prompt=true porém é preciso exibir a tela de consentimento. |
invalid_request_uri |
Quando request_uri está inválido. |
invalid_request_object |
Algo na requisição é inválido. |
request_not_supported |
Não suporta a requisição informada. |
request_uri_not_supported |
Não suporta o request_uri informado. |
registration_not_supported |
Não suporta o registration informado. |
HTTP/1.1 302 Found
Location: https://client.example.org/cb?
code=SplxlOBeZQQYbYS6WxSbIA
&state=af0ifjsldkj
Item | Validação |
---|---|
code |
Obrigatório. |
state |
Obrigatório se foi informado na requisição. Deve ser idêntico ao informado antes. |
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
Parâmetro | Valor e descrição |
---|---|
grant_type |
authorization_code |
code |
O código recebido na requisição de Autenticação. |
redirect_uri |
A URL que será redirecionado após o login. Note que essa URL precisa ser idêntica a informada na requisição de Autenticação. |
client_id |
O identificador do cliente (aplicação). |
HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"error": "invalid_request"
}
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"example",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
Item | Validação |
---|---|
access_token |
Deve existir. |
token_type |
Deve ser Bearer . |
id_token |
Deve existir e validar conforme descrito abaixo. |
expires_in |
Segundos em que o token irá expirar após a requisição. |
refresh_token |
Token que pode ser usado para renovar o atual após a expiração. |
scope |
Deve existir se foi informado na requisição, e deverá ser idêntico. |
- Retorna o token de acesso em um única requisição (o endpoint de Token não é utilizado), útil quando trabalhamos com SPAs, por exemplo.
- Em contra partida, tem uma segurança menor e por segurança não deve ser usado em aplicações que não são SPA (como as que tem um back-end).
A requisição de autenticação implícita, acontece da mesma forma que a requisição de autorização por código de autorização, exceto por alguns parâmetros diferentes:
Parâmetro | Valor e descrição |
---|---|
response_type |
Deve ser id_token token . |
nonce |
Se torna obrigatório ser informado. |
A validação e outros processos ocorrem da mesma forma que a requisição de autorização por código de autorização, porém ao obter uma resposta, no redirecionamento de volta para a aplicação é enviados todos os parâmetros direto na URL:
Parâmetro | Descrição |
---|---|
access_token |
O token de acesso. |
token_type |
O tipo do token obtido. Sempre Bearer . |
id_token |
O token de ID. |
state |
O valor de estado informado na requisição. |
expires_in |
Segundos em que o token irá expirar. |
Por exemplo:
HTTP/1.1 302 Found
Location: https://client.example.org/cb#
access_token=SlAV32hkKG
&token_type=bearer
&id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso
&expires_in=3600
&state=af0ifjsldkj
Quando receber a resposta, é interessante a aplicação validar em um servidor (back-end) o que foi recebido. Um exemplo de código JS presente na página de callback pode ser:
<script type="text/javascript">
// Primeiro pegamos os parâmetros
var params = {},
postBody = location.hash.substring(1),
regex = /([^&=]+)=([^&]*)/g,
m;
while (m = regex.exec(postBody)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
// E enviamos o token para os servidor
var req = new XMLHttpRequest();
// usando o POST, assim não é registrado no histórico do navegador
req.open('POST', 'https://' + window.location.host + '/catch_response', true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.onreadystatechange = function (e) {
if (req.readyState === 4) {
if (req.status === 200) {
// Se foi OK, então redireciona para a tela principal
window.location = 'https://' + window.location.host + '/apos_login'
}
// Se for inválido, gera um erro
else if (req.status === 400) {
alert('Ocorreu um erro ao processar o token.')
} else {
alert('Alguma coisa deu errada.')
}
}
};
req.send(postBody);
</script>
Esses parâmetros adicionais estarão presentes no ID Token recebido:
Claim | Descrição |
---|---|
nonce |
O número aleatório informado antes. |
at_hash |
Um hash que representa o token recebido e deve ser usado para garantir a legitimidade do mesmo. |
A autenticação híbrida é uma mistura do implícito e do código de autorização, recebendo assim alguns tokens direto na URL de callback e outros através da requisição de Token.
A requisição, assim como outros processos, também acontece da mesma forma que a requisição de autorização por código de autorização, exceto que:
Parâmetro | Valor e descrição |
---|---|
response_type |
Deve especificar o que será retornado diretamente, como:code id_token , code token , code id_token token . |
O tipo escolhido de resposta indicará o que irá retornar direto na URL de callback:
Tipo de resposta | Descrição |
---|---|
code |
Obrigatório. Irá sempre retornar o código de autorização. |
id_token |
Retorna o token de ID para identificação do usuário. |
token |
Retorna o token de acesso. |
Os tokens que não são retornados diretamente, podem ser obtidos usando o code na requisição de token.
4. Fluxo do dispositivo RFC 8628
Este fluxo acontece quando temos dispositivos conectados à Internet que não possuem um navegador ou não tem uma forma de escrever dados (como a falta de um teclado, por exemplo). Ele permite que os clientes OAuth presentes nestes dispositivos (como Smart TVs, quadros digitais e impressoras) possam obter um token de acesso.
Para que isso ocorra, é importante assegurar que:
- Os dispositivo está conectado à Internet e pode fazer chamadas HTTPS de saída.
- O dispositivo é capaz de exibir ou informar uma URI e um código para o usuário
- O usuário tem um dispositivo secundário (como um PC ou smartphone) que possa interagir com a requisição.
Após mostrar o código para o usuário, o dispositivo fica fazendo chamadas repetidas para o servidor de autorização até que ele obtenha acesso, se não houver uma forma de enviar requisições de entrada para o dispositivo.
Normalmente o dispositivo só interage com um único servidor de autorização.
A primeira etapa, é o dispositivo solicitar os códigos de verificação para o servidor de autorização no endpoint de autenticação de dispositivos, informando o ID do cliente (client_id
) e escopos (scope
) desejados.
POST /device_authorization HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
client_id=1406020730&scope=example_scope
Todas as requisições devem ser feitas através de TLS/SSL.
O servidor de autorização responde com um JSON informando dois códigos de verificação: de dispositivo e de usuário que valem por determinado tempo.
Campo | Descrição |
---|---|
device_code |
O código de verificação do dispositivo. |
user_code |
O código de verificação do usuário. |
verification_uri |
A URI que deve ser curta e será exibida para o usuário digitar em outro dispositivo. |
verification_uri_complete |
Uma URI opcional que pode incluir o código de usuário (ou funcionalidade similar). |
expires_in |
O tempo em segundos em que os códigos irão expirar. |
interval |
O tempo em segundos em que o dispositivo deve aguardar antes de fazer uma nova requisição para o IdP, perguntando sobre o estado atual. Por padrão esse valor é 5 . |
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.com/device",
"verification_uri_complete":
"https://example.com/device?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}
Quando o dispositivo recebe os códigos de verificação, ele deverá mostrar a URI de autorização (verification_uri
) e o código de usuário (user_code
) para o usuário, solicitando-o que acessa a URI em um outro dispositivo e para que digite o código informado.
Note que o código de verificação de dispositivo (
device_code
) não deve ser exibido!
Quando o campo verification_uri_complete
é recebido, o dispositivo pode exibi-lo de uma forma não textual (devido a URI longa e já informando o código de usuário), como por exemplo, exibindo um QR Code que irá abrir essa URL ou através de NFC. Porém é recomendável que o verification_uri
continue sendo exibido.
No servidor de autorização, deve fornecer uma tela para que seja informado o código de usuário. O servidor pode requisitar outros fluxos e processos antes, como solicitar a autenticação do usuário ou seu cadastro, solicitar um código OTP, entre outros.
Após informar o código, ou após acessar a URI completa (via QR Code por exemplo), o servidor deverá exibir uma tela solicitando que o usuário aprove ou rejeite o uso daquele dispositivo.
Enquanto o usuário interage com o servidor de autorização, o dispositivos faz chamadas repetidas até que o processo seja concluído, o token expire ou aconteça algum outro erro, respeitando o intervalo informado.
O dispositivo faz uma requisição no endpoint de token, porém informando o grant_type
como urn:ietf:params:oauth:grant-type:device_code
e o código de dispositivo:
POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
&client_id=1406020730
Quando o usuário finalmente aprova e dá consentimento, o endpoint de token responde com os tokens de acordo com o informado acima na requisição de token do fluxo de código de autorização
Porém existe uma extensão dos erros padrões, conforme:
Código de erro | Descrição | Deve continuar? |
---|---|---|
authorization_pending | Informa que está aguardando uma interação do usuário ou ela está sendo feita no momento. | Sim |
slow_down | Informa que ainda está aguardando uma interação do usuário, porém o dispositivo deve aumentar o intervalo das próximas chamadas em 5 segundos. | Sim |
access_denied | O usuário recusou seu consentimento e o dispositivo não está permitido. | Não |
expired_token | O token informado já expirou. | Não |
Quando o dispositivo recebe uma resposta de erro que não é possível continuar, então ele deve exibir alguma mensagem na tela para o usuário e deve parar de contactar o servidor para verificação.
Note que a especificação original aborda uma hipótese de que o dispositivo não consegue receber requisições de entrada. Porém, se suportado, pode-se adotar outras abordagens, como o uso de WebSockets - dessa forma o dispositivo recebe o evento de autorização ou erro sem ter que fazer chamadas repetidas.
Se o dispositivo receber um erro de timeout, então é recomendado aumentar exponencialmente o intervalo para cada nova chamada com erro.
# | Item | Validação |
---|---|---|
1 | criptografado | Se o cliente especificou uma criptografia no seu registro. Se foi solicitado um token criptografado e veio um sem, deve ser rejeitado. |
2 | iss |
O claim de emissor deve bater exatamente com o identificador do IdP, ou seja, a URL que foi chamada para autenticar, ou do serviço de descoberta. |
3 | aud |
A claim de audiência deve conter o client_id , e na sua ausência deve ser rejeitado.Se a lista de audiência conter outros destinos que o cliente não confia, também deve rejeitar. |
4 | azp |
Deve existir se o token tem várias audiências/públicos. Se existir deve verificar que seu client_id é um valor de Claim. |
5 | TLS | No fluxo de Authorization Code pode validar o TLS do servidor do IdP. |
6 | alg |
O valor deve ser RS256 ou o valor informado no registro pelo cliente em id_token_signed_response_alg . |
7 | exp |
A data atual deve ser anterior à data de expiração do token. |
8 | iat |
A data do claim iat pode ser usada para rejeitar tokens emitidos há muito tempo. |
9 | nonce |
Se o valor do nonce foi enviado na requisição de Autenticação, então o valor deve ser exatamente o mesmo no token. |
10 | acr |
Se o acr foi informado na requisição, deve-se checar se o valor é apropriado. |
11 | auth_time |
Se o auth_time foi informado na requisição, ou o max_age então esta claim deve ser validada.Deve ser solicitada uma nova autenticação se muito tempo se passou desde a última autenticação do usuário. |
RFC7519 O resultado de 3 JSONs codificados em base64: Cabeçalho.Corpo.Assinatura
.
Note que após a decodificação do JWT usando o base64, todo seu conteúdo se torna público.
RFC7515 O JWS é um JWT com assinatura (o mesmo que acima). Dessa forma é possível verificar que um Corpo (payload) não foi alterado após sua geração.
Obrigatório no OpenID!
RFC7516 Diferente dos dois acimas, o JWE é um JWT que possui seus dados criptografados. Ou seja, não é possível ver seu conteúdo facilmente, sem o uso de uma chave.
Segue o padrão: Cabeçalho.Chave.Vetor.Corpo.Marca
.
Opcional no OpenID
Ou Nested JWT é um JWT dentro do outro. No OpenID geramos um JWS (o token com o payload e assinado) e então colocamos esse token dentro de outro JWT, mas criptografado, gerando um JWE.
JWT aninhado (padrão JWS em JWE)
Os JWTs do OpenID contém várias claims (reivindicações/direitos).
Alguns são padrões e estão definidos na RFC7519.
Membro | Tipo | Descrição |
---|---|---|
aud |
texto ou lista | Deve conter pelo menos o client_id do destino. |
exp |
número | O timestamp (segundos desde 1970) de quando o token irá expirar. |
auth_time |
número | Timestamp do momento em que o usuário foi autenticado. Presente quando o cliente solicitou require_auth_time . |
nonce |
texto | Um valor informado na requisição para validar o uso do token na sessão. |
amr |
lista | Lista de identificadores de métodos de autenticação usados para autenticar o usuário. Ver lista abaixo |
Membro | Tipo | Descrição |
---|---|---|
sub |
texto | Identificador único (no emissor) do usuário. |
name |
texto | Nome completo do usuário. |
given_name |
texto | Primeiro nome do usuário. |
family_name |
texto | Sobrenome do usuário. |
middle_name |
texto | Nome do meio do usuário (se existir). |
nickname |
texto | Nome casual do usuário, ou como prefere ser chamado. ex.: Gabs |
preferred_username |
texto | Nome curto que representa o usuário, ou o "login". Ex.: gcacars ou [email protected] |
profile |
texto | URL da página de perfil do usuário. |
picture |
texto | URL da imagem de perfil do usuário (não uma qualquer). |
website |
texto | URL do Blog, site ou página na organização do usuário. |
email |
texto | Endereço de e-mail preferido do usuário. |
email_verified |
booleano | true se foi adotada maneiras de garantir que este e-mail é controlado por este usuário. |
gender |
texto | male , female ou outro valor. |
birthdate |
texto | Data de nascimento no formato YYYY-MM-DD ou YYYY .Se o ano for 0000 , então o valor foi omitido. |
zoneinfo |
texto | Fuso horário do usuário. ex.: America/Sao_Paulo Consultar lista |
locale |
texto | Localização ou idioma do usuário. ex.: pt-BR Consultar lista |
phone_number |
texto | Número de telefone preferencial do usuário. ex.: +5511955552222 |
phone_number_verified |
booleano | true quando foi adotado maneiras de garantir que esse telefone realmente pertence à este usuário. |
address |
objeto JSON | Endereço postal do usuário. |
updated_at |
número | Timestamp da última vez que as informações foram atualizadas. |
Para representar uma claim em vários idiomas, usa-se o formato [claim]#[idioma][-[PAÍS]]?
, como por exemplo:
Claim | Valor |
---|---|
profile |
https://exemplo.com.br/gabriel |
profile#en-US |
https://example.com.us/gabriel |
profile#es |
https://ejemplo.com.ar/gabriel |
O Authentication Context Class Reference especifica um conjunto de regras de negócio que uma requisição de autenticação deve satisfazer.
Um cliente solicita no início do processo quais ACRs devem ser satisfeitos através do parâmetro acr_values
. Ao final do processo o token ID gerado irá conter uma lista das regras que foram satisfeitas na claim acr
(e não como foram).
Quando esse valor é 0
, significa que tem um nível baixo de segurança e não atende os requisitos da ISO 29115, ou seja, um cliente não deve permitir nenhuma movimentação monetária, por exemplo. Esse valor pode/deve estar presente no token quando por exemplo a autenticação foi feita através de um token de longo prazo de expiração (como quando há um )
O Authentication Methods References informa quais métodos de autenticação foram usados para garantir um certo nível de garantia (RFC6711) de que o usuário informado no token é realmente o usuário da sessão, como quando após digitar a senha, o usuário ainda precisa usar sua digital no celular para confirmar o acesso. Ou quando um token OTP é necessário.
A claim amr
presente no token irá informar uma lista de métodos utilizados na autenticação. (veja a lista abaixo)
Note que geralmente os métodos de autenticação utilizados em um fluxo de autenticação, são aqueles necessários para satisfazer o parâmetro acr_values
informado na requisição. Dessa forma há uma ligação entre o acr
e o amr
recebido no token.
Note que o amr
informa os métodos técnicos utilizados para satisfazer o processo e por isso é recomendado que não seja utilizado pelos clientes (ou relying parties) para permitir acesso à recursos, visto que os métodos podem variar com o tempo com a necessidade de métodos com maior segurança.
Ao contrário, sendo que possível, a aplicação (cliente) deve verificar se os valores de acr
tem o resultado esperado para liberar acesso aos recursos, como fazer pagamentos por exemplo.
A claim amr
segundo a RFC8176 pode ter uma lista com os seguintes valores:
Valor | Descrição |
---|---|
pwd |
Autenticação baseada em senha. |
pin |
Número de identificação pessoal. |
sms |
Confirmação de acesso via envio de um texto por SMS. |
tel |
Confirmação de acesso via uma ligação para um número. |
mfa |
Autenticação de múltiplo fator. Quando estiver presente, deve incluir os métodos usados. |
otp |
One-time password. Senhas de uso único, como as geradas em Autenticadores no celular. |
mca |
Autenticação de múltiplo canal. Envolve mais de um canal de comunicação distinto. |
face |
Autenticação biométrica usando reconhecimento facial. |
fpt |
Autenticação biométrica usando uma digital (dedo). |
iris |
Autenticação biométrica usando um escaneamento de íris. |
retina |
Autenticação biométrica usando um escaneamento de retina. |
vbm |
Autenticação biométrica usando uma impressão de voz. |
wia |
Autenticação integrada do Windows. |
geo |
Uso de informação geográfica para autenticação. |
swk |
Prova de possessão através de um software de chave. |
hwk |
Prova de possessão através de um dispositivo de chave. |
kba |
Autenticação por base de conhecimento. |
sc |
Uso de cartões inteligentes. |
Deve ser acessado uma rota enviando o token de acesso no cabeçalho.
GET /userinfo HTTP/1.1
Host: server.example.com
Authorization: Bearer SlAV32hkKG
HTTP/1.1 200 OK
Content-Type: application/json
{
"sub": "248289761001",
"name": "Fulano de Tal",
"given_name": "Fulano",
"family_name": "Tal",
"preferred_username": "f.tal",
"email": "[email protected]",
"picture": "http://exemplo.com.br/f.tal/eu.jpg"
}
Retorna um cabeçalho de resposta para identificar o tipo de autenticação que deve ser usado para conseguir acesso ao recurso. WWW-Authenticate
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="openid",
error="invalid_token",
error_description="O token de acesso expirou!"
Define como as aplicações cliente (relying parties) conseguem se registrar (castro inicial), usando a especificação OpenID Connect Dynamic Registration.
Metadado | Descrição |
---|---|
redirect_uris |
Lista de URIs usados pelo cliente. Deve ser idêntico ao informado na requisição de autenticação em redirect_uri .Deve estar presente também em sector_identifier_uri |
response_types |
Uma lista que restringe ele mesmo dos tipos de resposta (response_type ) que pode obter.De: code , token , id_token |
grant_types |
Lista de auto-restrição dos tipos de grant_type que pode usar.De: authorization_code , implicit , refresh_token . |
application_type |
O tipo de aplicação, por padrão é web , mas pode ser também native . |
contacts |
Lista de e-mails dos responsáveis. |
client_name |
Nome da aplicação que é apresentado para o usuário. |
logo_uri |
URL da imagem do logotipo apresentado para o usuário. |
client_uri |
URL da página inicial da aplicação. |
policy_uri |
URL da política de privacidade, informando como os dados do usuário são usados. (LGPD) |
tos_uri |
URL da página de termos de serviço. |
subject_type |
Uma lista de tipos de sujeito que a aplicação pode usar. De: public ou pairwise .No nexso aplicações externas devem usar pairwise obrigatoriamente. (LGPD) |
default_max_age |
Validade máxima padrão da autenticação em segundos. Nesse período o usuário deve se manter ativo. |
require_auth_time |
Booleano que indica se a claim auth_time deve estar presente. Por padrão é false . |
default_acr_values |
Lista que especifica os valores padrões de acr em ordem de preferência. |
initiate_login_uri |
URI em https que um terceiro pode usar para iniciar o login. Deve aceitar tanto os métodos GET e POST como os parâmetros login_hint , iss e target_link_uri . |
request_uris |
Lista de URIs que serão pré-registradas para essa aplicação no IdP, habilitando o cache dos dados. A URI pode conter um fragmento que é o hash SHA-256 do conteúdo do arquivo usado para versionamento. |
sector_identifier_uri |
URL usando https para calcular um identificador pseudônimo para as contas pelo IdP, referenciando um arquivo JSON com a lista de URLs.Relativo ao pairwise e deve conter as URIs que estão em redirect_uris .Provê uma forma de alterar o redirect_uri sem ter que registrar novamente todos seus usuários. |
request_object_signing_alg |
Algoritmo usado para verificar a assinatura quando a requisição de autenticação ocorre com os dados em um JWT, tanto por valor request ou por referência request_uri . |
request_object_encryption_alg |
Algoritmo criptográfico para usar no JWE para criptografar o token de requisição de autenticação. |
token_endpoint_auth_method |
Forma de autenticação da aplicação (cliente) na requisição de token. Pode ser: client_secret_basic (padrão), client_secret_post , client_secret_jwt , private_key_jwt .No FAPI ainda existe: tls_client_auth e self_signed_tls_client_auth . |
token_endpoint_auth_signing_alg |
Algoritmo usado para assinar o JWT usado para autenticar o cliente quando o método é private_key_jwt ou client_secret_jwt . |
id_token_signed_response_alg |
Algoritmo usado para assinar o JWS. Por padrão: RS256 . |
id_token_encrypted_response_alg |
Algoritmo de criptografia usado no JWE. Por padrão: A128CBC-HS256 . |
userinfo_signed_response_alg |
JWS algoritmo para assinar respostas da UserInfo. (opcional) |
userinfo_encrypted_response_alg |
Algoritmo para usar no JWE para criptografar a resposta da UserInfo. (opcional) |
userinfo_encrypted_response_enc |
Algoritmo criptográfico para usar no JWE para criptografar a resposta da UserInfo. (opcional) |
jwks_uri |
URL do JSON Web Key Set da aplicação. |
Os metadados client_name
, tos_uri
, policy_uri
, logo_uri
, client_uri
podem ter opções em outros idiomas usando o # e o código do idioma conforme descrito acima.
A requisição contém os metadados indicados acima.
POST /connect/register HTTP/1.1
Content-Type: application/json
Accept: application/json
Host: server.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJ ...
{
"application_type": "web",
"redirect_uris": ["https://client.example.org/callback",
"https://client.example.org/callback2"],
"client_name": "My Example",
"client_name#ja-Jpan-JP": "クライアント名",
"logo_uri": "https://client.example.org/logo.png",
"subject_type": "pairwise",
"token_endpoint_auth_method": "client_secret_basic",
"request_uris":
["https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA"]
}
No campo sector_identifier_uri
deve ser informado o caminho para um arquivo JSON.
A requisição seria algo como:
GET /file_of_redirect_uris.json HTTP/1.1
Accept: application/json
Host: other.example.net
E deve obter como resposta:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
[ "https://client.example.org/callback",
"https://client.example.org/callback2",
"https://client.other_company.example.net/callback" ]
A resposta retorna os metadados informados na requisição e mais os valores padrões, além do ID gerado para o cliente e o segredo (se aplicável) conforme a tabela:
Metadado | Descrição |
---|---|
client_id |
O identificador único do cliente (aplicação). |
client_secret |
O segredo gerado para a aplicação conseguir se autenticar na requisição de Token. |
registration_access_token |
Um token de acesso que pode ser usada pela aplicação para usar outros métodos. |
registration_client_uri |
URI do local onde o cliente pode usar o token de acesso acima para executar outras operações. |
client_id_issued_at |
Timestamp de quando o registro foi criado. |
client_secret_expires_at |
Timestamp de quando o segredo irá expirar, ou 0 caso não tenha data de validade. |
HTTP/1.1 201 Created
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"client_id": "s6BhdRkqt3",
"client_secret": "ZJYCqe3GGRvdrudKyZS0XhGv_Z45DuKhCUk0gBR1vZk",
"client_secret_expires_at": 1577858400,
"registration_access_token": "this.is.an.access.token.value.ffx83",
"registration_client_uri": "https://server.example.com/connect/register?ient_id=s6BhdRkqt3",
"token_endpoint_auth_method": "client_secret_basic",
"application_type": "web",
"redirect_uris": ["https://client.example.org/callback",
"https://client.example.org/callback2"],
"client_name": "My Example",
"client_name#ja-Jpan-JP": "クライアント名",
"logo_uri": "https://client.example.org/logo.png",
"subject_type": "pairwise",
"request_uris": ["https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA"]
}
- Deve usar TLS, visto que trafega as credenciais em texto puro.
- Personificação do logotipo: um trapaceiro pode tentar imitar a tela de permissão usando o logotipo oficial. Para garantir isso podemos adotar (no contexto do nexso):
- O servidor de autenticação deve exibir um alerta quando o logotipo não vem do domínio do cliente.
- A imagem do logotipo pode ter restrição de acesso, permitindo apenas o IdP acessar a imagem.
- Restringir o acesso ao serviço apenas à rede interna, ou aplicações autorizadas.
No OpenID a sessão se inicia quando a aplicação valida o ID Token recebido através do parâmetro session_state
, calculado com base no ID do Cliente, na URL de origem e User Agent do navegador. Por padrão a funcionalidade de encerrar uma sessão (logout) não está ativada.
No oidc-provider
ela pode ser ativada com o código abaixo, habilitando ainda as funcionalidades check_session_iframe
(detecção de sessão) e end_session_endpoint
(processo para encerrar uma sessão).
const oidc = new Provider('http://localhost:3000', {
features: {
sessionManagement: true
}
});
Na aplicação podemos ter um iframe invisível incluso, que de tempos em tempos verifica se o usuário ainda tem sessão ativa e se ainda é o mesmo, através da troca de mensagens com o iframe enviando o código de estado recebido acima. Por segurança deve garantir que a troca de mensagens ocorre com a origem certa.
Um exemplo de código pode ser:
var stat = "unchanged";
var mes = client_id + " " + session_state;
var targetOrigin = "https://server.example.com"; // Validates origin
var opFrameId = "op";
var timerID;
function check_session() {
var win = window.parent.frames[opFrameId].contentWindow;
win.postMessage(mes, targetOrigin);
}
function setTimer() {
check_session();
timerID = setInterval(check_session, 5 * 1000);
}
window.addEventListener("message", receiveMessage, false);
function receiveMessage(e) {
if (e.origin !== targetOrigin) {
return;
}
stat = e.data;
if (stat === "changed") {
clearInterval(timerID);
// e fazer as ações descritas abaixo...
}
}
setTimer();
Quando detectar que uma sessão foi alterada, deve tentar uma requisição com prompt=none
dentro do iframe para obter um novo ID Token, enviando o ID Token anterior em id_token_hint
.
Se receber um novo basta atualizar na sessão, porém deve verificar se pertence ao mesmo usuário. Caso contrário, ou em caso de erro, deve fazer logout.
A aplicação também deve carregar um iframe do servidor de autenticação check_session_iframe
, e o servidor deve garantir que quem está chamando é uma origem esperada.
Quando receber uma mensagem de verificação, o iframe deve pegar o User Agent (que deve estar em um cookie ou no Local Storage) e calcular novamente o hash do session_state
, se URL de origem ou ID do cliente não puderem ser detectados, então um error
é enviado para a aplicação como resposta, ou se o cálculo difere changed
é enviado demonstrando que não é a mesma sessão, e unchanged
é enviado quando tudo está certo.
Um exemplo de código pode ser:
window.addEventListener("message", receiveMessage, false);
function receiveMessage(e){ // e.data contém client_id e session_state
var client_id = e.data.split(' ')[0];
var session_state = e.data.split(' ')[1];
var salt = session_state.split('.')[1];
// se a mensagem for sintaticamente inválida retorna
// postMessage('error', e.origin)
// se a mensagem vem de uma origem não esperada retorna
// postMessage('error', e.origin)
// get_op_user_agent_state() é uma função definida no OP
// que retorna o estado do login de User Agent no OP.
// Como isso é feito, ele é quem decide.
var opuas = get_op_user_agent_state();
// Aqui, a session_state é calculada desse jeito particular,
// mas é inteiramente responsabilidade do OP decidir como fazer
// de acordo com os requerimentos definidos na especificação.
var ss = CryptoJS.SHA256(client_id + ' ' + e.origin + ' ' + opuas + ' ' + salt) + "." + salt;
var stat = '';
if (session_state === ss) {
stat = 'unchanged';
} else {
stat = 'changed';
}
e.source.postMessage(stat, e.origin);
};
O estado da sessão é alterado quando ocorre algum evento significativo: entrou, saiu, adicionou uma nova sessão...
O parâmetro check_session_iframe
deve ser fornecido pelo OpenID Provider Discovery, retornando a URL que o iframe irá carregar, aceitando requisições cross origens e usando o HTML postMessage API. Deve usar https
.
Descreve o processo de encerramento de sessão pela aplicação especificado em OIDC Front-Channel Logout 1.0 e OIDC Back-Channel Logout 1.0.
Esse processo ocorre no navegador do usuário, incorporando uma URL da aplicação (cliente/Relying Party) no IdP que irá limpar a sessão atual na aplicação.
Essa URI absoluta usando https
é indicada no registro do cliente (frontchannel_logout_uri
) e deve estar presente nos valores de redirect_uris
. O IdP carrega um iframe com essa URL no processo de logout, e a página deve limpar a sessão associada, como cookies e localStorage.
O IdP enviará (configurar frontchannel_logout_session_required
para verdadeiro) como query parameters o iss
(issuer) e sid
(identificador da sessão) que devem ser comparados com os dados no token, e deve prevenir o cache:
Cache-Control: no-cache, no-store
Pragma: no-cache
Exemplo:
https://rp.example.org/frontchannel_logout?iss=https://server.example.com&sid=08a5019c-17e1-4977-8f42-65a12843ea02
Diferentemente do front-channel esse processo tem uma comunicação direta entre o cliente (aplicação) e o servidor de autenticação ("IdP").
No OpenID Connect Discovery, é preciso configurar os valores dos metadados abaixo no servidor de autenticação.
Metadado | Descrição |
---|---|
backchannel_logout_supported | Valor booleano que indica o suporte da funcionalidade. |
backchannel_logout_session_supported | Valor booleano que indica se o servidor deve enviar um sid (ID da sessão) no token de Logout para identificar a sessão. |
No registro do cliente deve ser informado os metadados:
Metadado | Descrição |
---|---|
backchannel_logout_uri |
URL usando https da aplicação que irá limpar a sessão nela mesmo quando receber um token de Logout. |
backchannel_logout_session_required |
Booleando que indica se deve ser enviado o sid no token de Logout. |
O token de logout, que deve ser assinado e pode ser criptografado e possui as claims:
Claim | Descrição |
---|---|
iss |
URL do servidor de autenticação. |
sub |
A identificação do sujeito/usuário. |
aud |
O ID de cliente da aplicação. |
iat |
Timestamp da data de geração do token. |
jti |
Identificador único deste token. |
events |
Um objeto JSON que contém o atributo vazio http://schemas.openid.net/event/backchannel-logout indicando que é um token de logout. |
sid |
Identificador da sessão atual. |
Exemplo:
{
"iss": "https://server.example.com",
"sub": "248289761001",
"aud": "s6BhdRkqt3",
"iat": 1471566154,
"jti": "bWJq",
"sid": "08a5019c-17e1-4977-8f42-65a12843ea02",
"events": {
"http://schemas.openid.net/event/backchannel-logout": {}
}
}
É recomendado que o token de logout expire no máximo em 2 minutos.
Deve seguir os mesmos passos da validação do ID Token e ainda:
Claim ou item | Validação |
---|---|
sid |
Deve existir o identificador de sessão. |
events |
Deve ter um atributo http://schemas.openid.net/event/backchannel-logout . |
nonce |
Não deve existir. |
jti |
Verificar se outro token com o mesmo ID único já não foi usado. |
iss |
Comparar o emissor do token de Logout com o token de ID. |
Em caso de erro, rejeitar e retornar uma resposta HTTP 400 - Bad Request.
Uma requisição HTTP POST é enviada para o endereço cadastrado no registro do aplicativo com o body em application/x-www-form-urlencoded
:
POST /backchannel_logout HTTP/1.1
Host: rp.example.org
Content-Type: application/x-www-form-urlencoded
logout_token=eyJhbGci ... .eyJpc3Mi ... .T3BlbklE ...
Algumas dicas e procedimentos para prevenir falhas de segurança e ataques maliciosos. RFC6819
- Quando possível usar tokens criptografados.
- Ao criar um token novo, salvar em algum lugar o
jti
, como uma instância Redis, que deve expirar assim que o token expira. Em toda requisição, validar se aquelejti
ainda existe no Redis. Em caso de atividade suspeita, remover ojti
do Redis, o que irá invalidar todas as requisições, mesmo com um token válido. ("Blacklist") - O servidor deve usar SSL/TLS no mínimo na versão 1.2. Gerador de configuração da Mozilla
- Detectar vazamentos de senhas e credenciais dos usuários em ferramentas como o have i been pwned, inserindo todos os tokens na Blacklist, trocar a senha por outra aleatória, e enviar um e-mail de recuperação de senha.
- Também verificar vazamentos de contas do próprio serviço/produto.
- Rotacionar o código secreto do cliente de tempos em tempos (3 meses?).
Outras fontes de leitura (bibliografia):