http://docs.identityserver.io/en/release/reference/grant_validation_result.html
I am building an extension grant, where the final call is;
context.Result = new GrantValidationResult(context.Request.Raw.Get("subject"), "arbitrary_owner_resource",userClaims);
my userClaims has 17 claims in it.
i.e. [{"test","some value"}]
looking at the code it makes its way into a ClaimsIdentity.
I traced this all the way into DefaultClaimsService.GetAccessTokenClaimsAsync.
I don't see anywhere that these claims ever make it into a final access_token. They don't make it into mine.
I was expecting that these claims would get into access_token this way.
I really don't want to introduce my own implementation of DefaultClaimsService. I was hoping to add all my claims I wanted and end my work at the context.Result = new GrantValidationResult(...).
H
It's a two-step process: 1) authenticate, 2) issue claims in token. Your custom grant validator is doing #1. Your profile service does #2. The identity produced by #1 is passed to #2.
OK, so I verified I can contribute claims from my implementation of IProfileService.
The claims I want to add came in as an argument.
grant_type:arbitrary_owner_resource
scope:offline_access
client_id:resource-owner-client
client_secret:secret
arbitrary-claims:{"some-guid":"1234abcd","In":"Flames"}
I haven't found an obvious way to make sure that those arbitrary-claims values make it all the way to my IProfileService.
How would I preserve state from the point of
here I read the Raw input, which are my claims.
new GrantValidationResult()
to
IProfileService.GetProfileDataAsync(ProfileDataRequestContext context);
no Raw input to read, just the ProfileDataRequestContext context.
I am limited to the ProfileDataRequestContext properties.
So far the only way I see is to add them to the Identity as a hint as that is the only thing that makes it all way through the pipeline.
H
I did more code stepping and I think we may need a context object that has integrity from the IExtensionGrantValidator implementation all the way through the pipeline. In my case all the way to the IProfileService implementation.
I am cheating at the moment by passing along the raw form input via identity claims.
Body:
grant_type:arbitrary_resource_owner
client_id:arbitrary-resource-owner-client
client_secret:secret
scope: offline_access nitro metal
arbitrary_claims:{"some-guid":"1234abcd","In":"Flames"}
subject:ratt
.....
var userClaimsFinal = new List<Claim>()
{
new Claim("arbitrary_claims", raw["arbitrary_claims"])
};
context.Result = new GrantValidationResult(principal.GetSubjectId(), ArbitraryResourceOwnerExtensionGrant.Constants.ArbitraryResourceOwner, userClaimsFinal);
I hope I am missing something.
The next thing I was going to try is an extension grant with no subject. No subject, no identity, no cheating. So maybe this will be answered for me if I know how to add claims from the NoSubject extension grant implementation you have as an example
NOTE: My claims are passed in and never stored
I have the same problem here trying to create a custom grant type. The claims I injected into the GrantValidationResult aren't persisted to the token.
I've found a solution but I don't know if it's the right way to do it.
I keep passing the claims to the GrantValidationResult and I replicate them to the IssuedClaims in the ProfileService.
public class DocGrantValidator : IExtensionGrantValidator
{
public string GrantType => "doc";
public Task ValidateAsync(ExtensionGrantValidationContext context)
{
string userDoc = context.Request.Raw.Get("userDoc");
string password = context.Request.Raw.Get("password");
var claims = new List<Claim> {
new Claim("authType", "doc"),
new Claim("channel", "web_dashboard"),
new Claim("hashDoc", userDoc.Sha256())
};
.............
context.Result = new GrantValidationResult(userDoc, "doc", claims);
return Task.FromResult(0);
}
}
public class CustomProfileService : IProfileService
{
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.IssuedClaims = context.Subject.Claims.ToList();
return Task.CompletedTask;
}
public Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
return Task.FromResult(0);
}
}
Take a look at my project:
https://github.com/ghstahl/IdentityServer4-Extension-Grants
But, yes you are doing exactly what I had to do.
I actually created one giant claim, where my claim value was a json object.
I didn't want to blindly replicate everything as you did.
Passing request state data on an identity isn't really the right thing to do in my opinion, so I created a pull request where the entire incoming request is sent to your IProfileService implementation in a context object. I hope they pick it up.
https://github.com/IdentityServer/IdentityServer4/pull/2298
BTW: I would suggest that your DocGrantValidator NOT mutate data, but simply validate that the incoming arguments are good. The mutation of that data should be done at the CustomProfileService level.
i.e. Don't actually build out your claims here, but build them in your CustomProfileService implementation. Send your raw input as a single json and then pick it up later to process it.
Whatever claims you return in your extension grant validator will be on the Subject property on the ProfileDataRequestContext.
@ghstahl Interesting your commit about multiple IProfileServices. It was exactly what I needed. The code it's just an example. I still need to validate these informations against a service or database and get the new claims. Thanks for your suggest!!!
@leastprivilege Yeah I get it. Maybe I am missing something, but is there any reason my claims injected on the GrantValidationResult aren't being persisted to the generated token?
@leastprivilege Yeah I get it. Maybe I am missing something, but is there any reason my claims injected on the GrantValidationResult aren't being persisted to the generated token?
They could be if the code in your profile service copies them over and they match the claims being requested via the scopes.
@brockallen Ok, so I always need an implementation of IProfileService to validate the claims coming from the GrantValidationResult and then pass them to the IssuedClaims so that they can be persisted to the token. Right?
In general you always need a profile service to do the job of mapping claims from wherever you store them into the tokens, yes. Our out of the box implementation only sources claims from the Subject passed into the profile service, which is normally just the claims you issue from the login page.
I didn't test, but the default one should also behave the same way when the claims come from your custom grant validator. So if you were trying to get it to work, then my only guess was that the scopes had no claims configured (or at least not the claims you issued from the custom grant validator).
@brockallen You're right. The default one can persist the claims coming from the grant validator. Custom claims need to be configured in ApiResource.
http://docs.identityserver.io/en/release/topics/resources.html#defining-api-resources
Thanks for you support!!!
@brockallen The main difference I bring in with my ask is that I am using IdentityServer4 as an OAuth2AsAService (OA2aaS).
The following is the extent of the config you will see on the IdentityServer4 side;
Config.cs
__IdentityServer4__ is responsible for managing __client_id(s)__ and what __scopes__ they have access to.
What is NOT on the IdentityServer4 side is users(subject), much less claims. Those 2 items (subject,claims) are passed in.
The grant_type arbitrary_resource_owner shows the usage of what outside services would call when using the __OA2aaS__.
When a subject is in play, I can have my IProfileService implementation add my passed in claims. When a subject is not in play, as in a NoSubject extension grant, I have no ability to add my passed in claims.
When a subject is in play, I can have my IProfileService implementation add my passed in claims. When a subject is not in play, as in a NoSubject extension grant, I have no ability to add my passed in claims.
Ah, that's the key difference. I didn't appreciate that about your scenario....
If there is no user involved, you can set the `ClientClaims麓 on the request object - either from the extension grant validator or a custom token request validator.
Works!
Got a little surprise with the ClientClaimsPrefix when I saw that all the claims I added to context.Request.ClientClaims were pre-pended with "client_{my claim name}"?
setting ClientClaimsPrefix = null fixed that.... in the config.
Thank you.
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.