Openiddict-core: Add additional values into Authorize endpoint response

Created on 14 Feb 2017  路  24Comments  路  Source: openiddict/openiddict-core

I integrate with Twilio and it requires me to produce a Twilio access token for client to work with their services.
Is there an integration point with which I can add tw_access_token value into an Authorize endpoint response along with id_token and access_token?

PS: do you want such questions to be posted on StackOverflow?

enhancement

Most helpful comment

New plan: in 3.0, the ASP.NET Core host will natively support returning custom parameters set via the new AuthenticationProperties.Parameters property introduced in ASP.NET Core 2.1. Both challenge, sign-in (authorization and token responses) and sign-out will be supported. E.g:

var properties = new AuthenticationProperties(
    items: new Dictionary<string, string>(),
    parameters: new Dictionary<string, object>
    {
        ["boolean_parameter"] = true,
        ["integer_parameter"] = 42,
        ["string_parameter"] = "Bob l'Eponge",
        ["array_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"[""Contoso"",""Fabrikam""]"),
        ["object_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}")
    });

return SignIn(principal, properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

Note: a custom event handler will be required when using OpenIddict on OWIN, as AuthenticationProperties.Parameters doesn't exist in the OWIN type.

All 24 comments

Is there an integration point with which I can add tw_access_token value into an Authorize endpoint response along with id_token and access_token?

Nope, there's no such thing. As an alternative, you can store it in your access or identity token.

Yep, I do that now.
Thanks for suggestion.
In order to achieve what I want, are changes to OpenIddict or ASOS required?

ASOS supports returning custom parameters via the ApplyAuthorizationResponse event but OpenIddict doesn't (deliberately) provide any way to use it, to encourage people to use the identity token to flow information from the OP to the client.

Do you think that my scenario violates your intent?
I just thought that the Twilio access token is not an information of the user but one more token. So it does not fit well into identity token.

Without knowing exactly how your app works and how this extra token is retrieved, it's hard to say.

In authorize request client can pass device_id and tw scope

``` C#
public Token AuthorizeTwilio(ClaimsPrincipal user, string deviceId)
{
var identity = user.Identity.Name;

// Create an IP messaging grant for this token
var grant = new IpMessagingGrant();
grant.EndpointId = $"Chat:{identity}:{deviceId}";
grant.ServiceSid = twilioOptions.ChatServiceSid;
var grants = new HashSet<IGrant>() { grant };

var token = new Token(twilioOptions.AccountSid, twilioOptions.ApiKeySid, twilioOptions.ApiSecret, identity, grants: grants);
return token;

}

``` C#
private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user, AuthenticationProperties properties = null)
{
    // Create a new ClaimsPrincipal containing the claims that
    // will be used to create an id_token, a token or a code.
    var principal = await signInManager.CreateUserPrincipalAsync(user);

    if (request.HasScope(Scopes.Twilio))
        await AddTwilioAccessToken(request, user, principal);

``` C#
private async Task AddTwilioAccessToken(OpenIdConnectRequest request, ApplicationUser user, ClaimsPrincipal principal)
{
var deviceIdParam = request.GetParameter("device_id");
if (deviceIdParam.HasValue)
{
var identity = principal.Identity as ClaimsIdentity;

    string accessToken;
    if (user.TwilioSid != null)
        accessToken = twilioManager.AuthorizeTwilio(principal, deviceIdParam.Value.Value.ToString()).ToJwt();
    else
        accessToken = string.Empty;

    identity.AddClaim(new Claim(TwilioClaims.TwilioAccessToken, accessToken));
}

}
```

We're going to support this feature in OpenIddict 1.x/2.x RC1. To force OpenIddict to return a custom property as part of an authorization or token response, you'll have to create a custom authentication property with a dedicated suffix (that will be dynamically removed when returning the response). E.g to return a custom string:

// By calling this method, a twilio_access_token parameter
// will be returned in the authorization/token response.
ticket.AddProperty("twilio_access_token#public_string", accessToken);
var profile = JObject.FromObject(new
{
    first_name = "Bob"
});

// By calling this method, a user_profile parameter
// will be returned in the authorization/token response.
ticket.AddProperty("user_profile#public_json", profile.ToString());

@xperiandri the PR adding support for this feature was just merged. Please give the latest OpenIddict packages a try when you have a minute. I'd like to ensure it fits your needs before releasing RC1.

Wow! Cool! I will have a look.
It requires Core 2.0, doesn't it?

No, it's also part of OpenIddict 1.x, that targets ASP.NET Core 1.0.

Nice!

Works perfect!

I just wonder:

  1. why suffix is required?
  2. why it appears as first value but not the last in a token? Adding it to the end would be better.

why suffix is required?

For 2 reasons:

  • Security: authentication properties are not supposed to be exposed. Requiring a suffix allows us to make sure only public properties are really returned.

  • Correctness: to return the property with the correct JSON type, you have to be explicit and tell OpenIddict how the property will be serialized.

why it appears as first value but not the last in a token? Adding it to the end would be better.

Because the logic was added in the new ProcessSignInResponse event, which is invoked before the other elements (e.g tokens) are added to the response.
I'm not sure I understand why it would be better to add them after the tokens. The order doesn't matter.

Just looked more logical in my opinion.
First of all main stuff, i.e. tokens and their parameters.
Then additional custom stuff.

Just looked more logical in my opinion.

It's just a cosmetic thing and JSON payloads returned from API endpoints are not primarily intended to be read by humans. Definitely not something I'd worry about.

In general.
Sometimes you look at it in Postman
Not a big deal however

@xperiandri FYI, I'm considering revisiting that in RC3.

The idea is to let you have access to the underlying ASOS events to take control of the various OIDC operations via an "advanced" API. To add new properties to an authorization/token response, you could something like that:

services.AddOpenIddict()
    .AddServer(options =>
    {
        options.RegisterProvider(new OpenIdConnectServerProvider
        {
            OnProcessSigninResponse = context =>
            {
                var properties = context.Ticket.Properties.Items;
                if (properties.TryGetValue("twilio_access_token", out var token))
                {
                    context.Response["twilio_access_token"] = token;
                }

                properties.Remove("twilio_access_token");

                return Task.CompletedTask;
            }
        });
    });

Thoughts?

Like I add properties to a ticket as usual but then they are moved 1 level up (from Identity Token to main payload) by this logic?

For the record, this feature was removed in OpenIddict 3.0. One will still be able to use the events model to achieve a similar result.

Oh
As far as I remember you don't use ASOS in 3.0, do you?
Does that mean some OpenIddict 3.0 events?

Does that mean some OpenIddict 3.0 events?

Yeah. OpenIddict 3.0 will come with a new overhauled events model, inspired from ASOS but more powerful (in 3.0, OpenIddict itself is a massive collection of event handlers you can replace, remove or reorder if you want to).

The equivalent in 3.0 will be something like that:

services.AddOpenIddict()
    .AddServer(options =>
    {
        options.AddEventHandler<ProcessSignInContext>(builder =>
        {
            builder.UseInlineHandler(context =>
            {
                // Retrieve the AuthenticationProperties set from the authorization controller
                // and add a "custom_property" containing the value of the "property" property.
                var properties = context.Transaction.GetProperty<AuthenticationProperties>(
                    typeof(AuthenticationProperties).FullName);

                context.Response["custom_property"] = properties?.GetString("property");

                return default;
            });
        });
    });

Looks nice, thank you very much for the sample!

You're welcome 馃槃

New plan: in 3.0, the ASP.NET Core host will natively support returning custom parameters set via the new AuthenticationProperties.Parameters property introduced in ASP.NET Core 2.1. Both challenge, sign-in (authorization and token responses) and sign-out will be supported. E.g:

var properties = new AuthenticationProperties(
    items: new Dictionary<string, string>(),
    parameters: new Dictionary<string, object>
    {
        ["boolean_parameter"] = true,
        ["integer_parameter"] = 42,
        ["string_parameter"] = "Bob l'Eponge",
        ["array_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"[""Contoso"",""Fabrikam""]"),
        ["object_parameter"] = JsonSerializer.Deserialize<JsonElement>(@"{""parameter"":""value""}")
    });

return SignIn(principal, properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

Note: a custom event handler will be required when using OpenIddict on OWIN, as AuthenticationProperties.Parameters doesn't exist in the OWIN type.

Was this page helpful?
0 / 5 - 0 ratings