Getting first name, last name and email from Keycloak using Quarkus OIDC integration - keycloak

When I access the /openid-connect/token endpoint using admin_cli client_id I can see more/different info in the payload of returned access_token compared to when token is injected into my bean by the Quarkus OIDC/Keycloak extension.
Here are some samples:
{
"exp": 1671084133,
"iat": 1671083533,
"jti": "b95bac0a-f95e-413d-b2cd-3b97fcf5f3c8",
"iss": "http://localhost:51521/realms/my-realm",
"sub": "cda64011-47a8-4a6a-8aac-06c7db6fc593",
"typ": "Bearer",
"azp": "admin-cli",
"session_state": "be12a28b-4143-4bbf-9914-c8454d93f50f",
"acr": "1",
"scope": "profile email",
"sid": "be12a28b-4143-4bbf-9914-c8454d93f50f",
"email_verified": false,
"preferred_username": "test",
"given_name": "vasia",
"family_name": "pupkin",
"email": "test#test.com"
}
Versus
{
"exp": 1671086253,
"iat": 1671085653,
"auth_time": 1671085653,
"jti": "05e9f66d-54a1-440b-9295-3ebd681a853a",
"iss": "http://localhost:51521/realms/my-realm",
"sub": "cda64011-47a8-4a6a-8aac-06c7db6fc593",
"typ": "Bearer",
"azp": "my-app",
"session_state": "ba00821c-1556-4214-90d7-5f2a55b0074a",
"scope": "openid microprofile-jwt",
"sid": "ba00821c-1556-4214-90d7-5f2a55b0074a",
"upn": "test",
"groups": [
"offline_access",
"admin",
"uma_authorization",
"default-roles-collar-club"
]
}
I'm interested in still getting the groups (for role based access) and given_name, family_name, email properties as well in my Quarkus app.
Tried to specify: quarkus.oidc.authentication.scopes=openid,profile,groups - getting error in the url redirected from Keycloak:
2022-12-14 23:11:19,984 DEBUG [io.qua.oid.run.CodeAuthenticationMechanism] (vert.x-eventloop-thread-2) Authentication has failed, error: invalid_scope, description: Invalid scopes: openid openid profile groups

openid is always added by Quarkus, Keycloak does not like duplicate scope values, so remove openid

Related

Keycloak JWT to contain user real and client role

I'm using Keycloak version 20.0.1 (also tried on 19.0.0).
I have a realm configured and under the Client Scopes -> realm roles -> Mappers I have added the realm_access.roles mapping.
For some reasons that I don't understand, the JWT token that I get as a response doesn't contain the Realm Roles (I have also assigned a realm role to the user that it's used for testing).
The response that I get is:
{
"exp": 1669579902,
"iat": 1669579602,
"auth_time": 1669577841,
"jti": "9dfe2638-a9f8-4094-8691-7a1423b629f7",
"iss": "https://auth.xxxx.com/realms/xxxx.com",
"sub": "6fe1b9b9-5ddd-478d-9b38-bd11698295cf",
"typ": "Bearer",
"azp": "spring-client",
"nonce": "d14b168b-3c77-489b-85dd-192dba533624",
"session_state": "55d938d4-42e7-4c2b-9038-f808c917c366",
"acr": "0",
"allowed-origins": [
"*"
],
"scope": "openid email profile roles",
"sid": "55d938d4-42e7-4c2b-9038-f808c917c366",
"email_verified": true,
"name": "first last",
"preferred_username": "user#email.com",
"given_name": "first",
"family_name": "last",
"email": "user#email.com"
}
How should I add the roles into the JWT response returned by Keycloak?
I have tried to configure the Client Scopes -> ream roles -> Mappers and I was expecting to receive in the JWT response the roles field.
In the JWT of Keycloak, two roles information.
It is not represented user's assigned role. Just assigned client role are included but realm's roles is possible list of realm.
If you want to get all of assigned role, have to call role mapping of user API (see #3.1)
I decoded JWT by jwo.io after get access token by Postman with Keycloak v 19.0.2
#1 realm roles list - It is not assigned realm role list, it is possible role list
User JWT(access token), get grant_type = password
{
"exp": 1669599866,
"iat": 1669596266,
"jti": "ad4e3b51-b23e-4abb-aba6-0099bb5213cf",
"iss": "http://localhost:8080/auth/realms/example",
"aud": "account",
"sub": "fae8bf9b-2209-4f01-ab32-629e029941ba",
"typ": "Bearer",
"azp": "spring-client",
"session_state": "8debdcfa-4252-4a27-8190-2a4981e6a795",
"acr": "1",
"realm_access": {
"roles": [
"offline_access",
"admin",
"default-roles-example",
"uma_authorization",
"user"
]
},
"resource_access": {
"spring-client": {
"roles": [
"client role2"
]
}
},
"scope": "openid profile email",
"sid": "8debdcfa-4252-4a27-8190-2a4981e6a795",
"email_verified": false,
"name": "first last",
"preferred_username": "user",
"given_name": "first",
"family_name": "last",
"email": "user#test.com"
}
five realm's roles are possible realm's role not assigned user's roles
User just assigned three realm's roles
Client JWT(access token), get grant_type = client_credentials
{
"exp": 1669597154,
"iat": 1669593554,
"jti": "ff6ae9db-7e05-4f9a-a538-0755a7f55125",
"iss": "http://localhost:8080/auth/realms/example",
"aud": "account",
"sub": "9db11aa2-6862-4ebb-9ee6-b03b51d7814d",
"typ": "Bearer",
"azp": "spring-client",
"acr": "1",
"realm_access": {
"roles": [
"offline_access",
"default-roles-example",
"uma_authorization"
]
},
"scope": "openid profile email",
"clientId": "spring-client",
"clientHost": "172.19.0.1",
"email_verified": false,
"preferred_username": "service-account-spring-client",
"clientAddress": "172.19.0.1"
}
Those are client's roles, it has three roles but not matched JWT's realm list
In the JWT (client access token), that list is possible realm list(not assigned client realm roles)
#2 client role - It is assigned client list
GET {KEYCLOAK-IP}/auth/admin/realms/{REALM-NAME}/clients/{client-UUID}/roles
http://localhost:8080/auth/admin/realms/example/clients/1cb76d56-b96f-42a7-91c0-c201a7761e9e/roles
[
{
"id": "e5171eb5-976e-429f-914c-0d63d7b394fd",
"name": "client role2",
"composite": false,
"clientRole": true,
"containerId": "1cb76d56-b96f-42a7-91c0-c201a7761e9e"
},
{
"id": "293c9c9c-bb76-4192-be09-ede769458394",
"name": "uma_protection",
"composite": false,
"clientRole": true,
"containerId": "1cb76d56-b96f-42a7-91c0-c201a7761e9e"
},
{
"id": "e1441ceb-7ea8-436b-9a55-30999c6de744",
"name": "client role1",
"description": "",
"composite": false,
"clientRole": true,
"containerId": "1cb76d56-b96f-42a7-91c0-c201a7761e9e"
}
]
#3 user's role list can get the separate API
3.1 all of user's role
GET {KEYCLOAK-IP}/auth/admin/realms/{REALM-NAME}/users/{USER-UUID}/role-mappings
UI:
It seems to block, UI not allow to assign directly a user into client role from UI. I use REST API call. Here
Example, get user's roles:
http://localhost:8080/auth/admin/realms/example/users/fae8bf9b-2209-4f01-ab32-629e029941ba/role-mappings
Response
{
"realmMappings": [
{
"id": "c31bd5ce-e400-4546-b633-d4d5bde596d8",
"name": "admin",
"description": "Administrator privileges",
"composite": false,
"clientRole": false,
"containerId": "e78f0c77-b44b-48da-850b-9d157e24a439"
},
{
"id": "d99f61be-bacd-438d-974d-06a006704a1e",
"name": "default-roles-example",
"description": "${role_default-roles}",
"composite": true,
"clientRole": false,
"containerId": "e78f0c77-b44b-48da-850b-9d157e24a439"
},
{
"id": "8d250d6c-e249-4b63-b86f-390b4550b12e",
"name": "user",
"description": "User privileges",
"composite": false,
"clientRole": false,
"containerId": "e78f0c77-b44b-48da-850b-9d157e24a439"
}
],
"clientMappings": {
"spring-client": {
"id": "1cb76d56-b96f-42a7-91c0-c201a7761e9e",
"client": "spring-client",
"mappings": [
{
"id": "e5171eb5-976e-429f-914c-0d63d7b394fd",
"name": "client role2",
"composite": false,
"clientRole": true,
"containerId": "1cb76d56-b96f-42a7-91c0-c201a7761e9e"
}
]
}
}
}
If you want to see, how to set the role into client or user,here
to get roles or anything in token we had protocol mappers in version 18.0 and earlier. for version 19 and above it is removed but only from ui
you can add protocol mapper by rest api
/POST {keycloak_url}/admin/realms/demo/clients/<clientId>/protocol-mappers/models
authorization:Bearer token //should be admin token
{
"protocol":"openid-connect",
"config{
"multivalued":"true",
"id.token.claim":"true",
"access.token.claim":"true",
"userinfo.token.claim":"true",
"usermodel.realmRoleMapping.rolePrefix":"",
"claim.name":"realmRoles"
},
"name":"roleNameMapper",
"protocolMapper":"oidc-usermodel-realm-role-mapper"
}

Open Liberty Microprofile-JWT always returns 401

I'm currently working on implementing JWT authentication on our backend via microprofile jwt (v1.2) on an Open Liberty server (v22.0.7.). The JWT is issued by a keycloak server.
It seems, I am somewhere mistaken though, because all my API calls return '401 Unauthorized' as soon as I add the RolesAllowed Annotation. And according to traces the principal is null.
My Microprofile properties (sensitive information in [ ]):
mp.jwt.verify.publickey.location=[host]/realms/[realm]/protocol/openid-connect/certs
mp.jwt.verify.issuer=[host]/realms/[realm]
mp.jwt.verify.id=myJWT
mp.jwt.verify.userNameAttribute=upn
mp.jwt.verify.audiences=backend
I also tried:
META-INF/micro-profile.properties:
mp.jwt.verify.publickey.location=[host]/realms/[realm]/protocol/openid-connect/certs
mp.jwt.verify.issuer=[host]/realms/[realm]
server.xml:
<mpJwt
id="myJWT"
userNameAttribute="upn"
audiences="backend"
/>
My Resource class:
#Path("/[path]")
#RolesAllowed({ "Authenticated" })
public class MyResource implements MyApi {
[...]
}
My Application class:
#LoginConfig(authMethod = "MP-JWT")
public class MyApplication extends Application {
}
My token payload:
"exp": 1662994046,
"iat": 1662986846,
"jti": "a0a92213-ae77-43fd-b0b8-8cb2f15524b2",
"iss": "[host]/realms/[realm]",
"aud": [
"backend"
],
"sub": "6194ef8a-dbe2-46fb-aa88-64dd61b4c8bc",
"typ": "Bearer",
"azp": "frontend",
"session_state": "6c60affa-b613-49cd-8e8b-a06b16eb8e61",
"acr": "1",
"scope": "microprofile-jwt",
"sid": "6c60affa-b613-49cd-8e8b-a06b16eb8e61",
"upn": "name",
"groups": [
"Authenticated"
]
}
I also tried the suggestion in Microprofile JWT responding with 401 all the time to no avail.
My API calls are made like normal, with the added Header:
"Authorization: Bearer [token]"
Finally you can find tracing in this pastebin

Why can JWT iat get smaller than nbf in very rare cases?

In rare cases in iOS and Android, the JWT attribute iat contains a smaller value than in nbf.
For this reason, the user gets the error message "ID Token expired". This also makes sense, but the question is why the time in iat can become smaller than nbf?
Is there a way to prevent iat from becoming smaller than nbf in identityserver4?
This can never be reproduced with via webbrowser (NextAuth.js). For the authentication in iOS and Android we use appauth-iOS and appauth-android.
In case of the mentioned error we get tokens like this:
{
"nbf": 1621951659,
"exp": 1621951959,
"iss": "issuer",
"aud": "apps",
"nonce": "V82ztq0cxNqslpVNsm_YYJZKJVUVhSpZlniB9hrXZ_o",
"iat": 1621951658,
"at_hash": "poDvhvDZ8EF__10uJiB2tQ",
"s_hash": "1gg8istJoI9ATRN3H814rw",
"sid": "F3559C9E64428AC1EA384588519CFBFB",
"sub": "test#email.com",
"auth_time": 1621951656,
"idp": "local",
"family_name": "Muster",
"given_name": "Peter",
"ClientId": "testapp",
"amr": [
"pwd"
]
}

Keycloak: Access token client-1 to manage client-2 resources

I have two clients in Keycloak:
CP: Client public
CC: Client confidential with Service Accounts enabled and several resources.
Resources owners are users who created them and they manage the access too.
The User (U) who created the Resource (R) can log in to CC and use the access token for call endpoints on CP.
Now I would like U will be able to set UMA policies, but the access token is from CP, not from CC where the resources are, so Keycloak is complaining about de token.
org.keycloak.authorization.client.util.HttpResponseException: Unexpected response from server: 403 / Forbidden / Response from server: {\"error\":\"invalid_clientId\",\"error_description\":\"Client application [CP] is not registered as a resource server.\"}
fun onlyOwner(accessToken: String, id: String, resourceId: String) {
val request = UmaPermissionRepresentation()
request.name = "Only owner can view $id"
request.description = "Only owner can view this resource"
request.scopes = setOf(ResourceScope.VIEW)
request.condition = ONLY_OWNER_CONDITION
authzClient.protection(accessToken).policy(resourceId).create(request)
}
Keycloak docs mention the following:
"
The Policy API is available at:
http://${host}:${port}/auth/realms/${realm_name}/authz/protection/uma-policy/{resource_id} This API is protected by a bearer token that must represent a consent granted by the user to the resource server to manage permissions on his behalf. The bearer token can be a regular access token obtained from the token endpoint using:
Resource Owner Password Credentials Grant Type
Token Exchange, in order to exchange an access token granted to some client (public client) for a token where audience is the resource server
I exchanged the CP client to CC client:
Original token:
{
"exp": 1606687405,
"iat": 1606651407,
"auth_time": 1606651405,
"jti": "1e4075a9-ce49-4462-91f7-33b8963f56dd",
"iss": "http://localhost/auth/realms/test",
"aud": "account",
"sub": "8381b629-5f10-401c-ae90-bb37769e5f70",
"typ": "Bearer",
"azp": "CP",
"session_state": "6c2d73e7-a4bd-44da-b242-cdf26ec812bc",
"acr": "1",
"allowed-origins": [
"*"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid email profile",
"email_verified": true,
"name": "Test First",
"preferred_username": "test",
"given_name": "Test",
"family_name": "First",
"email": "test#invent.com"
}
Exchanged token:
{
"exp": 1606687405,
"iat": 1606652039,
"auth_time": 1606651405,
"jti": "0c84f42a-973e-4bc7-9a6d-2c4fec548512",
"iss": "http://localhost/auth/realms/test",
"aud": [
"account",
"CC"
],
"sub": "8381b629-5f10-401c-ae90-bb37769e5f70",
"typ": "Bearer",
"azp": "CP",
"session_state": "6c2d73e7-a4bd-44da-b242-cdf26ec812bc",
"acr": "1",
"allowed-origins": [
"http://localhost"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "email profile",
"email_verified": true,
"name": "Test First",
"preferred_username": "test",
"given_name": "Test",
"family_name": "First",
"email": "test#invent.com"
}
But the error persists alghout aud changed from account to [account,cc].
The solution is exchanging the token using as client_id and client_secret the target client (cc). After that, you can use the access_token returned by Keycloak as bearer token for creating the UMA_Policy.
Exchange public client token to the confidential client token
Create a policy with the new access token

JWT CSOM/REST Azure Active Directory

We have been trying to make requests to sharepoint using CSOM/REST Authentication Bearer header requests with a token. It is related to this question below:
C# CSOM Sharepoint Bearer request from azure active directory
There is only one link/example that works all others including the android ADAL approach don't work.
https://samlman.wordpress.com/2015/02/27/using-adal-access-tokens-with-o365-rest-apis-and-csom/
They don't seem to return as long a token, when we look at the token in JWT parser, we can see that the scp value is different, the one that fails has user_impersonate, but the working one has AllSites.Manage AllSites.Read AllSites.Write MyFiles.Read MyFiles.Write. The aud url is also different, are one or both of these the problem and how do I get it working?
This is the ones that fails:
{
"aud": "https://srmukdev.onmicrosoft.com/3Squared-Api-Test",
"iss": "...",
"iat": ...,
"nbf": ...,
"exp": ..,
"acr": "...",
"aio": "...",
"amr": [
"pwd",
"mfa"
],
"appid": "...",
"appidacr": "0",
"e_exp": ...,
"family_name": "...",
"given_name": "...",
"ipaddr": "...",
"name": "...",
"oid": "...",
"onprem_sid": "...",
"platf": "3",
"scp": "user_impersonation",
"sub": "...",
"tid": "...",
"unique_name": "...",
"upn": "...",
"ver": "1.0"
}
This is the ones that works:
{
"aud": "https://srmukdev.sharepoint.com/",
"iss": "...",
"iat": ...,
"nbf": ...,
"exp": ...,
"acr": "...",
"aio": "...",
"amr": [
"pwd",
"mfa"
],
"app_displayname": "...",
"appid": "...",
"appidacr": "0",
"e_exp": ...,
"family_name": "...",
"given_name": "...",
"ipaddr": "...",
"name": "...",
"oid": "...",
"onprem_sid": "...",
"platf": "3",
"puid": "...",
"scp": "AllSites.Manage AllSites.Read AllSites.Write MyFiles.Read MyFiles.Write",
"sub": "...",
"tid": "...",
"unique_name": "...",
"upn": "...",
"ver": "1.0"
}
The access token is for the specific resource by checking its aud claim. The first token is used for authentication for your custom resource.
To get the token for the specific resource, we can use the parameter resource to specify which resource we want to request for the token. For example, if I want to get the token for the Microsoft Graph resource, we can construct the request like below:
POST /{tenant}/oauth2/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=2d4d11a2-f814-46a7-890a-274a72a7309e
&code=AwABAAAAvPM1KaPlrEqdFSBzjqfTGBCmLdgfSTLEMPGYuNHSUYBrqqf_ZT_p5uEAEJJ_nZ3UmphWygRNy2C3jJ239gV_DBnZ2syeg95Ki-374WHUP-i3yIhv5i-7KU2CEoPXwURQp6IVYMw-DjAOzn7C3JCu5wpngXmbZKtJdWmiBzHpcO2aICJPu1KvJrDLDP20chJBXzVYJtkfjviLNNW7l7Y3ydcHDsBRKZc3GuMQanmcghXPyoDg41g8XbwPudVh7uCmUponBQpIhbuffFP_tbV8SNzsPoFz9CLpBCZagJVXeqWoYMPe2dSsPiLO9Alf_YIe5zpi-zY4C3aLw5g9at35eZTfNd0gBRpR5ojkMIcZZ6IgAA
&redirect_uri=https%3A%2F%2Flocalhost%2Fmyapp%2F
&resource=https%3A%2F%2Fservice.contoso.com%2F
&client_secret=p#ssw0rd
If you want to acquire the access token for https://srmukdev.sharepoint.com/, you need to assign the value of resource parameter with https://srmukdev.sharepoint.com/ in the request according to the flow you were using.
More detail about the flows Azure AD support to acquire access token, you can refer the link below:
Azure Active Directory Authentication Protocols