Reputation: 337
I am working on ASP.NET Core Web API. I am trying to create a custom Authorize
attribute but I am stuck. I could not understand what I am missing. I have the following code for the Authorize
attribute and filter:
public class AuthorizeAttribute : TypeFilterAttribute
{
public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
{
Arguments = new object[] { claim };
}
}
public class AuthorizeFilter : IAuthorizationFilter
{
readonly string[] _claim;
public AuthorizeFilter(params string[] claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
var claimsIndentity = context.HttpContext.User.Identity as ClaimsIdentity;
if (IsAuthenticated)
{
bool flagClaim = false;
foreach (var item in _claim)
{
if (context.HttpContext.User.HasClaim("Role", item))
flagClaim = true;
}
if (!flagClaim)
{
//if (context.HttpContext.Request.IsAjaxRequest())
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; //Set HTTP 403
//else
// context.Result = new RedirectResult("~/Login/Index");
}
}
else
{
//if (context.HttpContext.Request.IsAjaxRequest())
//{
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; //Set HTTP 401 -
//}
//else
//{
// context.Result = new RedirectResult("~/Login/Index");
//}
}
return;
}
}
I have copied this code from somewhere and commented unnecessary lines.
Here is my controller class where I am trying to put this:
[Route("api/[controller]/[action]")]
[ApiController]
[Authorize]
public class JobController : ControllerBase
{
// GET: api/<JobController>
[HttpGet]
[ActionName("GetAll")]
public List<Job> Get()
{
return JobDataLog.GetAllJobQueue();
}
// GET api/<JobController>/5
[HttpGet("{ID}")]
[ActionName("GetByID")]
public Job Get(Guid ID)
{
return JobDataLog.GetJob(ID);
}
// GET api/<JobController>/5
[HttpGet]
[ActionName("GetCount")]
public int GetCount()
{
return JobDataLog.GetJobTotal();
}
}
Also the Configure and ConfigureService methods of Startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(60);
});
var tokenKey = Configuration.GetValue<string>("TokenKey");
var key = Encoding.ASCII.GetBytes(tokenKey);
services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; })
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddSingleton<IJWTAuthenticationManager>(new JWTAuthenticationManager(tokenKey));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCookiePolicy();
app.UseSession();
app.Use(async (context, next) =>
{
var JWToken = context.Session.GetString("JWToken");
if (!string.IsNullOrEmpty(JWToken))
{
context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
}
await next();
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The problem is that even this controller has the Authorize
attribute, all the actions are being called even the Authorize
filter invalidates the authorization.
Also when I placed the following code in the OnAuthorization
method:
context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
It blocked the access of all actions, including those which have an AllowAnnoynmous
attribute.
Please help me, I have been stuck on this for last 3 hours.
Upvotes: 3
Views: 9909
Reputation: 3636
If you really want to use a custom AuthorizeAttribute
, here you go, this works. :)
You'll have a few squiggly lines, but VS will be able to automatically add the using
statements.
The original code had multiple problems:
Reponse.StatusCode
doesn't actually lead to a response being returned.HttpContext.User
wouldn't be populated in the first place, because ASP.NET Core only attempts to authenticate the user and populate the user's claims/identity if an endpoint is secured with the built-in AuthorizeAttribute
. The following code solves this by deriving from AuthorizeAttribute
.TypeFilterAttribute
and AuthorizeAttribute
, and the claims list would be always empty.public class MyAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
readonly string[] _requiredClaims;
public MyAuthorizeAttribute(params string[] claims)
{
_requiredClaims = claims;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var isAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
if (!isAuthenticated)
{
context.Result = new UnauthorizedResult();
return;
}
var hasAllRequredClaims = _requiredClaims.All(claim => context.HttpContext.User.HasClaim(x => x.Type == claim));
if (!hasAllRequredClaims)
{
context.Result = new ForbidResult();
return;
}
}
}
The reason why this works in such a crappy way is that the ASP.NET Core team doesn't want you to write custom Authorize Attributes. See this answer on the subject. The 'proper' way is to create policies, and assign your claim requirements to those policies. But I also think it's silly that authorization is so inflexible and lacking support for basic scenarios.
Upvotes: 5