最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How do I add basic authentication on certain API calls in an ASP.NET Core website using Identity - Stack Overflow

programmeradmin4浏览0评论

I have an ASP.NET Core web application that is using Identity for authentication and authorization. This allows me to register users easily in the website and it all works as expected.

However, the website also publishes a number of API endpoints that I would like to make available using basic authentication, where that basic authentication uses the same database of users/password hashes that are stored against the main website, but controlled by a specific role within Identity.

So I'd assign that role to certain users and they could access the API through basic authentication. The reason for this is that the client that accesses these API's can only use basic authentication and nothing else.

How would I go about adding basic authentication to these specific endpoints, but leave the rest of the website as is?

I have an ASP.NET Core web application that is using Identity for authentication and authorization. This allows me to register users easily in the website and it all works as expected.

However, the website also publishes a number of API endpoints that I would like to make available using basic authentication, where that basic authentication uses the same database of users/password hashes that are stored against the main website, but controlled by a specific role within Identity.

So I'd assign that role to certain users and they could access the API through basic authentication. The reason for this is that the client that accesses these API's can only use basic authentication and nothing else.

How would I go about adding basic authentication to these specific endpoints, but leave the rest of the website as is?

Share Improve this question edited Apr 1 at 3:55 marc_s 756k184 gold badges1.4k silver badges1.5k bronze badges asked Apr 1 at 0:29 mxcolinmxcolin 1159 bronze badges 1
  • You could try adding some policy that requires certain role, and then requiring this policy in those endpoints by using AuthorizeAttribute(String) – Michał Turczyn Commented Apr 1 at 10:13
Add a comment  | 

1 Answer 1

Reset to default 1

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:

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论