Reputation: 1049
My setup,
dotnet new mvc -au Individual
and applying the http://docs.identityserver.io/en/release/quickstarts/0_overview.html tutorial, running in localhost 5000.dotnet new webapi
, running in localhost 5001.The IdentityServer resources and clients configuration is the following, notice that I'm using reference tokens:
public static IEnumerable<IdentityResource> GetIdentityResources() {
return new List<IdentityResource>{ new IdentityResources.OpenId() };
}
public static IEnumerable<ApiResource> GetApiResources() {
return new List<ApiResource>{
new ApiResource("api_resource", "API Resource") {
Description= "API Resource Access",
ApiSecrets= new List<Secret> { new Secret("apiSecret".Sha256()) },
}
};
}
public static IEnumerable<Client> GetClients() {
return new List<Client>{
new Client {
ClientId= "angular-client",
ClientSecrets= { new Secret("secret".Sha256()) },
AllowedGrantTypes= GrantTypes.ResourceOwnerPassword,
AllowOfflineAccess= true,
AccessTokenType = AccessTokenType.Reference,
AlwaysIncludeUserClaimsInIdToken= true,
AllowedScopes= { "api_resource" }
}
}
The password and user is send with postman and the token received is send to the WEB API also with postman, something like call localhost:5001/v1/test
with the token pasted in option bearer token
.
In the API Startup, in ConfigureServices I'm adding the lines below
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority= "http://localhost:5000";
options.ApiName= "api_resource";
options.ApiSecret = "apiSecret";
});
And I'm getting the Id of the user inside the controller as follows:
public async Task<IActionResult> Get(int id) {
var discoveryClient = new DiscoveryClient("http://localhost:5000");
var doc = await discoveryClient.GetAsync();
var introspectionClient = new IntrospectionClient(
doc.IntrospectionEndpoint,
"api_resource",
"apiSecret");
var token= await HttpContext.GetTokenAsync("access_token");
var response = await introspectionClient.SendAsync(
new IntrospectionRequest { Token = token });
var userId = response.Claims.Single(c => c.Type == "sub").Value;
}
The question itself is, am I using the right path to get the Id from the reference token?, because now It works but I don't want to miss anything, specially thinking that is a security concern.
I'm asking also because I have seen anothers using
string userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
that is more straightforward but doesn't seems to fit with reference tokens.
Thanks in advance.
Upvotes: 2
Views: 7446
Reputation: 6415
Inside a controller action that is protected with an [Authorize]
attribute you can simply get claims directly from the ClaimsPrinciple
, without having to go through a manual discovery client. The claims principle is handily aliased simply with User
inside your controllers.
I'm asking also because I have seen anothers using
string userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
that is more straightforward but doesn't seems to fit with reference tokens.
It works just fine with reference tokens. You should have no problems accessing the sub
claim.
EDIT:
As I mentioned in a comment below, I tend to use the standard JwtClaimTypes and create some extension methods on the ClaimsPrinciple
, such as:
public static string GetSub(this ClaimsPrincipal principal)
{
return principal?.FindFirst(x => x.Type.Equals(JwtClaimTypes.Subject))?.Value;
}
or
public static string GetEmail(this ClaimsPrincipal principal)
{
return principal?.FindFirst(x => x.Type.Equals(JwtClaimTypes.Email))?.Value;
}
... so that within my protected actions I can simply use User.GetEmail()
to get hold of claim values.
It's worth stating the obvious, that any method for retrieving claim values will only work if the claims actually exist. i.e. asking for the ZoneInfo claim will not work unless that claim was requested as part of the token request in the first place.
Upvotes: 7