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?
source to share
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);
source to share