Forms Authentication: How to handle unauthorized authenticated user - c#

I am trying to setup a very basic Forms authentication example.
It is correctly redirecting unauthenticated users to the login page
and on submit verifying the credentials and if correct calling:
FormsAuthentication.RedirectFromLoginPage(username.Text, false);
If the user is one named in the authorization section they get their page.
If not it bounces them back to the login page with no error.
How can I redirect correctly authenticated but unauthorized users to a specific error page or detect the authorization error to display an error message on the login page bounce back?
Here is my web.config
<authentication mode="Forms">
<forms name=".ASPXAUTH" loginUrl="/forms/Login" />
</authentication>
<authorization>
<deny users="?" />
<allow users="username1, username2" />
<deny users="*" />
</authorization>
Update:
Based on the answers / comments / research I've got two working solutions.
Put the following in the Page_Load method of your Login form:
if (Request.IsAuthenticated && !string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]))
{
// This is an unauthorized, authenticated request...
Response.Redirect("FailedAuthorization.aspx");
}
OR
Put the following in your Global.aspx file:
protected void Application_EndRequest(object sender, EventArgs e)
{
if (Response.StatusCode == 401)
{
//Use the built in 403 Forbidden response
Response.StatusCode = 403;
//OR redirect to custom page
//Response.Redirect("FailedAuthorization.aspx");
}
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Request.IsAuthenticated)
{
// Requires ASP.NET >= 4.5
Response.SuppressFormsAuthenticationRedirect = true;
}
}
Thank you for all the help with this!

Unfortunately, this is one of those things that ASP.NET continually gets wrong. Even though MS and the .NET framework team full well understand the difference between authentication and authorization, they still insist on treating unauthorized as unauthenticated. I don't know why that is.
This is just a quirk of the FormsAuthentication module handler, in that it returns a 401 Unauthorized instead of a 403 Forbidden. (it doesn't help that the HTTP standard confuses Authentication with authorization as well in this manner).
This is not something you can easily override, so your only recourse would be to do something like checking in your Login page to see if they are already logged in, and if they were redirected... it's not foolproof, but it's one way to handle it.
You don't say what version of .NET you're using, but if you are using .net 4.5 then you have another option, which is to use the SuppressFormsAuthenticationRedirect option as in this article:
Forms authentication: disable redirect to the login page

2 checks: if they're authenticated && if there is a return url (which will be there if sent to the log-in page).
if (Request.IsAuthenticated && !string.IsNullOrEmpty(Request.QueryString["ReturnUrl"]))
{
// This is an unauthorized, authenticated request...
Response.Redirect("~/somewhere.aspx");
}

The Unauthorized redirect Status Code is 302 but this overrides with status 200 when it's redirected to the login page.
In order to redirect the user to Unauthorize Page rather than to the login page, the Hack is to implement Application_EndRequest in Global and check for Response Status Code 302, which is a temporary redirect from the current called to action.
protected void Application_EndRequest(object sender, EventArgs e)
{
if(HttpContext.Current.Response.StatusCode == 302 && User.Identity.IsAuthenticated)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Redirect("/UnauthorizedPageUrl");
}
}

Related

ASP.NET FormsAuthenticationModule DefaultUrl overriding explicit

I have a mature ASP.NET web application using FormsAuthentication (FA) to manage logins. Under certain situations, I would like to redirect the "just logged in" user to a different URL to the one that FA uses. As per standard functionality, FA will redirect to our normal homepage (specified in web.config) unless a redirectUrl was used when it hits a page that requires an authenticated user.
In my system, after the user's username/password is validated I typically use
FormsAuthentication.RedirectFromLoginPage(userName, createPersistentCookie: true); // Also calls SetAuthCookie()
which handles most situations. However, depending on certain conditions (primarily based on the newly logged in user's role) I want to redirect to a different destination. My thoughts for doing this are to call SetAuthCookie() myself and then use Response.Redirect(myUrl, false); and ApplicationInstance.CompleteRequest().
Despite doing this, the very next request comes in using for the URL defined in my tag of web.config.
<authentication mode="Forms">
<forms loginUrl="~/Login" timeout="120" cookieless="UseCookies" defaultUrl="~/?raspberry=true" />
</authentication>
Here is the actual code I am using (if a different url is required, it is specified by the overrideUrl parameter:
internal static void CreateTicket(string userName, string overrideUrl)
{
// Ref: http://support.microsoft.com/kb/301240
if (overrideUrl == null)
{
FormsAuthentication.RedirectFromLoginPage(userName, createPersistentCookie: true); // Includes call to SetAuthCookie()
}
else
{
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie: true, strCookiePath:FormsAuthentication.FormsCookiePath);
HttpContext.Current.Response.Redirect(overrideUrl, false);
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
If I pass in a value of /special/path for overrideUrl I would like the next request to come in to be '/special/path'. Instead I am seeing /?raspberry=true
Is something else forcing defaultUrl?
Is there a way to "snoop" into the Response object while debugging to see if a Redirect is already in place? or set a breakpoint whenever it gets set so I can look at the call stack?
EDIT: At the end of my method, the Response object is showing the following properties:
RedirectLocation: "/special/path"
Status: "302 Found"
StatusCode: 302
StatusDescription: "Found"
IsRequestBeingRedirected: true
HeadersWritten: false
which all looks absolutely correct.
Thanks for any advise.
Okay, I can see what is causing it. It is the fact that I am using the Login Web Control and running my code as part of the Authenticate event. Looking at the reference source for the Login Web Control, the Authenticate event is raised by its AttemptLogin method (search for it in ref source). After raising the event and seeing that Authentication was successful, it then goes on to:
Call SetAuthCookie itself (I've already done this myself but presumably the only thing I should be doing in my code is determining if authentication was successful or not, and not messing with AuthCookie or redirects)
Performing a Redirect (overwriting my carefully crafted Redirect)
I'm going to have to figure out a solution as there these methods are private (can't override by inheriting the usercontrol) and there appears to be no option for overring or suppressing the user of it's GetRedirectUrl().

Redirecting to another page on Session_end event

I would like to auto-redirect to login page when session time outs.
In web.config file, i have the following code
<configuration>
<system.web>
<sessionState mode="InProc" timeout="1"/>
</system.web>
</configuration>
In Global.asax file-
protected void Session_End(object sender, EventArgs e)
{
Response.Redirect("LoginPage.aspx");
}
But after time-out, i am receiving the following error:
HttpException was unhandled by user code.
Response is not available in this context.
Any clue to solve this issue?
Session_End is called when the session ends - normally 20 minutes after the last request (for example if browser is inactive or closed).
Since there is no request there is also no response.
I would recommend to do redirection in Application_AcquireRequestState if there is no active session. Remember to avoid loops by checking current url.
Edit: I'm no fan of .Nets built in authentication, Example goes in Global.asax:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
try
{
string lcReqPath = Request.Path.ToLower();
// Session is not stable in AcquireRequestState - Use Current.Session instead.
System.Web.SessionState.HttpSessionState curSession = HttpContext.Current.Session;
// If we do not have a OK Logon (remember Session["LogonOK"] = null; on logout, and set to true on logon.)
// and we are not already on loginpage, redirect.
// note: on missing pages curSession is null, Test this without 'curSession == null || ' and catch exception.
if (lcReqPath != "/loginpage.aspx" &&
(curSession == null || curSession["LogonOK"] == null))
{
// Redirect nicely
Context.Server.ClearError();
Context.Response.AddHeader("Location", "/LoginPage.aspx");
Context.Response.TrySkipIisCustomErrors = true;
Context.Response.StatusCode = (int) System.Net.HttpStatusCode.Redirect;
// End now end the current request so we dont leak.
Context.Response.Output.Close();
Context.Response.End();
return;
}
}
catch (Exception)
{
// todo: handle exceptions nicely!
}
}
If you are using something like FormsAuthentication for maintaining the security of your application, then this part (that part that you are trying to do) will be done for you. If FormsAuthentication discovers that a user's session has expired it will redirect him or her back to you login page.
Second, don't rely too much on Session_End because it will never trigger if you change session provider from InProc to SQLServer or other out of process provider.
You can use session property IsNewSession to detect whether it is session timeout or not.
The ASP.NET HttpSessionState class has IsNewSession() method that returns true if a new session was created for this request. The key to detecting a session timeout is to also look for the ASP.NET_SessionId cookie in the request.
Definitely I too agree that we should put the below code in some so called a custom BasePage, which is used by all pages, to implement this effectively.
override protected void OnInit(EventArgs e)
{
base.OnInit(e);
if (Context.Session != null)
{
if (Session.IsNewSession)
{
string CookieHeader = Request.Headers["Cookie"];
if((CookieHeader!=null) && (CookieHeader.IndexOf("ASP.NET_SessionId") >= 0))
{
// redirect to any page you need to
Response.Redirect("sessionTimeout.aspx");
}
}
}
}
check this link for more explanations if you want to put the above code in a base page class .
You should use Application_AcquireRequestState
You'll find that Application_AuthenticateRequest no longer has a session context (or never had one).
In my research into this I cam across this post which solved it for me.
Thanks go to Waqas Raja.
asp.net: where to put code to redirect users without a session to the homepage?
I think you are getting "Response is not available in this context" because the user is not making a request to the server, and therefor you cannot provide it with a response. Try Server.Transfer instead.
The easiest way what I feel is to use Meta information and get the trick working. Consider we have a page WebPage.aspx add the below code in the the WebPage.aspx.cs file.
private void Page_Load(object sender, System.EventArgs e){
Response.AddHeader("Refresh",Convert.ToString((Session.Timeout * 60) + 5));
if(Session[“IsUserValid”].ToString()==””)
Server.Transfer(“Relogin.aspx”);
}
In the above code, The WebPage.aspx is refreshed after 5 seconds once the Session is expired. And in the page load the session is validated, as the session is no more valid. The page is redirected to the Re-Login page. Every post-back to the server will refresh the session and the same will be updated in the Meta information of the WebPage.aspx.
you can simply do the following in web.config
<configuration>
<system.web>
<sessionState mode="InProc" timeout="1" loginurl="destinationurl"/>
</system.web>
</configuration>
Since, we can't redirect from Session_End as no response/redirect is present there.By using this you will be redirected to destinationurl when session will timeout.Hope this helps.

HttpContext.Current.User.Identity.IsAuthenticated returns false

I have a strange issue.
I have a page with the following code.
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
Server.Transfer(#"~/Views/Public/Unauthorised.aspx");
return;
}
For some reason, with one user (and we've narrowed it down to his single machine and windows logon profile), IsAuthenticated always returns false. Even though he is logged into the website, and can navigate to other pages that require authenticated user. Except this one page.
We checked that the machine accepts all cookies and we still get the same issue. I'm not sure where to go from here... any suggestions?
There are at least two known cases that can make this behavior.
First case when you have set requireSSL="true" on the Authentication session on web.config and you call that function from a non secure page. So double check if the page is secure or not, if you use the requireSSL="true"
Debug.Assert(Request.IsSecureConnection, "The IsAuthenticated will fail.");
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
Server.Transfer(#"~/Views/Public/Unauthorised.aspx");
return;
}
Second case when you do not have set the domain="site.com" again on authentication session inside the web.config, and you try to request a cookie the one time from the www.yoursitename.com and the other from yoursitename.com. In that case the authentication cookies are different and it will fail. So set that parameter among others on web.config.
<authentication mode="Forms">
<forms domain="yoursitename.com" />
</authentication>

ASP.NET login form authentication without login control

I am using ASP.NET membership and on registration page trying to login without a login control.
When user clicks on the register button, the control goes to redirect.aspx page.
But in that page, while I am trying to redirect to the members homepage its throwing the following error.
ERROR -
Unable to evaluate expression because the code is optimized or a native frame
is on top of the call stack.
Web.config -
<authentication mode="Forms">
<forms name=".SSOAuth" loginUrl="login.aspx" defaultUrl="Redirect.aspx"
protection="Validation" timeout="30000"/>
</authentication>
RegistrationPage code -
protected void btnRegister_Click(object sender, EventArgs e)
{
MembershipUser userMemb = Membership.CreateUser(
txtemail.Text.Replace("'", "''").ToString(),
txtPassword.Text.Replace("'", "''").ToString(),
txtemail.Text.ToString());
Roles.AddUserToRole(txtemail.Text.ToString(), "Member");
FormsAuthentication.RedirectFromLoginPage(txtemail.Text.Trim(), false);
}
Redirect.aspx.cs code -
try
{
if (User.IsInRole("Member"))
{
string UserName = User.Identity.Name;
Response.Redirect("~/Member/MembeHome.aspx");
}
}
catch(Exception ex) {}
Read this document (issue and solution) ThreadAbortException Occurs If You Use Response.End, Response.Redirect, or Server.Transfer
Use Response.Redirect(url,false)
Response.Redirect("~/Member/MembeHome.aspx",false);

FBA login page issue in SharePoint

I have FBA sharepoint site with custom login code (see below). When the user login out side system and I passing Cookie value to FormsAuthentication.RedirectFromLoginPage(userName, false);. It works fine till here.
The issue is, If user goes out side the system and signed out and logged in with different user id and comes to my SharePoint site the login process is skipped and the user is logged in with old id (not with new login id).
Is there any way we can go through login process if user type sharepoint site url and redirected to shareoint site.
Please gurus help me out.
try
{
if (Request.Cookies[authCookie].Value.Length > 0 || Request.Cookies[authCookie].Value != null || Request.Cookies[authCookie].Value != "")
{
userName = Request.Cookies[authCookie].Value;
}
}
catch (Exception ex)
{
Response.Redirect("https://qa.company.com/appssecured/login/servlet/LoginServlet?TARGET_URL=" + Request.Url);
}
if (true)
{
userName = Request.Cookies[authCookie].Value;
FormsAuthentication.RedirectFromLoginPage(userName, false);
}
Web.Config
<authentication mode="Forms">
<forms loginUrl="LoginAuth.aspx" timeout="2880" enableCrossAppRedirects="false" />
<!-- <forms loginUrl="/_layouts/login.aspx" />-->
</authentication>
Why not use
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
This should clear the cookie properly and redirect to login page.

Categories