Ok, I had implemented a custom owin authentication middleware just to find out that it also wouldn't work. There reason for this is that I had added this line to my WebApi.config file some months ago to avoid unintentional cookie authentication for my web api controllers:
config.SuppressDefaultHostAuthentication();
This also suppressed my new owin auth middleware. To enable it I had to add this line:
config.Filters.Add(new HostAuthenticationFilter(SchedulerAuthenticationMiddlewareConstants.DefaultAuthenticationType));
Completely removing the SuppressDefaultHostAuthentication()
makes the HttpModule to work but in that case I have the disadvantage of implicitly enabling other authentication mechanisms other than bearer tokens for the REST endpoints.
The functionally corresponding Owin Auth Middleware of the HttpModule looks as follows:
using System.Collections.Generic;
using System.IO;
using System.Security.Claims;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Owin;
namespace SchedulerAuthenticationMiddleware
{
public static class SchedulerAuthenticationExtensions
{
public static IAppBuilder UseSchedulerAuthentication(this IAppBuilder app, SchedulerAuthenticationOptions options)
{
return app.Use(typeof(SchedulerAuthenticationMiddleware), app, options);
}
}
public static class SchedulerAuthenticationMiddlewareConstants
{
public const string DefaultAuthenticationType = "Scheduler";
}
public class SchedulerAuthenticationOptions : AuthenticationOptions
{
public SchedulerAuthenticationOptions(string schedulerSharedSecret)
: base(SchedulerAuthenticationMiddlewareConstants.DefaultAuthenticationType)
{
Description.Caption = SchedulerAuthenticationMiddlewareConstants.DefaultAuthenticationType;
// http://brockallen.com/2013/10/27/host-authentication-and-web-api-with-owin-and-active-vs-passive-authentication-middleware/
// Active middleware always look at every incoming request and attempt to authenticate the call and if successful
// they create a principal that represents the current user and assign that principal to the hosting environment.
// Passive middleware, on the other hand, only inspects the request when asked to.
AuthenticationMode = AuthenticationMode.Passive;
SchedulerSharedSecret = schedulerSharedSecret;
}
public string SchedulerSharedSecret { get; set; }
}
// One instance is created when the application starts.
public class SchedulerAuthenticationMiddleware : AuthenticationMiddleware<SchedulerAuthenticationOptions>
{
public SchedulerAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SchedulerAuthenticationOptions options)
: base(next, options)
{
}
// Called for each request, to create a handler for each request.
protected override AuthenticationHandler<SchedulerAuthenticationOptions> CreateHandler()
{
return new SchedulerAuthenticationHandler();
}
}
class SchedulerAuthenticationHandler : AuthenticationHandler<SchedulerAuthenticationOptions>
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
AuthenticationTicket ticket = null;
if (Context.Request.Headers.ContainsKey("x-ms-scheduler-jobid"))
{
using (StreamReader sr = new StreamReader(Context.Request.Body))
{
string bodyContent = sr.ReadToEnd();
var match = new Regex(@"secret:(\d*)").Match(bodyContent);
if (match.Success && match.Groups[1].Value == Options.SchedulerSharedSecret)
{
ticket = CreateTicket();
}
}
}
return Task.FromResult(ticket);
}
private AuthenticationTicket CreateTicket()
{
AuthenticationProperties properties = new AuthenticationProperties();
ClaimsIdentity claimIdentity = CreateSchedulerIdentity();
return new AuthenticationTicket(claimIdentity, properties);
}
private ClaimsIdentity CreateSchedulerIdentity()
{
// ASP.Net Identity requires the NameIdentifier field to be set or it won't
// accept the external login (AuthenticationManagerExtensions.GetExternalLoginInfo)
Claim nameIdentifier = new Claim(ClaimTypes.NameIdentifier, "scheduler", null, Options.AuthenticationType);
Claim nameIdClaim = new Claim(ClaimTypes.Name, "scheduler", null, Options.AuthenticationType);
Claim schedulerRoleClaim = new Claim(ClaimTypes.Role, "scheduler");
Claim identificatorClaim = new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "application");
ClaimsIdentity claimIdentity = new ClaimsIdentity(new List<Claim>
{
nameIdentifier,
nameIdClaim,
schedulerRoleClaim,
identificatorClaim
}, "custom", ClaimTypes.Name, ClaimTypes.Role);
return claimIdentity;
}
}
}
At last, I can authorize any of my web api actions using Authorize(Roles = "scheduler")