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:
When I am sending /Token I am getting 'unsupported_grant_type' error: At this call ValidateClientAuthentication is getting hit. Please help.