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.
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
From the IdentityServer 4 documentation :
If the scopes requested are an identity resources, then the claims in the RequestedClaimTypes will be populated based on the user claim types defined in the IdentityResource
This is my identity resource:
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Phone(),
new IdentityResources.Email(),
new IdentityResource(ScopeConstants.Roles, new List<string> { JwtClaimTypes.Role })
};
and this is my client
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Email,
ScopeConstants.Roles
},
ProfileService - GetProfileDataAsync method:
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = principal.Claims.ToList();
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
if (user.Configuration != null)
claims.Add(new Claim(PropertyConstants.Configuration, user.Configuration));
context.IssuedClaims = claims;
}
principal.claims.ToList() has all the claims listed but context.RequestedClaimTypes is empty, hence the filter by context.RequestedClaimTypes.Contains(claim.Type)) returns no claims.
Client configuration:
let header = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' });
let params = new HttpParams()
.append('username', userName)
.append('password', password)
.append('grant_type', 'password')
.append('scope', 'email offline_access openid phone profile roles api_resource')
.append('resource', window.location.origin)
.append('client_id', 'test_spa');
let requestBody = params.toString();
return this.http.post<T>(this.loginUrl, requestBody, { headers: header });
Response type :
export interface LoginResponse {
access_token: string;
token_type: string;
refresh_token: string;
expires_in: number;
}
Someone indicated adding AlwaysIncludeUserClaimsInIdToken = true resolves the issue - I tried and it did not.
What am i missing here? Please help.
You're using resource owner password flow. This is an OAuth2 flow.
OAuth2 is not really suited for "identity data". Therefore a typical setup is to acquire an access token first (like you do), but then you'd use this token to call /userinfo endpoint, which would send back to you that user identity data.
Because of that, on the first request (getting an access token) in your ProfileService, RequestedClaimTypes will not have any claims related to identity resources (e.g. profile, email).
However, on the second call (/userinfo endpoint), your ProfileService would be called again (Caller=UserInfoEndpoint). and RequestedClaimTypes should now contain the identity claims you miss.
If you want to get identity data in a single call, then you should use an OpenID flow (e.g Implicit with response_type=id_token). You would then get an id token with this data right away (given AlwaysIncludeUserClaimsInIdToken was set to true). When your ProfileService is called (Client=ClaimsProviderIdentityToken), RequestedClaimTypes will contain identity claims.
Reference: https://github.com/IdentityServer/IdentityServer4/blob/main/src/IdentityServer4/src/Services/Default/DefaultClaimsService.cs#L62
I want to protect all of my APIs using only one identityserver4 applcation.
My sirst resource api and client applications:
CustomerManagementApi
CustomerManagement.JavascriptApplication
CustomerManagement.iOSApp
CustomerManagement.AndroidApp
My Second resource api and applications:
HumanResourceApi
HumanResource.MVCApplication
My Other resource api and applicaitons:
DashboardApi
Dashboard.AngularApplication
I want to create only one IdentityServer4 and secure my reousrces (DashboardApi,HumanResourceApi,CustomerManagementApi) and I want save my client applications on same IdentityServer4 applicaitons.
Is this possible? Should I create different ApiResources and Scopes on identityserver? How can I do this?
Yes, it is possible because IdentityServer4 enables you to define Resource Apis, Client applications, Users, Scopes and you can configure these data using in memory data for initial tests or even other storage mechanism like Entity Framework for example.
It is not simple to explain here, but in the official documentation there are some quickstarts that you can do to learn more.
You can see above some examples of configurations for Resource Apis, Client applications, Users in memory (using a Config.cs class) just to give you an idea about how it can be simple to start:
Resource Apis: the protected apis that Clients wants to access
public static IEnumerable<ApiResource> GetApis()
{
return new List<ApiResource>
{
new ApiResource("CustomerManagementApi", "My CustomerManagementApi"),
new ApiResource("DashboardApi", "My DashboardApi"),
// others ...
};
}
Clients: applications that wants to access the Resource Apis
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "CustomerManagementApi" }
}
};
}
Users: end users that wants to access some resource
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password"
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "password"
}
};
}
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).