I am adding a custom code to AuthorizeAttribute
that routes a user to an action in another controller that displays a modal popup when they have not verified their phone number.
For some reason, the code just redirects forever that the web site just says:
redirected you too many times
And does not hit my breakpoint that I put in the VerifyPhoneNumber
Action.
Can you suggest why this happens? TIA.
Here is my custom overridden AuthorizeAttribute
class far below. Everything here has been here before, I just added:
if (CustomContext.AccountDomainType == AccountDomainType.ActiveDirectory &&
CustomContext.IsPhoneNumberConfirmed == false)
{
return false;
}
And:
if (CustomContext.AccountDomainType == AccountDomainType.ActiveDirectory &&
CustomContext.IsPhoneNumberConfirmed == false)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "action", "VerifyPhoneNumber" },
{ "controller", "Account" }
});
return;
}
CustomAuthorizeAttribute
class:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public Module Module { get; set; }
public Permission Permission { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var CustomContext = (Context)httpContext.Items[Context.HttpContextItemName];
var token = httpContext.Request.Form["token"];
if (!base.AuthorizeCore(httpContext) && token == null)
{
return false;
}
else if (token != null)
{
var tokenRepository = new AuthenticationTokenRepository(CustomContext);
var authenticationToken = tokenRepository.Get(token);
if (authenticationToken == null)
{
CustomContext.Log("Permission denied because authentication token was not valid");
return false;
}
var authenticationManager = httpContext.GetOwinContext().Authentication;
var userManager = httpContext.GetOwinContext().Get<AspNetUserManager>();
AspNetUser user;
if (authenticationToken.Role == AuthenticationTokenRole.PdfGenerator)
{
user = userManager.FindByName<AspNetUser, Guid>(authenticationToken.CreatedBy);
if (user == null)
{
CustomContext.Log("Permission denied because token creation user was not found");
return false;
}
}
else
{
return false;
}
var userIdentity = ApplicationSignInManager.GenerateUserIdentity(userManager, user, DefaultAuthenticationTypes.ApplicationCookie);
authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = false }, userIdentity);
if (!authenticationManager.User.Identity.IsAuthenticated)
{
authenticationManager.User = authenticationManager.AuthenticationResponseGrant.Principal;
}
}
if (token == null && !CustomContext.IsDeviceAuthorized())
{
CustomContext.UpdateDevice();
httpContext.GetOwinContext().Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.TwoFactorCookie, DefaultAuthenticationTypes.ApplicationCookie);
CustomContext.Log("Permission denied and forced sign out because device was already signed out based on cookie check");
return false;
}
if (Module != default(Module) && Permission != default(Permission) && !CustomContext.HasPermission(Module, Permission))
{
CustomContext.Log("Permission denied because user does not have permission to " + Module.DisplayName() + ", " + Permission.DisplayName());
return false;
}
if (Module != default(Module) && Permission == default(Permission) && !CustomContext.HasModule(Module))
{
CustomContext.Log("Permission denied because company does not have the following module features turned on " + Module.DisplayName());
return false;
}
if (CustomContext.IsPasswordExpired
&& httpContext.Request.RequestContext.RouteData.Values.ContainsKey("action")
&& "ChangePassword" != httpContext.Request.RequestContext.RouteData.Values["action"].ToString()
&& "ChangePasswordLink" != httpContext.Request.RequestContext.RouteData.Values["action"].ToString())
{
CustomContext.Log("Permission denied because password is expired. Password must be changed first");
return false;
}
if (Module == default(Module) && Permission != default(Permission))
{
throw new Exception("No module specified for permission check");
}
if (CustomContext.AccountDomainType == AccountDomainType.ActiveDirectory &&
CustomContext.IsPhoneNumberConfirmed == false)
{
return false;
}
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
var actionParams = filterContext.ActionDescriptor.GetParameters();
var paramTypes = actionParams.Select(ap => ap.ParameterType).ToArray();
var actionMethod = filterContext.Controller.GetType()
.GetMethod(filterContext.ActionDescriptor.ActionName, paramTypes);
var CustomContext = (Context)filterContext.HttpContext.Items[Context.HttpContextItemName];
var errorTypeViewPrefix = CustomContext.IsPasswordExpired ? "PasswordExpired" : "PermissionDenied";
if (CustomContext.AccountDomainType == AccountDomainType.ActiveDirectory &&
CustomContext.IsPhoneNumberConfirmed == false)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "action", "VerifyPhoneNumber" },
{ "controller", "Account" }
});
return;
}
if (actionMethod != null && (actionMethod.ReturnType == typeof(PartialViewResult) ||
actionMethod.ReturnType == typeof(Task<PartialViewResult>)))
{
// Partial views
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "action", errorTypeViewPrefix + "Partial" },
{ "controller", "Error" }
});
return;
}
// Full page views
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "action", errorTypeViewPrefix },
{ "controller", "Error" }
});
return;
}
base.HandleUnauthorizedRequest(filterContext);
}
}
}
The action I am trying to execute that sends a text message code to user and opens a popup window (it exists in another controller):
[Display(Name = "Verify Phone Number", Description = "Verify Phone Number")]
public async Task<PartialViewResult> VerifyPhoneNumber()
{
var strictPhoneAttr = new StrictPhoneAttribute();
if (!strictPhoneAttr.IsValid(Context.PhoneNumber))
{
ModelState.AddModelError(string.Empty, "Please, change your phone number to a valid cell phone number under the menu in the top right-hand corner under your name. A valid cell phone number is required for two-factor authentication used by this system.");
}
else
{
/....code that send SMS code ......./
Context.Log("Opened Verify Phone Number modal");
return PartialView("_VerifyPhoneNumber", new VerifyPhoneNumberModel());
}
2
Answers
Make sure that when the user is redirected to the
VerifyPhoneNumber
action, they are not automatically redirected again to the original action (due to the same condition being evaluated), i think you could add some logging to improve dubugging.You might want to change the logic to update the
CustomContext.IsPhoneNumberConfirmed
once the phone number is successfully verified, because if the modal does not allow the user to resolve the issue, they will keep being redirected.You can modify your
AuthorizeCore
method to check if the current action isVerifyPhoneNumber
. If so, you can skip the redirect:HandleUnauthorizedRequest
method, you are not creating multiple conditions that lead to the same redirect.AuthorizeCore
method that avoids redirect loops:It is not appropriate to use redirect inside AuthorizeAttribute because it is supposed only to check Authorize and return results,and also it may have redirected after checking Authorization.
why you don’t use: the action filter attribute
for more instances:
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs