I'm trying out IdentityServer4 demo project and I'm adding user claims to ProfileDataRequestContext.IssuedClaims in IProfileService implementation. One thing I've noticed is that there is a context.RequestedClaimTypes collection, which is always empty in any resource/identity/scope configuration variations I've tried. Under what condition does this collection has data?
If in the definition of your ApiResources you define UserClaims, these will then be populated in the context.RequestClaimTypes.
For example:
new ApiResource
{
Name = "TestAPI",
ApiSecrets = { new Secret("secret".Sha256()) },
UserClaims = {
JwtClaimTypes.Email,
JwtClaimTypes.EmailVerified,
JwtClaimTypes.PhoneNumber,
JwtClaimTypes.PhoneNumberVerified,
JwtClaimTypes.GivenName,
JwtClaimTypes.FamilyName,
JwtClaimTypes.PreferredUserName
},
Description = "Test API",
DisplayName = "Test API",
Enabled = true,
Scopes = { new Scope("testApiScore) }
}
Then your ProfileDataRequestContext.RequestClaimTypes will contain these request claims, for your Identity Server to fulfil how you see fit.
I've found out that it if you set client.GetClaimsFromUserInfoEndpoint = true and additional roundtrip is made to /connect/userinfo endpoint and the request has requested value "sub".
Answer: https://github.com/IdentityServer/IdentityServer4/issues/1067
Whenever you request a scope that has associated claims.
Related
I have a WPF app (.net 462 & API .net5.0).
I have set the openid on the api and this work, the permission work, but on the WPF app i have to check the permission for app element (menu access, button access).
Getting the token work, but i don't know how to valid a scope when openid
Key clock config :
Realm => demo-test
Client => demo-test-client
Client Role => DemoRole
Authorization Scope => demo:read
Associated Permissions => Permission demo:read
Associated Policy for role => DemoRole - Positive
I have create two user "user-test-ok" & "user-test-ko", "user-test-ok" have the client role "DemoRole".
I have test to user the introspection for validate the user have the scope "demo:read", but this not work.
I don't want use keycloak API, i want use the openid system for possibly change keycloak by other OAuth2.0 system.
This is my code to try to check the authorization scope :
var requestUri = new Uri($"https://localhost:8443/auth/realms/{realm}/protocol/openid-connect/token/introspect");
using (var client = new HttpClient())
{
var req = new HttpRequestMessage(HttpMethod.Post, requestUri);
req.Headers.Add("cache-control", "no-cache");
req.Headers.Add("accept", "application/x-www-form-urlencoded");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
req.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "token_type_hint", "requesting_party_token" },
{ "token", tokenResult.AccessToken },
{ "client_id", clientId },
{ "client_secret", clientSecret },
{ "scope", "test:read" },
});
var response = client.SendAsync(req).Result;
if (!response.IsSuccessStatusCode)
{
throw new Exception();
}
var responseString = response.Content.ReadAsStringAsync().Result;
}
Did you have any idea how to do it?
If I read the token introspection endpoint documentation here, then it says nothing about passing the scope ({ "scope", "test:read" },) as a parameter. Instead you take what you get back from that request and then you first check if the active claim is true. That signals that the token is still active and valid..
Then you just examine the data that is returned. Or you just use the scope value inside the access or ID-token to give the user access to the features in the application.
Do, check what the request returns in a tool like Fiddler.
Why does GetUserInfoAsync return only sub without other claims?
var discoveryResponse = client.GetDiscoveryDocumentAsync("some Authorization Url").Result;
var userInfoResponse = client.GetUserInfoAsync(new UserInfoRequest
{
Address = discoveryResponse.UserInfoEndpoint,
Token = token // access_token
}).Result;
After signed in I have in the response 'email' but when I call GetUserInfoAsync I don't have it. I pass to GetUserInfoAsync access_token maybe that? Because claims are in id_token but how I can return claims from GetUserInfoAsync in that case?
My code:
I have on the list of the IdentityResource 'email':
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Email()
};
}
In the client I have 'email' and 'AlwaysIncludeUserClaimsInIdToken':
return new Client
{
ClientId = clientId,
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "some url" },
PostLogoutRedirectUris = { "some url" },
AlwaysIncludeUserClaimsInIdToken = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
"email"
}
};
I pass scopes in the SignInAsync method:
await _accessor.HttpContext.SignInAsync(subject, new Claim(ClaimNames.Email, email));
In the requested scopes I have:
scope: 'openid email'
UserInfo endpoint requires authorization with access_token having at least openid scope, which is transformed into the sub claim in response. All the rest is optional. That is by the spec.
Now let's see how that's arranged in IdentityServer 4.
Everything related to access_token (intended to be used by APIs) is grouped into ApiResource configuration. That's the only place where you can configure the API scopes and their claims. After introducing a scope, you may add it to the list of accessible for a particular client. Then, client side you may request it explicitly. ApiResource configuration might look a bit messy as it has additional fields such as API credentials for Introspection endpoint access, but the constructor we need to fetch some UseInfo data is extremely simple:
new ApiResource("UserInfo", new []{JwtClaimTypes.Email, JwtClaimTypes.GivenName})
With the code above we created the ApiResource "UserInfo" with the scope "UserInfo" and a couple associated user claims.
All the same and more, from the first hand here
I've extracted a user's groups information from the OIDC endpoint of Keycloak, but they don't come with the group ATTRIBUTES I defined (see Attributes tab into the group form, near Settings). Is there a claim to add to my request?
I'm using a RESTeasy client to reach Keycloak's admin API (had much better results than using the provided admin client, yet):
#Path("/admin/realms/{realm}")
public interface KeycloakAdminService {
#GET
#Path("/users/{id}/groups")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
List<GroupRepresentation> getUserGroups(#PathParam("realm") String realm, #PathParam("id") String userId,
#HeaderParam(AUTHORIZATION) String accessToken);
//DEBUG the access token must always be prefixed by "Bearer "
}
So I can fetch a user's groups:
private void fetchUserGroups(UserInfoOIDC infos, String userId) {
log.info("Fetching user groups from {}...", getRealm());
try {
KeycloakAdminService proxy = kcTarget.proxy(KeycloakAdminService.class);
AccessTokenResponse response = authzClient.obtainAccessToken(getAdminUsername(), getAdminPassword());
List<GroupRepresentation> groups = proxy.getUserGroups(getRealm(), userId,
"Bearer " + response.getToken());
infos.importUserGroups(groups); //DEBUG here we go!
} catch (WebApplicationException e) {
log.error("User groups failure on {}: {}", getRealm(), e.getMessage());
}
}
But when it comes to data exploration, it turns out that no attributes are provided into the GroupRepresentation#getAttributes structure.
I've read that claims can be added to user info requests. Does it work on the admin API? How can I achieve that result with RESTeasy templates?
Thx
I was able to achieve this by adding groups/roles info in token other claims property:
For this in keycloak config, go to your client -> mappers & add a group/role mapper. E.g.
Now this info will start coming in your access token:
To access these group attribute in Java you can extract it from otherclaims property of accesstoken. E.g.:
KeycloakSecurityContext keycloakSecurityContext = (KeycloakSecurityContext)(request.getAttribute(KeycloakSecurityContext.class.getName()));
AccesToken token = keycloakSecurityContext.getToken();
In below image you can see that otherclaims property of token is filled with groups attribute that we created on keycloak. Note that if we had named "token claim property" as groupXYZ, the otherclaims would be showing:
groupsXYZ=[Administrator]
This is how I could eventually map group attributes (inherited as user attributes, as suspected before) into user informations, into the "other claims" section :
It is possible to inherit attributes from the group by switching on Aggregate attribute values option during the creation of a new User Attribute mapper.
First of all I think the answers above are correct. I was able to achieve what I wanted to do by following recommendations from them.
But I have also broke by production keycloak integration with auth2-proxy which lead to some outage for internal users :)
So, I took time to investigate a bit and came up with creating new client scope and adding custom client role / realm role / group mappers to it.
It works, and also you don't break your working keycloak integrations with other services ;)
Here is all my terraform code which you can you to reproduce what I did:
variable "realm_name" {
type = string
description = "Name of the realm to create"
default = "master"
}
variable "keycloack_user" {
type = string
description = "Keycloak admin user"
default = "admin"
}
variable "keycloack_password" {
type = string
description = "Keycloak admin password"
default = "admin"
}
variable "keycloak_url" {
type = string
description = "Keycloak url"
default = "http://localhost:8090"
}
variable "oauth_fqdn" {
type = string
description = "FQDN of the oauth server used for valid redirects"
default = "http://localhost:3000/*"
}
terraform {
required_version = ">= 1.0.0"
required_providers {
keycloak = {
source = "mrparkers/keycloak"
version = ">= 3.7.0"
}
}
}
provider "keycloak" {
client_id = "admin-cli"
username = var.keycloack_user
password = var.keycloack_password
url = var.keycloak_url
realm = var.realm_name
# base_path = "/auth"
}
data "keycloak_realm" "realm" {
realm = var.realm_name
}
resource "keycloak_openid_client" "client" {
realm_id = data.keycloak_realm.realm.id
client_id = "my-client"
name = "my-client"
enabled = true
access_type = "CONFIDENTIAL"
valid_redirect_uris = [
var.oauth_fqdn
]
login_theme = "keycloak"
standard_flow_enabled = true
}
output "keycloak_client_id" {
value = keycloak_openid_client.client.client_id
}
output "keycloak_client_secret" {
value = keycloak_openid_client.client.client_secret
sensitive = true
}
// creating custom scope
resource "keycloak_openid_client_scope" "this" {
realm_id = data.keycloak_realm.realm.id
name = "group_and_roles"
description = "When requested, this scope will map a user's group memberships and all roles to a claim"
include_in_token_scope = true
}
// creating custom group mapper
resource "keycloak_generic_protocol_mapper" "groups" {
realm_id = data.keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.this.id
name = "groups mapper"
protocol = "openid-connect"
protocol_mapper = "oidc-group-membership-mapper"
config = {
"full.path" : "true",
"id.token.claim" : "true",
"access.token.claim" : "true",
"claim.name" : "groups",
"userinfo.token.claim" : "true"
}
}
// creating custom role mapper for realm level roles
resource "keycloak_generic_protocol_mapper" "realm_roles" {
realm_id = data.keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.this.id
name = "realm roles mapper"
protocol = "openid-connect"
protocol_mapper = "oidc-usermodel-realm-role-mapper"
config = {
"multivalued" : "true",
"userinfo.token.claim" : "true",
"id.token.claim" : "true",
"access.token.claim" : "true",
"claim.name" : "realm_roles",
"jsonType.label" : "String"
}
}
// creating custom role mapper for client level roles
resource "keycloak_generic_protocol_mapper" "client_roles" {
realm_id = data.keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.this.id
name = "client roles mapper"
protocol = "openid-connect"
protocol_mapper = "oidc-usermodel-client-role-mapper"
config = {
"multivalued" : "true",
"userinfo.token.claim" : "true",
"id.token.claim" : "true",
"access.token.claim" : "true",
"claim.name" : "client_roles",
"jsonType.label" : "String"
}
}
// adding custom scope to client as optional
resource "keycloak_openid_client_optional_scopes" "client_optional_scopes" {
realm_id = data.keycloak_realm.realm.id
client_id = keycloak_openid_client.client.id
optional_scopes = [
"address",
"phone",
"offline_access",
"microprofile-jwt",
keycloak_openid_client_scope.this.name
]
}
And later I setup my application level auth config as follows:
AUTH_ISSUER_URL=http://localhost:8090/realms/master
AUTH_CLIENT_ID=my-client
AUTH_CLIENT_SECRET=client-secret-get-it-from-output
AUTH_SCOPES="profile,email,openid,offline_access,group_and_roles"
I've written a small IdentityServer demo server, following the examples in the documentation. I have the following TestUser:
new TestUser
{
SubjectId = "1",
Username = "Username",
Password = "password",
Claims = new List<Claim>()
{
new Claim(System.Security.Claims.ClaimTypes.Name, "Username"),
new Claim(System.Security.Claims.ClaimTypes.Email, "username#domain.com")
}
}
I get an access token using ResourceOwnerPassword flow. And I am authorized to access my API.
The problem is that when in my protected API I'm trying to get the user identity, the name property is returned as null, and I don't see the email claim. No matter what I do I always see the same 12 claims. The sub claim is the only one passed with the information I put in the Client object.
How can I populate the HttpContext.User.Identity.Name property and send additional claims/data about the user?
The reason probably is that you are not requesting the proper resources/scopes for your client.
You need to define an API resource with the claims you need in the access token.
e.g in Resources.cs you can add the claims to be included in all api2 scopes
new ApiResource
{
Name = "api2",
ApiSecrets =
{
new Secret("secret".Sha256())
},
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email
},
Scopes =
{
new Scope()
{
Name = "api2.full_access",
DisplayName = "Full access to API 2",
},
new Scope
{
Name = "api2.read_only",
DisplayName = "Read only access to API 2"
}
}
}
Then you allow your resource owner client the access to those API resources.
e.g in client.cs
new Client
{
ClientId = "roclient",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowOfflineAccess = true,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
"custom.profile",
"api1", "api2.read_only"
}
},
You can then request the scope in your roclient
client.RequestResourceOwnerPasswordAsync("bob", "bob", "api2.read_only", optional).Result
Post the access token to the API and you will get the claims you added to your API resource.
In the call to UseOpenIdConnectAuthentication, or wherever you're trying to use the token, make sure you set the TokenValidationParameters for the Name property to ClaimTypes.Name.
By default, the Name claim type is set to name (JwtClaimType.Name).
I'm new to Identity Server and am confused on the topic of Identity & Access tokens. I understand access tokens are meant to secure resources (i.e. web api) and that identity tokens are used to authenticate. However, whenever I call /connect/token I always receive an "access_token". Within the request I've asked for a client which has various scopes and claims.
new Client
{
ClientId = "Tetris",
ClientName = "Tetris Web Api",
AccessTokenLifetime = 60*60*24,
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
RequireClientSecret = false,
AllowedScopes = {"openid", "TetrisApi", "TetrisIdentity"}
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new[]
{
new ApiResource("TetrisApi", "Tetris Web API", new[] { JwtClaimTypes.Name, JwtClaimTypes.Role, "module" })
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "TetrisIdentity",
UserClaims =
new[]
{
JwtClaimTypes.Name,
JwtClaimTypes.Role,
JwtClaimTypes.GivenName,
JwtClaimTypes.FamilyName,
JwtClaimTypes.Email,
"module",
"module.permissions"
}
}
};
}
Below is a copy of postman:
Any thoughts? I didn't see an example in the Quickstarts that employs Identity Tokens.
Thanks!
The password grant type does not support identity tokens. See RFC6749.
The best you can do here is to use the access token to get claims for the user using the userinfo endpoint.
The recommendation is to use an interactive flow like implicit or hybrid for end-user authentication.
#leastprivilege 's answer is correct but instead of calling the userinfo endpoint, you also have the option of including the UserClaims you desire in your ApiResource definition.
At the moment you request new[] { JwtClaimTypes.Name, JwtClaimTypes.Role, "module" }, but if you changed that to include all the claims you (currently) define as part of the IdentityResources then those claims will also be available in the access_token.