Asp.net-core – Net Core 2.0 API setting up authorization and authentication

asp.netasp.net-coreasp.net-core-2.0

I have a .net core application that I have upgraded from 1.1 to 2.0.
The problem I am having is working out how to set up both authentication and authorization.

I am getting this exception when I am trying to hit an api endpoint…

2017-08-15 15:28:12.2191|13|Microsoft.AspNetCore.Server.Kestrel|ERROR|
Connection id "0HL73T7CAJGBE", Request id "0HL73T7CAJGBE:00000001": An
unhandled exception was thrown by the application. No
authenticationScheme was specified, and there was no
DefaultChallengeScheme found.

My controller has this attribute on it…

[Authorize(Policy = "Viewer3AuthPolicy")]

My startup.cs has this method to try and set everything up…

   public void ConfigureServices(IServiceCollection services)
    {
    SetCorsPolicy(services);

    services.AddMvc();

    services.AddAuthentication(o =>
    {
        o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    });

    SetAuthorisationPolicy(services);

    services.AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build();
        options.AddPolicy("Viewer3AuthPolicy",
            policy => policy.RequireClaim(Constants.AuthPolicyClaimsName, Constants.AuthPolicyClaimsValue));
    });
}

In my Configuremethod I am calling…

app.UseAuthentication();

I am thinking that I must have some ordering wrong or am making the wrong calls in the setup.

Does anyone have any ideas?

Best Answer

Solution with _userManager.AddClaimsAsync. Here is the simplified version of changes I made under ConfigureServices:

services.AddAuthorization(options => {       
    options.AddPolicy("CRM", policy => { policy.RequireClaim("department", "Sales", "Customer Service", "Marketing", "Advertising", "MIS"); });
});

AccountController constructor:

    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IEmailSender _emailSender;
    private readonly ILogger _logger;

    private readonly MyDB_Context _context;

    public AccountController(
        MyDB_Context context,
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ILogger<AccountController> logger)
    {
        _context = context;
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
        _logger = logger;
    }

Under LogIn: (var vUser is my own class with properties Name, department, SingIn, etc...). The sample below uses combination of custom user table mytable (to read from claim types and their values) and AspNetUserClaims table (to add claims):

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid) {
        var vUser = _context.mytable.SingleOrDefault(m => m.Email.ToUpper() == model.Email.ToUpper());

        const string Issuer = "https://www.mycompany.com/";
        var user = _userManager.Users.Where(u => u.Email == model.Email).FirstOrDefault();

        ApplicationUser applicationUser = await _userManager.FindByNameAsync(user.UserName);
        IList<Claim> allClaims = await _userManager.GetClaimsAsync(applicationUser); // get all the user claims

        // Add claim if missing
        if (allClaims.Where(c => c.Type == "department" && c.Value == vUser.department).ToList().Count == 0) {
            await _userManager.AddClaimAsync(user, new Claim("department", vUser.department, ClaimValueTypes.String, Issuer));
        }
        // Remove all other claim values for "department" type
        var dept = allClaims.Where(c => c.Type == "department" && c.Value != vUser.department);
        foreach(var claim in dept) {
            await _userManager.RemoveClaimAsync(user, new Claim("department", claim.Value, ClaimValueTypes.String, Issuer));
        }

        vUser.SignIn = DateTime.Now; _context.Update(vUser); await _context.SaveChangesAsync();

        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(vUser.Name, model.Password, model.RememberMe, lockoutOnFailure: false);
        //var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded) {
            _logger.LogInformation("User logged in.");
            return RedirectToLocal(returnUrl);
        }
        if (result.RequiresTwoFactor) {
            return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
        }
        if (result.IsLockedOut) {
            _logger.LogWarning("User account locked out.");
            return RedirectToAction(nameof(Lockout));
        } else {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return View(model);
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

This is what I have in my controller:

[Authorize(Policy = "CRM")]
public class CRMController : Controller