ASP.Net OAuth authorization server: add an array as an optional response parameter

I have implemented a custom OAuthAuthorizationServerProvider

one and I want to add some additional elements to the response when my client requests an access token.

To do this, I overridden the method OAuthAuthorizationServerProvider.TokenEndpoint

and I was able to add some individual elements (adding them to the dictionary context.AdditionalResponseParameters

). Now I have this answer:

{
  "access_token": "wxoCtLSdPXNW9KK09PVhSqYho...",
  "token_type": "bearer",
  "expires_in": 1199,
  "refresh_token": "uk0kFyj4Q2OufWKt4IzWQHlj...",
  "toto": "bloblo",
  "tata": "blabla"
}

      

This is great, but my goal is to add an array to get a response like this:

{
  "access_token": "wxoCtLSdPXNW9KK09PVhSqYho...",
  "token_type": "bearer",
  "expires_in": 1199,
  "refresh_token": "uk0kFyj4Q2OufWKt4IzWQHlj...",
  "scopes": ["read", "write"]
}

      

I tried adding a json-processed list or array instead of a simple string, but it gives me

"scopes": "[\"read\",\"write\"]"

      

So that the string is processed in Json and not in a Json array: /

How do I add a Json array in response to the Endpoint token?

+2


source to share


1 answer


Problem

When we use app.OAuthBearerAuthenticationExtensions

, the following chain is called:

public static class OAuthBearerAuthenticationExtensions
  {
    public static IAppBuilder UseOAuthBearerAuthentication(this IAppBuilder app, OAuthBearerAuthenticationOptions options)
    {
      if (app == null)
        throw new ArgumentNullException(nameof (app));
      app.Use((object) typeof (OAuthBearerAuthenticationMiddleware), (object) app, (object) options);
      app.UseStageMarker(PipelineStage.Authenticate);
      return app;
    }
  }

      

Then the object of the type OAuthAuthorizationServerMiddleware

uses the inner class OAuthAuthorizationServerHandler

where is used JsonTextWriter

:

using (var jsonTextWriter = new JsonTextWriter((TextWriter) new StreamWriter((Stream) memory)))
{
    jsonTextWriter.WriteStartObject();
    jsonTextWriter.WritePropertyName("access_token");
    jsonTextWriter.WriteValue(accessToken);
    jsonTextWriter.WritePropertyName("token_type");
    jsonTextWriter.WriteValue("bearer");
    // and so on
    this.Response.ContentLength = new long?((long) body.Length);
    await this.Response.WriteAsync(body, this.Request.CallCancelled);
}

      

There are two limitations here:
*) JsonTextWriter

is a pure class that cannot be customized, it just writes the string as a StringBuilder, so Json.Settings = new MySettings()

cannot be applied. Also JsontTextWriter

does not support complex objects. Arrays can only be written like jsonTextWriter.WriteStartArray()

and jsonTextWriter.WriteEndArray()

, but this is ignored in OAuthAuthorizationServerHandler

.
*) Some classes are internal and cannot be overwritten or inherited.

It looks like Microsoft developers didn't anticipate this issue and only limited custom properties IDictionary<string, string>

.

Solution 1

Instead, app.UseOAuthBearerAuthentication(...)

apply your own code



app.Use<MyOAuthBearerAuthenticationMiddleware>(options);
app.UseStageMarker(PipelineStage.Authenticate);

      

You can get a class from OAuthBearerAuthenticationMiddleware

and use it for your own purposes.

Solution 2

Undo the end point of the marker. This is a tricky thing.

1) Create your own middleware that will wrap other calls and cancel the body response flow.

class AuthenticationPermissionsMiddleware : OwinMiddleware
{
    public AuthenticationPermissionsMiddleware(OwinMiddleware next) 
        : base(next)
    {            
    }

    public override async Task Invoke(IOwinContext context)
    {
        if (!context.Request.Path.Equals("/Token")
        {
            await Next.Invoke(context);
            return;
        }

        using (var tokenBodyStream = new MemoryStream())
        {
            // save initial OWIN stream
            var initialOwinBodyStream = context.Response.Body;

            // create new memory stream
            context.Response.Body = tokenBodyStream;

            // other middlewares will will update our tokenBodyStream
            await Next.Invoke(context);

            var tokenResponseBody = GetBodyFromStream(context.Response);
            var obj = JsonConvert.DeserializeObject(tokenResponseBody);
            var jObject = JObject.FromObject(obj);

            // add your custom array or any other object
            var scopes = new Scope[];
            jObject.Add("scopes", JToken.FromObject(scopes));
            var bytes = Encoding.UTF8.GetBytes(jObject.ToString());
            context.Response.Body.Seek(0, SeekOrigin.Begin);
            await tokenBodyStream.WriteAsync(bytes, 0, bytes.Length);

            context.Response.ContentLength = data.LongLength;
            tokenBodyStream.Seek(0, SeekOrigin.Begin);

            // get back result to the OWIN stream
            await context.Response.Body.CopyToAsync(initialOwinBodyStream);
            }
        }
    }

    private string GetBodyFromStream(IOwinResponse response)
    {
        using (var memoryStream = new MemoryStream())
        {
            response.Body.Seek(0, SeekOrigin.Begin);
            response.Body.CopyTo(memoryStream);
            memoryStream.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(memoryStream))
            {
                return reader.ReadToEnd();
            }
        }
    }
}

      

2) Use the new middleware before UseOAuthBearerTokens

in the authentication startup method.

app.Use<AuthenticationPermissionsMiddleware>();
app.UseOAuthBearerTokens(options);

      

0


source







All Articles