We are currently working with a team on two different RestAPIs. One is our main system and the other one is just an empty host with IdentityServer registered in it. We have successfully configured CORS
and pre-flight OPTIONS
handling in the main app, but now we are struggling with OPTIONS
in IdentityServer (CORS
is enabled and working). Unfortunately, we have some specific headers that are needed in token request and browser is sending pre-flight OPTIONS
request. Every time we do that, we get:
The requested resource does not support HTTP method 'OPTIONS'.
In our RestAPI application pipeline we just had to create DelegatingHandler
:
public class OptionsHttpMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Options)
{
var apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
var controllerRequested = request.GetRouteData().Values["controller"] as string;
var supportedMethods = apiExplorer.ApiDescriptions
.Where(d =>
{
var controller = d.ActionDescriptor.ControllerDescriptor.ControllerName;
return string.Equals(
controller, controllerRequested, StringComparison.OrdinalIgnoreCase);
})
.Select(d => d.HttpMethod.Method)
.Distinct();
if (!supportedMethods.Any())
{
return Task.Factory.StartNew(
() => request.CreateResponse(HttpStatusCode.NotFound));
}
return Task.Factory.StartNew(() =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK);
return response;
});
}
return base.SendAsync(request, cancellationToken);
}
}
And register it in Global.asax
:
GlobalConfiguration.Configuration.MessageHandlers.Add(new OptionsHttpMessageHandler());
I have no idea how to register it in IdentityServer pipeline. Here's the simplified implementation of Owin Startup class:
[assembly: OwinStartup(typeof(MyNamespace.IdentityServer.Host.Startup))]
namespace MyNamespace.IdentityServer.Host
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var identityServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryClients(Clients.Get());
identityServerServiceFactory.UserService = new Registration<IUserService>(resolver => UserServiceFactory.Create());
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "MyNamespace.IdentityServer",
SigningCertificate = this.LoadCertificate(),
Factory = identityServerServiceFactory
});
});
}
}
}
Tried so far:
Setting some ISS handlers in
<handlers>
section in web.config- How to support HTTP OPTIONS verb in ASP.NET MVC/WebAPI application
- IIS hijacks CORS Preflight OPTIONS request
- and many more, all about this approach
I don't remember where I found this solution, but someone suggested to do the following:
- register host API before IdentityServer
- create a controller with routing set to address we want to hijack OPTIONS requests (in my case
/identity/connect/token
) - add
[HttpOptions]
tagged action, which returns200, OK
… but unfortunately, as I expected, my new controller hijacked all the traffic to the IdentityServer, not only
OPTIONS
requests.
Update - forgot to mention about third option
- I've come up with a solution that works (I mean almost because I haven't finished yet). It's simple, but I would rather avoid such an approach, because it adds additional work and, later on, code to maintain. The idea is to simply wrap
IdentityServer's
token endpoint in our own endpoint, so that AJAX calls only our RestAPI. And, as I stated before, our host is already set up forCORS
and pre-flightOPTIONS