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).
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.
I'm using resource owner password grand type. I implemented 2 interfaces: IResourceOwnerPasswordValidator and IProfileService. When I request a token, I get to the ResourceOwnerPasswordValidator, after successful validation I expect ProfileService provides me with requested climes. But it never does.
The thing is the RequestedClaimTypes from ProfileDataRequestContext in the ProfileService is always empty, as a result I can't filter out claims which weren't asked.
I expect it contains all claims, which are related to the requested scopes.
For example if I request such scopes as email or "profile" then I expect claims like "email", "first_name", "preferred_username" and others to be in the RequestedClaimTypes list.
There is simple clients and identity resource config:
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
};
...........................................................................
new Client
{
ClientId = "client",
ClientName = "SomeClient",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowOfflineAccess = true,
UpdateAccessTokenClaimsOnRefresh = true,
RefreshTokenUsage = TokenUsage.ReUse,
SlidingRefreshTokenLifetime = 1200,
RefreshTokenExpiration = TokenExpiration.Sliding,
AccessTokenLifetime = 900,
IdentityTokenLifetime = 120,
AllowedGrantTypes = { GrantType.ResourceOwnerPassword },
AccessTokenType = AccessTokenType.Jwt,
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
}
},
Interesting fact is that RequextedResource from ProfileDataRequestContext has claims data, which I can use to figure out what claims user has requested. For example:
var claimsNames = new List<string>();
claimsNames.AddRange(context.RequestedResources.IdentityResources.SelectMany(r => r.UserClaims));
claimsNames.AddRange(context.RequestedResources.ApiResources.SelectMany(r => r.UserClaims));
context.RequestedClaimTypes = claimsNames;
But it seems to be hacky way. Does someone know why RequestedClaimTypes isn't populated automatically?
P.S Client is the postman with the following parameters
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
How do you request additional claims for the access token jwt in identity server 4 / auth code flow? My custom profile service always shows RequestedClaimTypes of 0 during my auth code flow signin so the resulting access token jwt has my subject claim but no firstname, lastname, or email claim.
Here are my requested scopes from the client:
"TestApi openid profile email"
Here is my client definition on identity server:
new Client {
ClientId = "authorizationCodeClient2",
ClientName = "Authorization Code Test",
ClientSecrets = {
new Secret("secret".Sha256())
},
Enabled = true,
AllowedGrantTypes = GrantTypes.Code,
RequireConsent = true,
AllowRememberConsent = false,
RedirectUris =
new List<string> {
"http://localhost:5436/account/oAuth2"
},
AllowedScopes = { "TestApi", "openid", "profile", "email" },
AccessTokenType = AccessTokenType.Jwt
}
Using https://github.com/bayardw/IdentityServer4.Authorization.Code for the test client.
I discovered that identity server will let you optionally stamp the id token with the user profile claims (instead of having to call the userinfo endpoint). You basically set a Boolean property for that particular client:
AlwaysIncludeUserClaimsInIdToken = true;
Note, you will want to request the following scopes on your auth request : (openid profile email)
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.