We have an application that uses ASP.net Membership to provide the basic login mechanisms. It all works fine, but recently we discovered that if you try to go to the login page whilst logged in you are redirected to the 'Unauthorized' page.
Example user flow.
User goes to secured page (whole application requires login, there's not even a home page you can visit, just redirects straight to login). This redirects them to https://www.example.com/Account/Login .
User logs in and is redirected to home page https://www.example.com/ . They are logged in and everything works fine.
User clicks a bookmark that happens to be set to https://www.example.com/Account/Login
User is redirected to generic Unauthorized page.
I have the <Authorize()>
attribute on my AccountController but the <AllowAnonymous()>
attribute on the 'Login' action, which as we saw earlier, works fine when you are not logged in, but when you are it seems to get in a bit of a muddle.
AccountController
<Authorize()> _
Public Class AccountController
'''other functions go here'''
<AllowAnonymous()> _
Public Function Login(ByVal returnUrl As String) As ActionResult
ViewData("ReturnUrl") = returnUrl
Return View()
End Function
AuthorizeRedirect filter
<AttributeUsage(AttributeTargets.[Class] Or AttributeTargets.Method)> _
Public Class AuthorizeRedirect
Inherits AuthorizeAttribute
Private Const IS_AUTHORIZED As String = "isAuthorized"
Public RedirectUrl As String = "~/Home/Unauthorized"
Protected Overrides Function AuthorizeCore(httpContext As System.Web.HttpContextBase) As Boolean
Dim isAuthorized As Boolean = MyBase.AuthorizeCore(httpContext)
httpContext.Items.Add(IS_AUTHORIZED, isAuthorized)
Return isAuthorized
End Function
Public Overrides Sub OnAuthorization(filterContext As AuthorizationContext)
MyBase.OnAuthorization(filterContext)
Dim isAuthorized = If(filterContext.HttpContext.Items(IS_AUTHORIZED) IsNot Nothing, Convert.ToBoolean(filterContext.HttpContext.Items(IS_AUTHORIZED)), False)
If Not isAuthorized AndAlso filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated Then
filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl)
End If
End Sub
End Class
Seeing all this I thought the simplest solution would be to check if the user is already logged in on my Login action and redirect them away myself, something like this.
<AllowAnonymous()> _
Public Function Login(ByVal returnUrl As String) As ActionResult
If User.Identity.IsAuthenticated() Then
Return RedirectToAction("Index", "Home")
End If
ViewData("ReturnUrl") = returnUrl
Return View()
End Function
But the AuthorizeFilter always jumps in the way first, which is understandable, but I can't quite figure out the last missing piece. All I want is it to not show the 'You don't have permission to view this page' if the user goes to the login screen when logged in, and rather redirect them to home page. What am I missing?
Edit to make things a bit clearer
When already logged in, I go to /Account/Login
. This 302
redirects me to /Home/Unauthorized
(my custom page). However, I am still logged in.
Network requests
Unauthorized page. Notice the highlighted yellow sections show I am still logged in. This only appears if you are logged in. When not logged in you get none of that.
The problem seems to be that the application does not know what to do when I am already logged in and trying to go to a page that has the [AllowAnonymous]
attribute on it. If anything, the behaviour I am seeing here is preferable to it actually giving me a login page again, because that would be confusing, but still, its not ideal.
Edit 2 - Stepping through the code line by line
Here are the results of stepping through the code line by line.
Page /Account/Login
whilst logged in.
First breakpoint in OnAuthorization
sub in AuthorizeRedirect
filter.
Public Overrides Sub OnAuthorization(filterContext As AuthorizationContext)
MyBase.OnAuthorization(filterContext)
Dim isAuthorized = If(filterContext.HttpContext.Items(IS_AUTHORIZED) IsNot Nothing, Convert.ToBoolean(filterContext.HttpContext.Items(IS_AUTHORIZED)), False)
If Not isAuthorized AndAlso filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated Then
filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl)
End If
End Sub
Line starting with Dim isAuthorized
returns False. filterContext.HttpContext.Items(IS_AUTHORIZED)
is nothing (does not exist in list of items).
This then means the next If statement evaluates to True (Not isAuthorized AndAlso ...IsAuthenticated), resulting in the redirect to RedirectUrl
.
After this happens it appears to go back over the same steps, except this time it evaluates to false, meaning the redirect doesn't occur, although I'm guessing this is just the 'Unauthorized' page loading and running through the same code again.
I attempted to add the following block to the top of the Login
function of the AccountController
.
If User.Identity.IsAuthenticated() Then
Return RedirectToAction("Index", "Home")
End If
But, of course, since the filter is run before the actions occur this code isn't hit until after it's already redirected me to Unauthorized
(verified by stepping through).
The base class for AuthorizationAttribute has this code in its OnAuthorization
method:
bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
if (skipAuthorization)
{
return;
}
if (AuthorizeCore(filterContext.HttpContext))
// ...
As such if the controller action has an AllowAnonymousAttribute
defined upon it, your AuthorizeCore
method will not get called.
Because of that filterContext.HttpContext.Items(IS_AUTHORIZED)
will never get set.
You could simply copy the code from here to implement OnAuthorization
without calling the base class. This way you could deal with the caching in which ever way you want.
Incidentally, I was under the impression that if authorization failed that a later process in the request pipeline did a redirect to the login page anyway. This is why the base implementation of OnAuthorization
sets the filterContext.Result
to a new HttpUnauthorizedResult
instance. So it is not entirely clear why you are overriding OnAuthorization
and doing the redirect in the first place. If you want some kind of custom authorization code simply returning true
or false
from AuthorizeCore
ought to be enough.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.