DuendeSoftware / Support

Support for Duende Software products
21 stars 0 forks source link

Receive custom parameter in token endpoint and add to access token #1448

Open OskarKlintrot opened 1 month ago

OskarKlintrot commented 1 month ago

Which version of Duende IdentityServer are you using?

v7.0.7

Which version of .NET are you using?

.NET 8

Question

I want to receive a custom parameter in the token endpoint that I then add to the access token. It has nothing to do with the actual authentication so acr seems to be the wrong place as well as parameterized scopes (but I might be wrong), hence why I just want to add a parameter, just like IdentityModel lets me; client.Parameters.Add("device_id", "1234abc");.

Everything looks right in the request:

info: Duende.IdentityServer.Validation.TokenRequestValidator[0]
      Token request validation success, {
        "ClientId": "5NexyxUEWSix1amW2lZ_EpheKPYnI85ziFa8iQ2dYps",
        "ClientName": "no backend",
        "GrantType": "authorization_code",
        "AuthorizationCode": "****B3-1",
        "RefreshToken": "********",
        "Raw": {
          "grant_type": "authorization_code",
          "code": "***REDACTED***",
          "redirect_uri": "https://oauth.pstmn.io/v1/callback",
          "code_verifier": "8XuF_MOggYuW0clIeRamDQANPC1ogdZiAKMRb00mA8Q",
          "device_id": "1234abc"
        }
      }

Then I tried to follow Dynamic Request Validation and Customization like this:

using System.Security.Claims;
using Duende.IdentityServer.Validation;

namespace Identity.Web;

public sealed class DefaultCustomTokenRequestValidator : ICustomTokenRequestValidator
{
    public Task ValidateAsync(CustomTokenRequestValidationContext context)
    {
        if (context.Result.ValidatedRequest.Raw?.Get("device_id") is string deviceId)
        {
            context.Result.ValidatedRequest.ClientClaims.Add(new Claim("device_id", deviceId));
        }

        return Task.CompletedTask;
    }
}

Which does add the claim to the client (might be the wrong place, the device id belongs to a user and not a client) before exiting the method but then it disappears. I can't find it later in my custom IProfileService. Doesn't seem to matter if I request the scope device_id or not either (I added the scope to the client). I assume I'm doing something wrong but I'm out of ideas by now...


Update

If I create a new ClaimsPrincipal and add the claim there I can find it later in the profile service:

public sealed class DefaultCustomTokenRequestValidator : ICustomTokenRequestValidator
{
    public Task ValidateAsync(CustomTokenRequestValidationContext context)
    {
        if (context.Result.ValidatedRequest.GrantType == GrantType.AuthorizationCode
            && context.Result.ValidatedRequest.Raw?.Get("device_id") is string deviceId)
        {
            context.Result.ValidatedRequest.Subject.AddIdentity(new ClaimsIdentity([new Claim("device_id", deviceId)]));
        }

        return Task.CompletedTask;
    }
}

Maybe this is more correct? I can also add it directly to the client (context.Result.ValidatedRequest.Client.Claims.Add(new ClientClaim("device_id", deviceId));) to work with it later in the profile service. Not sure what is most correct.