Serviço de autenticação e autorização para a framework iDigital do IFAP.
O OpenID Connect (OIDC) é um layer de autenticação sobre a framework de autorização OAuth 2.0.
Estão disponíveis os seguintes endpoints:
<server_base_url>/idigital-oauth2/api/authorization<server_base_url>/idigital-oauth2/api/token<server_base_url>/idigital-oauth2/api/introspect<server_base_url>/idigital-oauth2/api/logout<server_base_url>/idigital-oauth2/api/userinfoO <server_base_url> irá depender do ambiente (desenvolvimento, testes ou produção).
O Authentication Request deverá ser feito usando o Authorization Code Flow como especificado na secção 4.1 do OAuth 2.0 e secção 3.1.2.1 do OpeniID Connect.
O Authorization Code Flow retorna um Authorization Code ao cliente, que depois pode ser trocado por um ID Token e Access Token diretamente.
O cliente deve construir o URI indicando os seguintes parâmetros:
response_type - OBRIGATÓRIO. O valor deverá ser “code”.
client_id - OBRIGATÓRIO. O identificador do cliente.
redirect_uri - OPCIONAL. Se indicado terá de ser igual ao URI que está registado para o cliente.
scope - OBRIGATÓRIO. Deve conter pelo menos o valor “openid”, tal como está definido no OpenID Connect. Para além deste valor pode conter:
Para além destes scopes podem também ser indicados códigos de perfis do iDigital (identificador numérico). Na resposta serão devolvidos no scope apenas os códigos destes perfis que estão atribuídos ao utilizador.
[v1.6.0] Também podem ser indicadas permissões a menus no formato 1111.ABCDEFG:permissão ou só ABCDEFG:permissão, onde 1111 é a campanha do menu (não obrigatório para menus do iDigital, se não for indicado é assumido 1111), ABCDEFG é o código do menu e permissão` pode ser os valores “insert”/“create”, “query”/“read”, “update” ou “delete”. Na resposta serão devolvidos apenas as permissões que o utilizador tenha. Por exemplo, se o utilizador só tem permissão de “query” para o menu ABC se indicar no authorization request um scope “openid ABC:insert ABC:query ABC:update ABC:delete” na resposta o scope só irá conter “openid ABC:query”.
state - RECOMENDADO. Valor usado para manter estado entre o request e o callback. O valor que for colocado aqui no request será devolvido no callback. Este parâmetro também pode ser usado para mitigar ataques de Cross-Site Request Forgery (CSRF, XSRF).Exemplo de um authorization request:
https://xxxx.ifap.pt/idigital-oauth2/api/authorization?
response_type=code
&client_id=123456789
&redirect_uri=https://dummy.site.pt/authorization-response
&scope=openid%20name%20email%20uti_nif%20uti_tipo
&state=qwertyuiop1234567890
Se o request estiver válido e o utilizador der a sua autorização, o authorization server gera um Authorization Code e redireciona o utilizador novamente para a aplicação, adicionando o “code” e o “state” ao redirect URI.
Exemplo de uma resposta:
HTTP/1.1 302 Found
Location: https://dummy.site.pt/authorization-response?
code=DUMMY-CODE
&state=qwertyuiop1234567890
Existem dois casos em que o servidor deve mostrar uma mensagem de erro diretamente em vez de redirecionar o utilizador para a aplicação: se o client_id for inválido ou se o redirect_uri for inválido. Nos restantes casos pode-se redirecionar o utilizador para o endereço de redirect da aplicação juntamente com parâmetros de query string descrevendo o erro.
Ao redirecionar para a aplicação o servidor adiciona os seguintes parâmetros ao redirect URL:
Pode conter um dos seguintes valores:
invalid_request - falta um parâmetro no request, o parâmetro é inválido, está repetido ou é de alguma forma inválido.
access_denied - O utilizador ou o servidor de autorização negou o pedido.
unauthorized_client - O cliente não está autorizado para pedir um código de autorização usando este método, por exemplo se um criente confidencial tenta usar o tipo “implicit grant”.
unsupported_response_type - O servidor não suporta a obtenção de um código de autorização usando este método, por exemplo se o servidor não tem implementado o tipo de autorização “implicit grant”.
invalid_scope - O scope pretendido é inválido ou não é conhecido.
server_error - Em vez de mostrar a página de erro “500 Internal Server Error” ao utilizador o servidor pode redirecionar com este código de erro.
temporarily_unavailable - O servidor não está disponível. Este código pode ser retornado em vez de responder com “503 Service Unavailable”.
O servidor pode opcionalmente incluir uma descrição do erro neste parâmetro. Este parâmetro é destinado ao developer para este perceber melhor a situação e não deve ser mostrado ao utilizador.
O servidor também pode retornar um URL para uma página com informação acerca do erro.
HTTP/1.1 302 Found
Location: https://dummy.site.pt/authorization-response?
error=access_denied
&error_description=The+user+denied+the+request
&error_uri=https%3A%2F%2Ftools.ietf.org%2Fhtml%2Frfc6749
&state=qwertyuiop1234567890
Para obter um Access Token, um ID Token e um Refresh Token o cliente deve enviar um Token Request para o Token Endpoint:
POST /idigital-oauth2/api/token HTTP/1.1
Host: xxxx.ifap.pt
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=DUMMY-CODE
&client_id=123456789
&client_secret=QWERTYUIOP
&redirect_uri=https%3A%2F%2Fdummy.site.pt%2Ftoken-response
Uma resposta com sucesso pode ser algo como:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"scope": "openid name email uti_nif uti_tipo",
"state": "qwertyuiop1234567890",
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJqdGkiOiI1ZDA4YzQyZS0wNTZjLTQwZTMtYWY0OS01ZTRlOTIwZWUyYzciLCJpYXQiOjE1MjYwNTA3MjgsImlzcyI6ImxvY2FsaG9zdCIsInRva2VuX3R5cGUiOiJpZF90b2tlbiIsInNjb3BlIjoib3BlbmlkIG5hbWUgZW1haWwgdXRpX25pZiB1dGlfdGlwbyIsImF1ZCI6IjQxMWU4YjlkLTVjMjAtNDc2Yy1iZGU1LWNlZWJlMDY0YmVmNSIsInN1YiI6ImR1bW15dXNlciIsIm5hbWUiOiJEdW1teSBVc2VyIE5hbWUiLCJlbWFpbCI6ImR1bW15LnVzZXJAaWZhcC5wdCIsInV0aV9uaWYiOiIxMjM0NTY3ODkiLCJ1dGlfdGlwbyI6IkkiLCJleHAiOjE1MjYwNTEwMjh9.g0Uvm-HxHug_zx-ZjiYmsApP5fw4N8WEEDMCLq7kRcG0jwbwtRHY15GAZDa1JIR5NskA4LxeIFo26jjVZ-zJVQ",
"access_token": "qwertyuiopasdfghjklzxcvbnm",
"token_type": "bearer",
"expires_in": 1526050785886,
"refresh_token": "mnbvcxzlkjhgfdsapoiuytrewq"
}
O id_token é um JSON Web Token (JWT) e destina-se a ser usado diretamente pelo cliente para obter informação acerca do utilizador.
O token está assinado com o algoritmo HS512 usando como chave o client_secret. O cliente deve verificar o token conforme especificado na secção 3.1.3.7. do OpenID Connect
Um exemplo de ID Token:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJqdGkiOiI1ZDA4YzQyZS0wNTZjLTQwZTMtYWY0OS01ZTRlOTIwZWUyYzciLCJpYXQiOjE1MjYwNTA3MjgsImlzcyI6ImxvY2FsaG9zdCIsInRva2VuX3R5cGUiOiJpZF90b2tlbiIsInNjb3BlIjoib3BlbmlkIG5hbWUgZW1haWwgdXRpX25pZiB1dGlfdGlwbyIsImF1ZCI6IjQxMWU4YjlkLTVjMjAtNDc2Yy1iZGU1LWNlZWJlMDY0YmVmNSIsInN1YiI6ImR1bW15dXNlciIsIm5hbWUiOiJEdW1teSBVc2VyIE5hbWUiLCJlbWFpbCI6ImR1bW15LnVzZXJAaWZhcC5wdCIsInV0aV9uaWYiOiIxMjM0NTY3ODkiLCJ1dGlfdGlwbyI6IkkiLCJleHAiOjE1MjYwNTEwMjh9.g0Uvm-HxHug_zx-ZjiYmsApP5fw4N8WEEDMCLq7kRcG0jwbwtRHY15GAZDa1JIR5NskA4LxeIFo26jjVZ-zJVQ
Depois de descodificado o token contém o header:
{
"typ": "JWT",
"alg": "HS512"
}
E o payload:
{
"jti": "5d08c42e-056c-40e3-af49-5e4e920ee2c7",
"iat": 1526050728,
"iss": "localhost",
"token_type": "id_token",
"scope": "openid name email uti_nif uti_tipo",
"aud": "411e8b9d-5c20-476c-bde5-ceebe064bef5",
"sub": "dummyuser",
"name": "Dummy User Name",
"email": "dummy.user@ifap.pt",
"uti_nif": "123456789",
"uti_tipo": "I",
"exp": 1526051028
}
Para verificar o conteúdo de um JWT pode ser usado o site https://jwt.io/ .
HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"error": "invalid_request",
"error_description": "Request was missing the 'redirect_uri' parameter.",
"error_uri": "https://tools.ietf.org/html/rfc6749"
}
Para atualizar um Access Token o cliente tem de fazer um request ao token endpoint com os seguintes parâmetros:
POST /idigital-oauth2/api/token HTTP/1.1
Host: xxxx.ifap.pt
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=mnbvcxzlkjhgfdsapoiuytrewq
Opcionalmente também pode ser incluído o parâmetro scope que, no caso de ser incluído, não pode conter nenhum scope que não tenha sido atribuído no token original. No caso de não ser indicado é tratado como sendo o mesmo do token original.
Quando um cliente OAuth 2.0 faz um request a um resource server, este precisa de verificar que o Access Token é válido. A especificação do OAuth 2.0 não define um método específico para verificar Access Tokens, apenas menciona que esta verificação deve ser coordenada entre as partes. Em alguns casos, nomeadamente em serviços locais, ambos os endpoints fazem parte do mesmo sistema e podem partilhar informação necessária para fazer estas validações através da base de dados, por exemplo. Em sistemas maiores em que os endpoints fazem parte de sistemas diferentes é necessário um método diferente para fazer esta verificação.
O RFC7662 - OAuth 2.0 Token Introspection define um protocolo que retorna informação acerca de um Access Token e destina-se a ser usado por resource servers.
O request é feito por POST contendo o parâmetro “token” e a autenticação do cliente. Os parâmetros são enviados no body por “application/x-www-form-urlencoded”, como definido em W3C.REC-html5-20141028.
POST /idigital-oauth2/api/introspect HTTP/1.1
Host: xxxx.ifap.pt
Content-Type: application/x-www-form-urlencoded
token=qwertyuiopasdfghjklzxcvbnm
&client_id=123456789
&client_secret=QWERTYUIOP
O Token Introspection Endpoint responde com um objeto JSON com várias propriedades.
No caso de o token estar válido e ativo a resposta será:
{
"active": true,
"scope": "openid profile",
"client_id": 123456789,
"sub": "dummyuser",
"sid": "999999999",
"username": "Dummy User Name",
"exp": 1582904531000,
"state": "qwertyiop1234567890"
}
Se o token não estiver ativo a resposta será:
{
"active": false,
"scope": mull,
"client_id": null,
"sub": null,
"username": null,
"exp": null,
"state": "qwertyiop1234567890"
}
O logout request termina a sessão e invalida todos os tokens que tenham sido gerados a todos os clientes. Normalmente os clientes não devem invocar este endpoint porque só precisam de fazer o logout da sua aplicação.
https://xxxx.ifap.pt/idigital-oauth2/api/logout?
id_token_hint=eyJ0eXAiOiJKV1Qi ... zJVQ
&post_logout_redirect_uri=https://dummy.site.pt/logout
&state=qwertyuiop1234567890
Nenhum parâmero é obrigatório. No entanto no caso de não ser fornecido um id_token_hint o post_logout_redirect_uri só pode ter endereços do domínio ou subdomínios ifap.pt (por exemplo https://exemplo.ifap.pt/qualquer-coisa).
Se o request estiver válido, o authorization server termina a sessão e redireciona o utilizador novamente para a aplicação, eliminando o session cookie e acrescentando o “state” ao redirect URI.
Exemplo de uma resposta:
HTTP/1.1 302 Found
Location: https://dummy.site.pt/logout?
state=qwertyuiop1234567890
O cliente envia o pedido usando HTTP GET ou HTTP POST. DEVE ser enviado um bearer token com o access_token obtido de uma solicitação de autenticação do OpenID Connect, conforme definido na Seção 2 do Uso de bearer tokens do OAuth 2.0 [RFC6750].
É RECOMENDADO que o pedido seja feito com o método HTTP GET e o access_token seja enviado no campo Authorization do header.
GET /idigital-oauth2/api/userinfo HTTP/1.1
Host: xxxx.ifap.pt
Authorization: Bearer qwertyuiopasdfghjklzxcvbnm
Se o pedido estiver correto a resposta é devolvida em JSON:
{
"sub": "dummyuser",
"name": "Dummy User Name",
"email": "dummyuser@ifap.pt",
"uti_tipo": "I",
"uti_nif": null,
"uor_cod_uni_org": "IFAP00"
}
Os atributos devolvidos vão depender do scope do token enviado.
—
Copyright (c) 2023, IFAP. Todos os direitos reservados.