0
votes

I am trying to implement OAuth Owin token based authentication in ASP.Net Web API2 (MVC). When I apply [Authorize] annotation on my controller, the request is not hitting ValidateclientAuthentication and I am getting the error "Authorization has been denied for this request". Following is my code:

Startup.cs

[assembly: OwinStartup(typeof(EY.DRA.WebAPI.Startup))]
namespace EY.DRA.WebAPI
{
    public class Startup
    {
        public static string PublicClientId { get; private set; }
        public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
        public void Configuration(IAppBuilder app)
        {
            ConfigureOAuth(app);
            HttpConfiguration httpConfig = new HttpConfiguration();

            //httpConfig.Formatters.Clear();
            //httpConfig.Formatters.Add(new JsonMediaTypeFormatter());
            //httpConfig.Formatters.JsonFormatter.SerializerSettings =
            //new JsonSerializerSettings
            //{
            //    ContractResolver = new CamelCasePropertyNamesContractResolver()
            //};

            //app.UseWebApi(httpConfig);

            WebApiConfig.Register(httpConfig);
            app.UseWebApi(httpConfig);
        }

        public void ConfigureOAuth(IAppBuilder app)
        {
            try
            {
                // Configure the db context and user manager to use a single instance per request
                app.CreatePerOwinContext(OwinAuthDbContext.Create);
                app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

                // Enable the application to use a cookie to store information for the signed in user
                // and to use a cookie to temporarily store information about a user logging in with a third party login provider
                app.UseCookieAuthentication(new CookieAuthenticationOptions());
                app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);


                // Configure the application for OAuth based flow
                PublicClientId = "self";

                OAuthOptions = new OAuthAuthorizationServerOptions
                {
                    TokenEndpointPath = new PathString("/Token"),
                    Provider = new AuthorizationServerProvider(PublicClientId),
                    //AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
                    AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
                    AllowInsecureHttp = true
                };

                //app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
                //{
                //    TokenEndpointPath = new PathString("/Token"),
                //    Provider = new AuthorizationServerProvider(PublicClientId),
                //    AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
                //    AllowInsecureHttp = true,

                //});
                // Enable the application to use bearer tokens to authenticate users
                 app.UseOAuthBearerTokens(OAuthOptions);
            }
            catch(Exception ex)
            {
                throw ex;
            }

        }

        //private static UserManager<IdentityUser> CreateManager(IdentityFactoryOptions<UserManager<IdentityUser>> options, IOwinContext context)
        //{
        //    var userStore = new UserStore<IdentityUser>(context.Get<OwinAuthDbContext>());
        //    var owinManager = new UserManager<IdentityUser>(userStore);
        //    return owinManager;
        //}
    }

AuthorizationServerProvider.cs

 public class AuthorizationServerProvider: OAuthAuthorizationServerProvider
    {
        private readonly string _publicClientId;
        public AuthorizationServerProvider(string publicClientId)
        {
            if (publicClientId == null)
            {
                throw new ArgumentNullException("publicClientId");
            }

            _publicClientId = publicClientId;
        }

        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            try
            {
                context.Validated();

                return Task.FromResult<object>(null);
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }

        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (context.ClientId == _publicClientId)
            {
                Uri expectedRootUri = new Uri(context.Request.Uri, "/");

                if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                {
                    context.Validated();
                }
            }

            return Task.FromResult<object>(null);
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            UserManager<IdentityUser> userManager = context.OwinContext.GetUserManager<UserManager<IdentityUser>>();
            IdentityUser user;
            try
            {
                user = await userManager.FindAsync(context.UserName, context.Password);
            }
            catch
            {
                // Could not retrieve the user due to error.
                context.SetError("server_error");
                context.Rejected();
                return;
            }
            if (user != null)
            {
                ClaimsIdentity identity = await userManager.CreateIdentityAsync(
                                                        user,
                                                        DefaultAuthenticationTypes.ExternalBearer);
                context.Validated(identity);
            }
            else
            {
                context.SetError("invalid_grant", "Invalid UserId or gpn'");
                context.Rejected();
            }
        }


    }

IdentityConfig.cs : this class implements the ApplicationManager. AspNetUser is the model class created from the table that Asp.Net.Identity.EntityFramework has created in the backend.

public class ApplicationUserManager : UserManager<AspNetUser>
    {
        public ApplicationUserManager(IUserStore<AspNetUser> store)
            : base(store)
        {
        }

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        {
            var manager = new ApplicationUserManager(new UserStore<AspNetUser>(context.Get<OwinAuthDbContext>()));

            var dataProtectionProvider = options.DataProtectionProvider;
            if (dataProtectionProvider != null)
            {
                manager.UserTokenProvider = new DataProtectorTokenProvider<AspNetUser>(dataProtectionProvider.Create("ASP.NET Identity"));
            }
            return manager;
        }
    }

AspNetUser.cs:

using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.EntityFramework;
    using System;
    using System.Collections.Generic;
    using System.Security.Claims;
    using System.Threading.Tasks;

    public partial class AspNetUser : IdentityUser
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public AspNetUser()
        {
            this.AspNetUserClaims = new HashSet<AspNetUserClaim>();
            this.AspNetUserLogins = new HashSet<AspNetUserLogin>();
            this.AspNetRoles = new HashSet<AspNetRole>();
        }

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<AspNetUser> manager, string authenticationType)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
            // Add custom user claims here
            return userIdentity;
        }

        public string Id { get; set; }
        public string Email { get; set; }
        public string PasswordHash { get; set; }
        public string SecurityStamp { get; set; }
        public string PhoneNumber { get; set; }
        public bool TwoFactorEnabled { get; set; }
        public Nullable<System.DateTime> LockoutEndDateUtc { get; set; }
        public int AccessFailedCount { get; set; }
        public string UserName { get; set; }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<AspNetUserClaim> AspNetUserClaims { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<AspNetUserLogin> AspNetUserLogins { get; set; }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<AspNetRole> AspNetRoles { get; set; }
    }

Global.asax.cs:

public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }

WebApiConfig.cs:

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // enable elmah
            config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());

            // Web API configuration and services
            // Configure Web API to use only bearer token authentication.
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional },

                // constraint required so this route only matches valid controller names
                constraints: new { controller = GetControllerNames() }
            );                        
        }

        // helper method that returns a string of all api controller names 
        // in this solution, to be used in route constraints above
        private static string GetControllerNames()
        {
            var controllerNames = Assembly.GetCallingAssembly()
                .GetTypes()
                .Where(x =>
                    x.IsSubclassOf(typeof(ApiController)) &&
                    x.FullName.StartsWith(MethodBase.GetCurrentMethod().DeclaringType.Namespace + ".Controllers"))
                .ToList()
                .Select(x => x.Name.Replace("Controller", ""));

            return string.Join("|", controllerNames);
        }
    }
}

The controller class where I am applying [Authorize] attribute:

[Authorize]
    public class RegisteredUserController : ApiController
    {
        private readonly IRegisteredUserService _regUsrServices;

        #region Constructors

        public RegisteredUserController()
        {
            _regUsrServices = new RegisteredUserService();
        }

        #endregion

        public List<RegisteredUserDTO> GetRegisteredUser()
        {
            return _regUsrServices.GetRegisteredUser();
        }
    }

The issue with the above code is when a new request comes in (eg. http://localhost:44720/api/RegisteredUser), the execution goes through ConfigureOAuth(IAppBuilder app) -> AppicationUserManager.Create -> and then it goes directly to RegisteredUserController without going to AuthorizationServerProvider -> ValidateClientAuthentication. The header is passed correctly and I can view it from IOwinContext context. Please help me how I can hit ValidateClientAuthentication method to get the request validated.

Following is how my request to server looks like in Postman: enter image description here

When I am sending /Token I am getting 'unsupported_grant_type' error: enter image description here At this call ValidateClientAuthentication is getting hit. Please help.

1

1 Answers

2
votes

The main purpose of Authorization Server is to issue token to third party clients with the approval of the resource owner. You can get the token from endpoint defined in OAuthOptions. Check this link for more information. http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/