0
votes

I have an Asp.Net Web API project hosted in a Web Role (IIS 8.5), and I'm following the steps of this post to use Azure Scheduler to send a request to my app every some time to do some work.

The problem is that the HttpModule responsible for authenticating the scheduler's requests, is being loaded but its result is being ignored afterwards when the request hits the ApiController.

I'm using POSTMAN to test the REST endpoint and this is what I see in the http module when debugging:

Http Module

And the response message is:

Authorization has been denied for this request

If I remove the Authorize attribute from the controller, this is what I see when the request hits it:

SchedulerController

Relevant part of Startup.Auth.cs:

        app.UseCookieAuthentication(cookieOptions);
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);  
        app.UseOAuthBearerTokens(OAuthBearerOptions);
        app.UseLinkedInAuthentication("777777", "8888888");

Is it possible to use an HttpModule to authenticate the request when using Asp.Net Identity and Owin, or I must implement an Owin AuthenticationMiddleware module to achieve this?

1

1 Answers

0
votes

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")