We can implement this feature by adding custom BasicAuthenticationHandler.
Here are the detailed steps - BasicAuthenticationHandler.cs:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
namespace _79547481
{
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly UserManager<IdentityUser> _userManager;
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
UserManager<IdentityUser> userManager)
: base(options, logger, encoder, clock)
{
_userManager = userManager;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue("Authorization", out var authHeader))
return AuthenticateResult.NoResult();
if (!authHeader.ToString().StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
return AuthenticateResult.NoResult();
try
{
var encodedCredentials = authHeader.ToString()["Basic ".Length..].Trim();
var decodedBytes = Convert.FromBase64String(encodedCredentials);
var decodedCredentials = Encoding.UTF8.GetString(decodedBytes);
var separatorIndex = decodedCredentials.IndexOf(':');
if (separatorIndex < 0)
return AuthenticateResult.Fail("Invalid credentials format");
var username = decodedCredentials[..separatorIndex];
var password = decodedCredentials[(separatorIndex + 1)..];
var user = await _userManager.FindByNameAsync(username);
if (user == null || !await _userManager.CheckPasswordAsync(user, password))
return AuthenticateResult.Fail("Invalid username or password");
var roles = await _userManager.GetRolesAsync(user);
if (!roles.Contains("APIRole"))
return AuthenticateResult.Fail("Access denied");
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id),
new(ClaimTypes.Name, user.UserName),
new(ClaimTypes.Role, "APIRole")
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
}
catch
{
return AuthenticateResult.Fail("Authentication error");
}
}
}
}
Program.cs:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using _79547481.Data;
using _79547481;
using Microsoft.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>() //Add this line to fix the error message `Store does not implement IUserRoleStore<TUser>.`
.AddEntityFrameworkStores<ApplicationDbContext>();
// Add BasicAuthentication
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
})
.AddCookie()
.AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiAccess", policy =>
policy.RequireAuthenticatedUser()
.RequireRole("APIRole"));
});
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
Test code in controller:
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using _79547481.Models;
using Microsoft.AspNetCore.Authorization;
namespace _79547481.Controllers;
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private readonly ILogger<DataController> _logger;
public DataController(ILogger<DataController> logger)
{
_logger = logger;
}
[HttpGet("secure")]
[Authorize(AuthenticationSchemes = "BasicAuthentication", Policy = "ApiAccess")]
public IActionResult GetSecureData()
{
return Ok(new { data = "Sensitive information" });
}
}
Test result:

AuthorizeAttribute(String)