Home > front end >  ASP.NET 5 Core User is in Role but [Authorize(Roles = "Admin")] returns Authorization fail
ASP.NET 5 Core User is in Role but [Authorize(Roles = "Admin")] returns Authorization fail

Time:03-09

I have been searching for an answer to this for a few days. So I figure it is time to seek help.

I found a similar problem but that fix does not work for me. Asp.net User is in Role but [Authorize(Roles = "Admin")] returns Authorization failed

I am using .Net 5 Core Api Basic Auth with Identity and Roles. I am able to auth fine but the problem comes when I try to use the [Authorize(Roles = "Admin")] attrib.

Using this shows me that the user is in the that role:

if (await _userManager.IsInRoleAsync(user, "Admin"))

Here is my Startup:

    public class Startup
{
    public static RoleManager<IdentityRole> roleManager;
    public static UserManager<PTPublicAPIUser> userManager;

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "PTPublicAPI", Version = "v1" });
        });

        services.AddDbContext<PTPublicAPIContext>(
    options => options.UseSqlServer("name=ConnectionStrings:PTPublicAPIContextConnection"));

        services.AddAuthentication().AddScheme<AuthenticationSchemeOptions, BasicAuthenticationIdentityHandler>("BasicAuthentication", options => { });
        services.AddAuthorization(options =>
        {
            options.AddPolicy("BasicAuthentication", new AuthorizationPolicyBuilder("BasicAuthentication").RequireAuthenticatedUser().Build());
        });

        // Configure Identity
        services.Configure<IdentityOptions>(options =>
        {
            // Password settings
            options.Password.RequireDigit = false;
            options.Password.RequiredLength = 8;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireUppercase = false;
            options.Password.RequireLowercase = false;
            options.Lockout.AllowedForNewUsers = false;
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, UserManager<PTPublicAPIUser> userManager, RoleManager<IdentityRole> roleManager)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "PTPublicAPI v1"));
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        IdentityDataInitializer.SeedData(userManager, roleManager);

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

When I try this I get 403 Forbidden

    [BasicAuthIdentity]
    [HttpGet]
    [Authorize(Roles = "Admin")]
    public IEnumerable<WeatherForecast> Get()

But this works

    [BasicAuthIdentity]
    [HttpGet]
    public IEnumerable<WeatherForecast> Get()

Edited: Added BasicAuthenticationIdentityHandler

public class BasicAuthenticationIdentityHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private readonly PTPublicAPIContext _publicAPIContext;
    private readonly UserManager<PTPublicAPIUser> _userManager;

    public BasicAuthenticationIdentityHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, PTPublicAPIContext publicAPIContext, UserManager<PTPublicAPIUser> userManager, ISystemClock clock) : base(options, logger, encoder, clock)
    {
        _publicAPIContext = publicAPIContext;
        _userManager = userManager;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        //Response.Headers.Add("WWW-Authenticate", "Basic");

        if (!Request.Headers.ContainsKey("Authorization"))
        {
            return AuthenticateResult.Fail("Authorization header missing.");
        }

        // Get authorization key
        var authorizationHeader = Request.Headers["Authorization"].ToString();
        var authHeaderRegex = new Regex(@"Basic (.*)");

        if (!authHeaderRegex.IsMatch(authorizationHeader))
        {
            return AuthenticateResult.Fail("Authorization code not formatted properly.");
        }

        var authBase64 = Encoding.UTF8.GetString(Convert.FromBase64String(authHeaderRegex.Replace(authorizationHeader, "$1")));
        var authSplit = authBase64.Split(Convert.ToChar(":"), 2);
        var authUsername = authSplit[0];
        var authPassword = authSplit.Length > 1 ? authSplit[1] : throw new Exception("Unable to get password");

        var user = _publicAPIContext.Users.FirstOrDefault(x => x.UserName == authUsername);

        if (user == null)
            return AuthenticateResult.Fail("The username or password is not correct.");

        bool passwordGood = await _userManager.CheckPasswordAsync(user, authPassword);

        if (!passwordGood)
            return AuthenticateResult.Fail("The username or password is not correct.");

        AuthenticatedUser authenticatedUser = new AuthenticatedUser("BasicAuthentication", true, authUsername);
        ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(authenticatedUser));

        return AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, Scheme.Name));
    }
}

Add here is AuthenticatedUser

    public class AuthenticatedUser : IIdentity
{
    public AuthenticatedUser(string authenticationType, bool isAuthenticated, string name)
    {
        AuthenticationType = authenticationType;
        IsAuthenticated = isAuthenticated;
        Name = name;
    }

    public string AuthenticationType { get; }

    public bool IsAuthenticated { get; }

    public string Name { get; }
}

CodePudding user response:

Generally, after adding the user in the role, the relevant claim will be automatically generated...But I infer that the cliam about role is not added to your User, you can add the following code in constructor in the controller to Check the related claims.

 public class HomeController : Controller
{
        private readonly IHttpContextAccessor _context;

        public HomeController(IHttpContextAccessor context)
        {
            //......
            _context = context;
            /Check the claims here
            var result = _context.HttpContext.User.Claims;
        }

        [BasicAuthIdentity]
        [HttpGet]
        [Authorize(Roles = "Admin")]
        public IEnumerable<WeatherForecast> Get(){}

}

If there is no claim like

{xxxxxx: Admin}

You can to add claims in login method by yourself

        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel model)
        {
            if (ModelState.IsValid)
            {
                var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password,isPersistent: false,lockoutOnFailure:false);
                if (result.Succeeded)
                {
                   //.....

                   //add the following code in login method to add the role claim 

                    IdentityUser currentuser = await _userManager.FindByNameAsync(Input.Email);
                    Claim claim = new Claim(ClaimTypes.Role, "Admin",ClaimValueTypes.String);
                    IdentityResult identityresult = await 
                    _userManager.AddClaimAsync(currentuser, claim);

                    //signin again and get the latest claims.
                    await _signInManager.SignInAsync(currentuser, false, null);
                    //......
                 }
             }

      }

Notice: This method will directly write the data to the AspNetUserClaims database, so when you need to change the relevant role, you need to delete the data in the database

CodePudding user response:

Ok guys thanks for all the responses. I got it working. This is what I did.

In the BasicAuthenticationIdentityHandler.HandleAuthenticateAsync I changed this

        AuthenticatedUser authenticatedUser = new AuthenticatedUser("BasicAuthentication", true, authUsername);
        ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(authenticatedUser));

        return AuthenticateResult.Success(new AuthenticationTicket(claimsPrincipal, Scheme.Name));

to this:

        var principal = await _claimsPrincipalFactory.CreateAsync(user);
        var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name);

        return AuthenticateResult.Success(ticket);

Here is the class now

public class BasicAuthenticationIdentityHandler : AuthenticationHandler<AuthenticationSchemeOptions>{
private readonly IUserClaimsPrincipalFactory<PTPublicAPIUser> _claimsPrincipalFactory;
private readonly PTPublicAPIContext _publicAPIContext;
private readonly UserManager<PTPublicAPIUser> _userManager;

public BasicAuthenticationIdentityHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, PTPublicAPIContext publicAPIContext, UserManager<PTPublicAPIUser> userManager, ISystemClock clock, IUserClaimsPrincipalFactory<PTPublicAPIUser> claimsPrincipalFactory) : base(options, logger, encoder, clock)
{
    _publicAPIContext = publicAPIContext;
    _userManager = userManager;
    _claimsPrincipalFactory = claimsPrincipalFactory;
}

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
    //Response.Headers.Add("WWW-Authenticate", "Basic");

    if (!Request.Headers.ContainsKey("Authorization"))
    {
        return AuthenticateResult.Fail("Authorization header missing.");
    }

    // Get authorization key
    string authorizationHeader = Request.Headers["Authorization"].ToString();
    Regex authHeaderRegex = new Regex(@"Basic (.*)");

    if (!authHeaderRegex.IsMatch(authorizationHeader))
    {
        return AuthenticateResult.Fail("Authorization code not formatted properly.");
    }

    string authBase64 = Encoding.UTF8.GetString(Convert.FromBase64String(authHeaderRegex.Replace(authorizationHeader, "$1")));
    string[] authSplit = authBase64.Split(Convert.ToChar(":"), 2);
    string authUsername = authSplit[0];
    string authPassword = authSplit.Length > 1 ? authSplit[1] : throw new Exception("Unable to get password");

    var user = _publicAPIContext.Users.FirstOrDefault(x => x.UserName == authUsername);

    if (user == null)
        return AuthenticateResult.Fail("The username or password is not correct.");

    bool passwordIsValid = await _userManager.CheckPasswordAsync(user, authPassword);

    if (!passwordIsValid)
        return AuthenticateResult.Fail("The username or password is not correct.");

    ClaimsPrincipal principal = await _claimsPrincipalFactory.CreateAsync(user);
    AuthenticationTicket ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name);

    return AuthenticateResult.Success(ticket);
}

}

  • Related