Skip to content

Instantly share code, notes, and snippets.

@daltonnyx
Created November 13, 2019 16:22
Show Gist options
  • Save daltonnyx/f25babbcd4561d1e1d3c9c6fa56bdafa to your computer and use it in GitHub Desktop.
Save daltonnyx/f25babbcd4561d1e1d3c9c6fa56bdafa to your computer and use it in GitHub Desktop.
Custom Redirect for Authorization Policy Failure in .Net Core 2.1
Hello everyone, I spent a good deal of time on this problem yesterday and wanted to share my solution as it might help others and also if someone else has a better way of doing this.
Problem: Need a custom redirect for an Authorization Policy Failure (Claims in my case). Do not want to go to default "Access Denied" page.
Why: In my case, I am building a SAAS application and storing a user's monthly expiration time in a claim. For example, if you sign up today, your access is good for 1 month. For every month you renew your subscription, 1 month is added to that "expiration date" claim. Every time you access the application, I check whether the user's access has expired or not. If their access has expired, I want to redirect to the Renewal page, instead of the Access Denied page.
Solution: After searching through the docs: I came across this: https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1
This gets you started on how Authorization Policies work and how to create custom ones. However, it does not say anything about custom redirects. After a little searching, I came up with the following solution.
public class ActiveUserRequirement : IAuthorizationRequirement
{
public DateTime TodayUTC { get; set; }
public ActiveUserRequirement()
{
TodayUTC = DateTime.UtcNow;
}
}
public class ActiveUserHandler : AuthorizationHandler<ActiveUserRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ActiveUserRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.Expiration))
{
return Task.CompletedTask;
}
var expirationClaim = context.User.FindFirstValue(ClaimTypes.Expiration);
if (expirationClaim != null)
{
DateTime expiryDate;
bool success = DateTime.TryParse(expirationClaim, out expiryDate);
if (success)
{
if (expiryDate > requirement.TodayUTC)
{
context.Succeed(requirement);
}
else // If user's account expiration is here, redirect to renewal page
{
var authFilterContext = context.Resource as AuthorizationFilterContext;
authFilterContext.Result = new RedirectToActionResult("Renewal", "Home", null);
context.Succeed(requirement);
}
}
}
return Task.CompletedTask;
}
}
And in startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("Active", policy => policy.Requirements.Add(new ActiveUserRequirement()));
});
services.AddSingleton<IAuthorizationHandler, ActiveUserHandler>();
And then on my individual controllers, I apply this policy as follows:
[Authorize(Policy = "Active")]
public class TestController : Controller
Anyways, I was scratching my head over this, and thought I'd like to share. Please pick this apart if you have a better solution.
Thanks!
Edit - I want to note that an alternative to the Auth Policy solution is to create a custom AuthorizeAttribute, example here. However, the general consensus seems to be that you shouldn't create custom AuthorizeAttributes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment