'returns 403(forbidden) when using authentication scheme in .net core
I am doing authentication in my web application with JWT
Security Tokens and a custom authentication scheme.
I'm generating tokens when a user login
I created an authentication handler where I validate tokens for all requests
Authentication Handler
public class CustomAuthenticationHandler : AuthenticationHandler<CustomAuthenticationOptions>
{
public CustomAuthenticationHandler(
IOptionsMonitor<CustomAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
Exception ex;
var key = Request.Headers[Options.HeaderName].First();
if (!IsValid(key, out ex))
{
return Task.FromResult(AuthenticateResult.Fail(ex.Message));
//filterContext.Result = new CustomUnauthorizedResult(ex.Message);
}
else
{
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(),new AuthenticationProperties(),this.Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
catch (InvalidOperationException)
{
return Task.FromResult(AuthenticateResult.Fail(""));
}
}
}
Custom Authentication Extension
public static class CustomAuthenticationExtensions
{
public static AuthenticationBuilder AddCustomAuthentication(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<CustomAuthenticationOptions> configureOptions)
{
return builder.AddScheme<CustomAuthenticationOptions, CustomAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
}
}
Integrate Custom Authentication into Startup
public class Startup
{
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.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Person>());
services.AddTransient<IRepositoryWrapper, RepositoryWrapper>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(options=> {
options.DefaultScheme = "CustomScheme";
options.DefaultAuthenticateScheme = "CustomScheme";
options.DefaultChallengeScheme = "CustomScheme";
}).AddCustomAuthentication("CustomScheme", "CustomScheme", o => { });
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug(LogLevel.Information);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseMvc();
}
}
Use Authentication scheme within Controller
[Authorize(AuthenticationSchemes ="CustomScheme")]
[ApiController]
[Route("api/controller")]
public class UserController : BaseController
{
public UserController(IRepositoryWrapper repository) : base(repository)
{
}
[HttpGet]
public IEnumerable<Users> Get()
{
return _repository.Users.FindAll();
}
}
When I call the API from Postman with a valid token it returns a 403
error.
Please help to solve this...!!
Solution 1:[1]
I found the solution.
I used CustomAuthenticationMiddle
class instead of AuthenticationHandler
class
public class CustomAuthenticationMiddleware
{
private readonly RequestDelegate _next;
public CustomAuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
Exception ex;
var key = context.Request.Headers["Authorization"].First();
if (!IsValid(key))
{
//logic for authentication
}
else
{
await _next.Invoke(context);
}
}
catch (InvalidOperationException)
{
context.Response.StatusCode = 401; //Unauthorized
return;
}
}
private bool IsValid(string key)
{
//Code for checking the key is valid or not
}
}
Replace All above Code for Integrate Custom Authentication Handler with the next line under Startup
class
app.UseMiddleware<CustomAuthenticationMiddleware>();
Solution 2:[2]
For anyone else running into this problem:
The original issue appears to be that a ClaimsIdentity was not passed to the ClaimsPrincipal when returning the AuthenticationResult in AuthenticationHandler. Note also that the authentication type must be passed to the ClaimsIdentity, or IsAuthenticated will be false. Doing something like this within the AuthenticationHandler should fix the issue:
else
{
var claimsPrincipal = new ClaimsPrincipal();
var claimsIdentity = new ClaimsIdentity("JWT");
claimsPrincipal.AddIdentity(claimsIdentity);
var ticket = new AuthenticationTicket(claimsPrincipal, this.Scheme.Name);
return AuthenticateResult.Success(ticket);
}
Solution 3:[3]
I would like to Explain the two answers to this Question as Both are correct.
About First Answer By @mskowron:
Is the solution for 403 Error in case using the same Custom Authentication Handler Implementation under the Question as questioner missing for define Authentication Schema type under ClaimIdentity
instance initialization, Here is a full implementation of CustomAuthenticationHandler
class with fix bug
public class CustomAuthenticationHandler : AuthenticationHandler<CustomAuthenticationOptions>
{
public CustomAuthenticationHandler(
IOptionsMonitor<CustomAuthenticationOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock)
: base(options, logger, encoder, clock)
{
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
try
{
var key = Request.Headers[Options.HeaderName].First();
Exception vaildationException;
if (!IsValid(key, out vaildationException))
{
return Task.FromResult(AuthenticateResult.Fail(vaildationException.Message));
}
else
{
// ----->> Here Changes
var claims = new[] {}; // define claims what's you need
var identity = new ClaimsIdentity(claims, "JWT");
var claimsPrincipal = new ClaimsPrincipal(identity);
AuthenticationTicket ticket = new AuthenticationTicket(claimsPrincipal, this.Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
catch (InvalidOperationException exception)
{
return Task.FromResult(AuthenticateResult.Fail(""));
}
}
}
Here is An article for the same solution, But for Basic
Authentication
About Second Answer By Who Ask Question:
Is another Way to Implement A Custom Authentication Different from an already exists in Question By depends on implementing a custom Authentication Middleware instead of depending on the default Dotnet Core Authentication Middleware.
Here is An Article for this solution approach, But for Basic
Authentication.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | ahmed hamdy |
Solution 2 | mskowron |
Solution 3 | ahmed hamdy |