|
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. |