i've been stuck for a few days trying to configure the PasswordFlow/Refresh sample and need some help on how to troubleshoot my issue.
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed for user: (null).
I tried the suggestion made by Kevin Chalet on July 19th to use OAuthValidationDefaults.AuthenticationScheme
public class Startup
{
public IConfiguration Configuration { get; set; }
private string _environmentName;
private string _userAuthConnectionString;
public Startup(IHostingEnvironment env)
{
Debug.WriteLine($"EnvironmentName:{env.EnvironmentName}");
_environmentName = env.EnvironmentName;
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{_environmentName.ToLower()}.json", optional: true);
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
//services.AddCors();
services.AddMvc(); // Add framework services.
ConfigureEntityFramework(services);
ConfigureSettings(services);
services.AddOptions();
ConfigureCustomServices(services);
services.AddAuthorization(auth => ConfigureAuthorization(auth));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Reference: http://benjii.me/2016/01/angular2-routing-with-asp-net-core-1/
// Route all unknown requests to app root
app.Use(async (context, next) =>
{
await next();
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page.
// Rewrite request to use app root
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/index.html"; // Put your Angular root page here
await next();
}
});
app.UseAuthentication();
// Reference: http://www.mithunvp.com/angular-2-in-asp-net-5-typescript-visual-studio-2015/
// For ASP.NET 5 to serve static files, we need to add StaticFiles middle ware in Configure method of StartUp.cs page.
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
}
private void ConfigureSettings(IServiceCollection services)
{
services.Configure<PortalAuthenticationSettings>(Configuration.GetSection("PortalAuthentication"));
}
private void ConfigureEntityFramework(IServiceCollection services)
{
//Configure with our Settings object
var portalConnectionString = Configuration.GetSection("Data:DefaultConnection:ConnectionString").Value;
_userAuthConnectionString = Configuration.GetSection("Data:UserAuthConnection:ConnectionString").Value;
services.AddDbContext<PortalContext>(options => {
options.UseSqlServer(portalConnectionString);
});
services.AddDbContext<AuthPortalDbContext>(options => {
options.UseSqlServer(_userAuthConnectionString);
// Register the entity sets needed by OpenIddict.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
options.UseOpenIddict();
});
// Register the Identity services.
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AuthPortalDbContext>()
.AddDefaultTokenProviders();
#region may not need
//services.ConfigureApplicationCookie(config =>
//{
// config.Events = new CookieAuthenticationEvents
// {
// OnRedirectToLogin = ctx =>
// {
// if (ctx.Request.Path.StartsWithSegments("/api"))
// {
// ctx.RedirectUri = null;
// ctx.Response.WriteAsync("{\"error\": " + ctx.Response.StatusCode + "}");
// }
// else
// {
// ctx.Response.Redirect(ctx.RedirectUri);
// }
// return Task.FromResult(0);
// }
// };
//});
#endregion
//https://github.com/openiddict/openiddict-samples/blob/dev/samples/PasswordFlow/AuthorizationServer/Startup.cs
// Configure Identity to use the same JWT claims as OpenIddict instead
// of the legacy WS-Federation claims it uses by default (ClaimTypes),
// which saves you from doing the mapping in your authorization controller.
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name;
options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject;
options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role;
});
// Register the OpenIddict services.
services.AddOpenIddict(options => {
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<AuthPortalDbContext>();
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.AddMvcBinders();
// Enable the token endpoint.
options.EnableTokenEndpoint("/connect/token");
// Enable the password and the refresh token flows.
options.AllowPasswordFlow()
.AllowRefreshTokenFlow();
// During development, you can disable the HTTPS requirement.
options.DisableHttpsRequirement();
// Note: to use JWT access tokens instead of the default
// encrypted format, the following lines are required:
options.UseJsonWebTokens();
options.AddEphemeralSigningKey();
});
services.AddAuthentication(options =>
{
//options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = OAuthValidationDefaults.AuthenticationScheme;
//options.DefaultChallengeScheme = OAuthValidation;
});
#region not part of refresh token sample, nor password flow sample
// this is not part of refresh token sample
// use jwt bearer authentication
var Events = new JwtBearerEvents
{
OnAuthenticationFailed = context => { return Task.FromResult(0); },
OnTokenValidated = context => { return Task.FromResult(0); }
};
#endregion
var portalURL = Configuration.GetSection("PortalURL").Value;
// If you prefer using JWT, don't forget to disable the automatic
// JWT -> WS-Federation claims mapping used by the JWT middleware:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = portalURL;
options.Audience = portalURL;
options.RequireHttpsMetadata = false;
options.Events = Events;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = OpenIdConnectConstants.Claims.Subject,
RoleClaimType = OpenIdConnectConstants.Claims.Role
};
});
services.AddScoped(typeof(Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<ApplicationUser>), typeof(Portal.UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>));
//services.AddScoped<UserManager<ApplicationUser>, PortalUserManager>(); // shiro
services.AddScoped<IAuthorizationHandler, PermissionHandler>();
}
private void ConfigureAuthorization(Microsoft.AspNetCore.Authorization.AuthorizationOptions auth)
{
// Add Inline Authorization Policies
var optionsBuilder = new DbContextOptionsBuilder<AuthPortalDbContext>();
optionsBuilder.UseSqlServer(_userAuthConnectionString);
var ctx = new AuthPortalDbContext(optionsBuilder.Options);
foreach (var permission in ctx.Permission)
{
// Try to convert string to PermissionEnum type
PermissionEnum permEnum;
if (Enum.TryParse(permission.PermissionName, out permEnum))
{
auth.AddPolicy(permission.PermissionName,
policy => policy.Requirements.Add(new PermissionRequirement(permEnum)));
}
}
}
private static void ConfigureCustomServices(IServiceCollection services)
{
// Inject caching helper
//services.AddTransient<IDatabaseInitializer, DatabaseInitializer>();
services.AddSingleton<IResearchRepository, ResearchRepository>();
services.AddSingleton<IResearchService, ResearchService>();
services.AddSingleton<ICompanyRepository, CompanyRepository>();
services.AddSingleton<ICompanyService, CompanyService>();
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IUserService, UserService>();
services.AddSingleton<IAuthorizationHandler, EvaluationAuthorizationHandler>();
}
}
I was able to successfully login but when I'm trying to make a GET call /api/company and getting this Authorization failed for user. I upgraded to .NET Core 2.0 and using Angular 2.0 (with angular-jwt). I'm getting the access token and refresh token. I'm able to decode the token on the browser. I would greatly appreciate the help!
I also added the UserClaimsPrincipalFactory.cs
public class UserClaimsPrincipalFactory<TUser, TRole> : IUserClaimsPrincipalFactory<TUser>
where TUser : class
where TRole : class
{
/// <summary>
/// Initializes a new instance of the <see cref="UserClaimsPrincipalFactory{TUser, TRole}"/> class.
/// </summary>
/// <param name="userManager">The <see cref="UserManager{TUser}"/> to retrieve user information from.</param>
/// <param name="roleManager">The <see cref="RoleManager{TRole}"/> to retrieve a user's roles from.</param>
/// <param name="optionsAccessor">The configured <see cref="IdentityOptions"/>.</param>
public UserClaimsPrincipalFactory(
UserManager<TUser> userManager,
RoleManager<TRole> roleManager,
IOptions<IdentityOptions> optionsAccessor)
{
if (optionsAccessor == null || optionsAccessor.Value == null)
{
throw new ArgumentNullException(nameof(optionsAccessor));
}
UserManager = userManager ?? throw new ArgumentNullException(nameof(userManager));
RoleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager));
Options = optionsAccessor.Value;
}
/// <summary>
/// Gets the <see cref="UserManager{TUser}"/> for this factory.
/// </summary>
/// <value>
/// The current <see cref="UserManager{TUser}"/> for this factory instance.
/// </value>
public UserManager<TUser> UserManager { get; private set; }
/// <summary>
/// Gets the <see cref="RoleManager{TRole}"/> for this factory.
/// </summary>
/// <value>
/// The current <see cref="RoleManager{TRole}"/> for this factory instance.
/// </value>
public RoleManager<TRole> RoleManager { get; private set; }
/// <summary>
/// Gets the <see cref="IdentityOptions"/> for this factory.
/// </summary>
/// <value>
/// The current <see cref="IdentityOptions"/> for this factory instance.
/// </value>
public IdentityOptions Options { get; private set; }
/// <summary>
/// Creates a <see cref="ClaimsPrincipal"/> from an user asynchronously.
/// </summary>
/// <param name="user">The user to create a <see cref="ClaimsPrincipal"/> from.</param>
/// <returns>The <see cref="Task"/> that represents the asynchronous creation operation, containing the created <see cref="ClaimsPrincipal"/>.</returns>
public virtual async Task<ClaimsPrincipal> CreateAsync(TUser user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var userId = await UserManager.GetUserIdAsync(user);
var userName = await UserManager.GetUserNameAsync(user);
var id = new ClaimsIdentity( CookieAuthenticationDefaults.AuthenticationScheme,
Options.ClaimsIdentity.UserNameClaimType,
Options.ClaimsIdentity.RoleClaimType);
id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, userName));
//if (UserManager.SupportsUserSecurityStamp)
//{
// id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
// await UserManager.GetSecurityStampAsync(user)));
//}
if (UserManager.SupportsUserRole)
{
var roles = await UserManager.GetRolesAsync(user);
foreach (var roleName in roles)
{
id.AddClaim(new Claim(Options.ClaimsIdentity.RoleClaimType, roleName));
if (RoleManager.SupportsRoleClaims)
{
var role = await RoleManager.FindByNameAsync(roleName);
if (role != null)
{
id.AddClaims(await RoleManager.GetClaimsAsync(role));
}
}
}
}
if (UserManager.SupportsUserClaim)
{
id.AddClaims(await UserManager.GetClaimsAsync(user));
}
return new ClaimsPrincipal(id);
}
}
Here's the api that I'm calling. (nothing special)
[Authorize]
// Get the account profile information for the current user
[Route("api/[controller]")]
public class UserController : Controller
{
ILogger _logger;
private readonly UserManager<ApplicationUser> _userManager;
private ICompanyService _companyService;
private IUserService _userService;
public UserController(ILoggerFactory loggerFactory, UserManager<ApplicationUser> userManager,
ICompanyService companyService, IUserService userService)
{
_logger = loggerFactory.CreateLogger(this.GetType().FullName);
_userManager = userManager;
_companyService = companyService;
_userService = userService;
}
// GET: api/values
[HttpGet]
public async Task<User> Get()
{
User _acct = null;
var user = await _userManager.GetUserAsync(this.User);
if (user != null)
{
var company = await _companyService.GetCompany(user.CompanyId);
_acct = new User()
{
//AccountId = this.User.
FirstName = user.FirstName,
LastName = user.LastName,
JoinDate = user.JoinDate,
TermsOfAgreementDate = user.TermsOfAgreementDate,
};
if (company != null)
{
_acct.CompanyId = company.CompanyId;
_acct.CompanyName = company.CompanyName;
}
}
return _acct;
}
// PUT api/values/5
// [HttpPut("{id}")]
//public async Task Put(int id, [FromBody]DateTime agreedDate)
[HttpPut]
public async Task<bool> Put([FromBody]User acct)
{
//var user = await _userManager.GetUserAsync(this.User);
ClaimsPrincipal currentUser = this.User;
var user = await _userManager.FindByNameAsync(currentUser.Identity.Name);
var currentUserId = currentUser.FindFirst(ClaimTypes.NameIdentifier).Value;
User _acct = new User
{
UserId = currentUserId,
FirstName = user.FirstName,
LastName = user.LastName,
JoinDate = user.JoinDate,
CompanyId = user.CompanyId,
TermsOfAgreementDate = acct.TermsOfAgreementDate
};
try {
var result = await _userService.UpdateUser(_acct);
}
catch (Exception e)
{
_logger.LogError("Error found {0}", e.Message);
}
return true;
}
}
Logs when my /api/user is called.
Portal> info: OpenIddict.OpenIddictHandler[0]
Portal> The token response was successfully returned: {
Portal> "token_type": "Bearer",
Portal> "access_token": "[removed for security reasons]",
Portal> "expires_in": 3600,
Portal> "refresh_token": "[removed for security reasons]"
Portal> }.
Portal> info: OpenIddict.OpenIddictHandler[0]
Portal> The token response was successfully returned: {
Portal> "token_type": "Bearer",
Portal> "access_token": "[removed for security reasons]",
Portal> "expires_in": 3600,
Portal> "refresh_token": "[removed for security reasons]"
Portal> }.
Portal> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Portal> Executed action Portal.Controllers.Api.AuthorizationController.Exchange (Portal) in 15839.2511ms
Portal> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Portal> Executed action Portal.Controllers.Api.AuthorizationController.Exchange (Portal) in 15839.2511ms
Portal> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Portal> Request finished in 16289.7179ms 200 application/json;charset=UTF-8
Portal> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Portal> Request finished in 16289.7179ms 200 application/json;charset=UTF-8
Portal> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Portal> Request starting HTTP/1.1 GET http://localhost:49142/api/user/ application/json
Portal> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Portal> Request starting HTTP/1.1 GET http://localhost:49142/api/user/ application/json
Portal> info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Portal> Authorization failed for user: (null).
Portal> info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Portal> Authorization failed for user: (null).
Portal> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
Portal> Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Portal> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3]
Portal> Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Portal> info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Portal> Executing ChallengeResult with authentication schemes ().
Portal> info: Microsoft.AspNetCore.Mvc.ChallengeResult[1]
Portal> Executing ChallengeResult with authentication schemes ().
Portal> info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[12]
Portal> AuthenticationScheme: Identity.Application was challenged.
Portal> info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[12]
Portal> AuthenticationScheme: Identity.Application was challenged.
Portal> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Portal> Executed action Portal.Controllers.Api.UserController.Get (Portal) in 5947.5521ms
Portal> info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Portal> Executed action Portal.Controllers.Api.UserController.Get (Portal) in 5947.5521ms
Portal> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Portal> Request finished in 5979.075ms 302
Portal> info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Portal> Request finished in 5979.075ms 302
Update your authentication options to override the default schemes set by
services.AddIdentity()
:Alternatively, you can decorate your API controllers/actions with
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]