Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Authorization with ASP.NET Identity & Autofac OWIN integration

(an update has been added at the bottom of this question)

I have a web application that uses both MVC5 and WebAPI2 with Autofac for DI. The app uses ASP.NET Identity and oAuth bearer tokens, though the latter may be beside the point. This has all worked perfectly well, but at this point I need the same instances of my injected services to be shared throughout the OWIN pipeline as well as the rest of my application and therefore I'm trying to setup Autofac's OWIN integrations for MVC and Web API. I seem to be close- everything seems to work except for AuthorizeAttibutes on ApiControllers. The oAuth process completes successfully and I end up signed in with a bearer token, but subsequent attempts to authorize with said token on WebAPI controllers/actions fail.

Specifically, in the IsAuthorized method of System.Web.Http.AuthorizeAttribute, IPrincipal.Identity seems that it has not been instantiated correctly in that it doesn't have the appropriate claims and the IsAuthenticated property is always false. The developers of Autofac indicate that this attribute should work with the OWIN integrations, even though that code uses GlobalConfiguration which is not advisable for the OWIN integrations. I've seen multiple recommendations to remove config.SuppressDefaultHostAuthentication() (here and here), which, while not advisable, I've tried out of desperation and yet to no avail- for my particular configuration this causes IPrincipal to come back as null. I've also tried to modify a much simpler example project than my own to use AuthorizeAttribute on the WebAPI controller, also with no success. At this point I'm out of things to try, and help would be greatly appreciated.

Here is my Startup.cs:

[assembly: OwinStartup(typeof (Startup))]
namespace Project.Web
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var builder = new ContainerBuilder();
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
            var config = new HttpConfiguration();
            builder.RegisterHttpRequestMessage(config);
            builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
            RegisterGeneralTypes(builder);
            var container = builder.Build();
            WebApiConfig.Register(config);
            config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
            WebApiFilterConfig.RegisterGlobalFilters(config.Filters);

            app.UseAutofacMiddleware(container);
            app.UseAutofacWebApi(config);
            app.UseAutofacMvc();
            app.UseWebApi(config);

            ConfigureAuth(app);
        }

        private static void RegisterGeneralTypes(ContainerBuilder builder)
        {
            builder.Register(c => new DomainModelContext())
                .AsSelf()
                .InstancePerRequest();

            builder.Register(c => HttpContext.Current.User.Identity)
                .As(typeof (IIdentity));

            builder.RegisterType<EmailService>()
                .AsImplementedInterfaces()
                .InstancePerRequest();

            builder.Register(c => new IdentityFactoryOptions<DomainUserManager>
            {
                DataProtectionProvider = DataProtectionProvider
            }).InstancePerRequest();

            builder.RegisterType<DomainUserManager>()
                .AsSelf()
                .UsingConstructor(typeof (IIdentityMessageService),
                    typeof (IdentityFactoryOptions<DomainUserManager>),
                    typeof (CustomUserStore))
                .InstancePerRequest();

            builder.RegisterType<CustomUserStore>()
                .AsImplementedInterfaces()
                .AsSelf()
                .InstancePerRequest();

            builder.Register(c => HttpContext.Current.GetOwinContext().Authentication)
                .As<IAuthenticationManager>();
        }
    }
}

and my Startup.Auth.cs:

public partial class Startup
{
    internal static IDataProtectionProvider DataProtectionProvider;
    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
    public static string PublicClientId { get; private set; }

    public void ConfigureAuth(IAppBuilder app)
    {
        var onValidateIdentity = SecurityStampValidator
            .OnValidateIdentity<DomainUserManager, DomainUser, int>(
                validateInterval: TimeSpan.FromMinutes(30),
                regenerateIdentityCallback: (manager, user) =>
                    user.GenerateUserIdentityAsync(manager, CookieAuthenticationDefaults.AuthenticationType),
                getUserIdCallback: id => id.GetUserId<int>());

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            LoginPath = new PathString("/account/login"),

            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = onValidateIdentity
            }
        });
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Configure the application for OAuth based flow
        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/token"),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            AuthorizeEndpointPath = new PathString("/api/v1/account/externallogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);

        DataProtectionProvider = app.GetDataProtectionProvider();
    }
}

I think that covers it, but I will gladly post additional code upon request.

UPDATE

So based on jumuro's answer, I changed my order of registrations as recommended. However, this simply transferred the exact same issue from Web API authorization to MVC authorization. Since I had MVC auth working before the update, I ultimately tried registering auth in the pipeline twice as follows:

app.UseAutofacMiddleware(container);
ConfigureAuth(app);
app.UseAutofacWebApi(config);
app.UseAutofacMvc();
app.UseWebApi(config);
ConfigureAuth(app);

This works, but I really can't say that I understand why and I can't imagine that it's good to be doing this twice. So now I have new questions:

  1. It makes sense that WebAPI would need Auth registered in the pipeline first, but why in the world does MVC want me to register Auth last?
  2. How can I clean this up and avoid calling ConfigureAuth twice?
like image 476
joelmdev Avatar asked Jun 15 '16 05:06

joelmdev


People also ask

What is authorization in ASP.NET Core?

Authentication is the process of determining a user's identity. Authorization is the process of determining whether a user has access to a resource. In ASP.NET Core, authentication is handled by the authentication service, IAuthenticationService, which is used by authentication middleware.

Does ASP.NET support Windows authentication?

Windows Authentication relies on the operating system to authenticate users of ASP.NET Core apps. You can use Windows Authentication when your server runs on a corporate network using Active Directory domain identities or Windows accounts to identify users.

How do I Authorize in C#?

Action. To restrict access for specific actions, add the attribute to the action method. public class StudentsController : ApiController{ public HttpResponseMessage Get() { ... } // Require authorization for a specific action. [Authorize] public HttpResponseMessage Post() { ... } }

How authorization works in ASP NET MVC?

The Authorize Attribute In ASP.NET MVC, any incoming request is bound to a controller/method pair and served. This means that once the request matches a supported route and is resolved to controller and method, it gets executed no matter what.


1 Answers

You have to add middlewares to the application pipeline in the correct order. Bearer token has to be validated before the MVC and Web Api middlewares process the request.

Try this order in your Configuration() method:

public void Configuration(IAppBuilder app)
{
    ...
    app.UseAutofacMiddleware(container);
    ConfigureAuth(app);
    app.UseAutofacMvc();
    app.UseWebApi(config);
    app.UseAutofacWebApi(config);
    ...
}

I hope it helps.

like image 94
jumuro Avatar answered Sep 24 '22 11:09

jumuro