I want to be able to track user logins and logouts on my site.
I wrote an http module for that.
I can detect a login no problem. Logouts, however, I'm having a trouble with.
My initial thought was to check for the destruction of the cookie inside Application_EndRequest handler.
That does no good, because after the call to FormsAuthentication.SignOut(), request cookie collection still contains the auth cookie.
// In Application_EndRequest
if (httpRequest.IsAuthenticated)
{
HttpCookie authCookie = httpRequest.Cookies[FormsAuthentication.FormsCookieName];
// Doesn't work. "authCookie" is always non-empty
if (authCookie == null || authCookie.Value == "")
{
//logout detected
}
}
else
{
HttpCookie authCookie = httpRequest.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//login detected
}
}
If this is not the right approach, let me know.
Are you calling FormsAuthentication.SignOut() and httpRequest.Cookies[FormsAuthentication.FormsCookieName] in same request?
If so, the cookie will still be valid, because SignOut() could not make the cookie invalid in the current request.
You will need to test whether cookie is valid or not in separate request. Even then, calling Request.IsAuthenticated is enough, because you do not need to call Cookies[FormsAuthentication.FormsCookieName] explicitly.
Related
LoginPage.aspx:-
protected void Button1_Click(object sender, EventArgs e)
{
Context.Items["Username"] = txtUserId.Text;
Context.Items["Password"] = txtPassword.Text;
//
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, Context.Items["Username"].ToString(), DateTime.Now, DateTime.Now.AddMinutes(10), true, "users", FormsAuthentication.FormsCookiePath);
// Encrypt the cookie using the machine key for secure transport
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie
hash); // Hashed ticket
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
Response.Cookies.Add(cookie);
Response.Redirect("Default.aspx");
}
Global.asax file:-
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id =
(FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
// Get the stored user-data, in this case, our roles
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
Response.Write(HttpContext.Current.User.Identity.Name);
Response.Redirect("Default.aspx");
}
}
}
}
I get the following error after signing in
This webpage has a redirect loop.
The webpage at http://localhost:1067/Default.aspx has resulted in too many redirects. Clearing your cookies for this site or allowing third-party cookies may fix the problem. If not, it is possibly a server configuration issue and not a problem with your computer.
This is the rough idea of what your module should look like. Your module will run on every request. You don't invoke it or pass anything to it, it just automatically fires whenever a request is made that ASP.Net is set to process.
Your module will do two things, 1) authenticate a user in the login page, 2) authenticate a user on subsequent pages. The first step is to subscribe to the BeginRequest method which will be given the current HttpApplication as the first parameter. From there you need to determine if the user is on your login page or not. If they're not on your login page, check your session or cookie or querystring token, or whatever you're using to make sure that they're still valid. If they're invalid, bounce them back to the login page.
If they're on your login page and have made a POST, look at the raw form fields and validate them. TextBoxes, checkboxes, etc don't exist here, only raw form fields. If they're valid, set your authentication token however you want (session, cookies, etc). If they're invalid, either redirect to the login page or inject a "try again" message or something.
Also, if you double-post a message please reference it so that we can follow the chain of what was already said.
class MyModule : IHttpModule
{
void IHttpModule.Init(HttpApplication context)
{
//Subscribe to the BeginRequest event
context.BeginRequest += new EventHandler(this.Application_BeginRequest);
}
private void Application_BeginRequest(Object source, EventArgs e)
{
//Initialize our variables, null checks should be put here, too
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
System.Web.SessionState.HttpSessionState s = context.Session;
//Normally our module needs to validate every request to make sure our request is still authenticated.
//The exception to that rule is on our logon page where they obviously don't have credentials yet.
if(!context.Request.FilePath.ToLowerInvariant().StartsWith("/login.aspx")){
//If we're here then we're not on the logon page, validate our current session according to whatever logic we want
if (s != null && s["isvalid"] == "true"){
return;
}else{
context.Response.Redirect("/login.aspx");
}
}else{
//If we're here then we're on the login page itself. If there's a post, assume that they've hit the login button
if (context.Request.HttpMethod == "POST")
{
//Whatever your form variables are called
string username = context.Request.Form["username"];
string password = context.Request.Form["password"];
//Your own validation logic would go here
if (MyCustomLogin.IsUserValid(username, password))
{
s["isvalid"] = "true";
context.Response.Redirect("/Home.aspx");
}else{
s["isvalid"] = "false";
context.Response.Redirect("/login.aspx?error=invalid_login");
}
}else{
//If we're here then the request is probably a GET or HEAD which would be from a person
//initially browsing to our page so just do nothing and pass it through normally
}
}
}
}
There is no direct way to have access to this information in the module (for authenticated user, you can access the username via the context, but not the password). The module checks if a request is carrying required authentication information and serve or deny the request based on that. Unless you deliberately from the login page collect this information and store somewhere where you can access it in the module, e.g session. But ideally, storing password is not widely recommended, collect it use it for authentication and destroy.
You might ideally throw more light on the reason why you want to have access to this information in the module and guys can then suggest methods to accomplish it.
Edited, after Chandan comment:
#Chandan, your comment here suggest to me what you want to do is use httpmodule for your authentication as against using standard form authentication. If I am on track, then you can check this project on codeproject at http://www.codeproject.com/KB/web-security/AspNetCustomAuth.aspx. Goodluck
I'm implementing an authentication timeout detection mechanism per a previous question and answer of mine here. I've implemented an HTTP module that uses the AuthenticateRequest event to run code to capture whether the authentication period has expired. The code to do this is below:
public class AuthenticationModule : IHttpModule
{
#region IHttpModule Members
void IHttpModule.Dispose() { }
void IHttpModule.Init(HttpApplication application)
{
application.AuthenticateRequest += new EventHandler(this.context_AuthenticateRequest);
}
#endregion
/// <summary>
/// Inspect the auth request...
/// </summary>
/// <remarks>See "How To Implement IPrincipal" in MSDN</remarks>
private void context_AuthenticateRequest(object sender, EventArgs e)
{
HttpApplication a = (HttpApplication)sender;
HttpContext context = a.Context;
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = context.Request.Cookies[cookieName]; // no longer a forms cookie in this array once timeout has expired
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
DateTime expirationTime = authTicket.Expiration;
// check if previously authenticated session is now dead
if (authTicket != null && authTicket.Expired)
{
// send them a Response indicating that they've expired.
}
}
}
}
The problem is that, once the authentication period has expired (I set it to 1 min to test), there is no longer a forms cookie (see comment in code). This means that the authentication cookie will be null, and I won't make it past the null check in my code. But there's a convenient "Expired" property for a FormsAuthenticationTicket that I feel like I should be checking to see if the period is expired. But how do I get that far if the cookie is no longer there? Is it reasonable to assume the authentication period has expired if there's no longer a forms cookie?
Any help would be appreciated on this.
You might want to try something like this:
if (User != null)
{
FormsIdentity id = (FormsIdentity)User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
if (ticket.Expired)
{
//do something
}
}
More info
Edit:
1: I see the User will be null. So using User.Identity is out of question.
2: How about trying the code you have in your original question in the BeginRequest event instead of AuthenticateRequest.
If If isPersistent is set to false on the FormsAuthenticationTicket then a persistent cookie is not set. When the ticket expires the cookie is not sent with the request, therefore you cannot access it.
This behavior is controlled by the System.Web.Security.FormsAuthenticationModule. This module checks if the ticket is expired and in this case remove the cookie.
Note also that this module checks slidingExpiration option and if required renew the ticket.
So back to your question:
Is it reasonable to assume the authentication period has expired if there's no longer a forms cookie?
The response I think is yes.
I am using Form Authentication and sending an Aajx request to the server for authentication. Based on the json result, the client decides where to go and what to do. That is the reason I am not using FormsAuthentication.RedirectFromLoginPage to not interfere the ajax/json response.
In this case Request.IsAuthenticated returns false, even after validating the user with Membership.ValidateUser. Then I set the cookie using
FormsAuthentication.SetAuthCookie(username, false);
Although the second parameter, persistent cookie, is false, the cookie is still valid across browser sessions.
Any idea how to make Request.IsAuthenticated work without using FormsAuthentication.RedirectFromLoginPage?
You need to update the current security principal for the request. When you call Response. Redirect(...) a new request is done and the security principal is reinitialized and Request.IsAuthenticated returns true in your case. FormsAuthentication.RedirectFromLoginPage internally calls Response. Redirect(...). You can manually renew the security principal for the current request like this:
public void RenewCurrentUser()
{
System.Web.HttpCookie authCookie =
System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = null;
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && !authTicket.Expired)
{
FormsAuthenticationTicket newAuthTicket = authTicket;
if (FormsAuthentication.SlidingExpiration)
{
newAuthTicket = FormsAuthentication.RenewTicketIfOld(authTicket);
}
string userData = newAuthTicket.UserData;
string[] roles = userData.Split(',');
System.Web.HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(new FormsIdentity(newAuthTicket), roles);
}
}
}
FormsAuthentication.SetAuthCookie
Method Creates an authentication
ticket for the supplied user name and
adds it to the cookies collection of
the response, or to the URL if you are
using cookieless authentication.
Ref: msdn
Have a look at the Forms Authentication Control Flow. The authentication cookie is set to the response cookie collection, and should be observable at the http protocol level (e.g. use FireCookie or Fiddler2 to verify this).
Membership only verifies a username/password. Neither Membership nor SetAuthCookie() will modify the current request. They expect to send the cookie back to the caller, and the next request is when the properties like IsAuthenticated will return true.
Note that you can override and extend these automatic processes using custom IIdentity and IPrincipal, and hook into the authentication events if you need to.
Also have a look at Using Forms Authentication with ASP.NET AJAX
Redirecting after a POST is best practice, and should be considered the correct solution.
In some cases, you may still want to find out whether a user is authenticated within the scope of the authentication request (for instance if you are running additional logic after the authentication was performed which is shared with other requests).
In this case, you can reset the value of Request.IsAuthenticated with the following code:
// set the forms auth cookie
FormsAuthentication.SetAuthCookie(username, createPersistentCookie);
// reset request.isauthenticated
var authCookie = System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && !authTicket.Expired)
{
var roles = authTicket.UserData.Split(',');
System.Web.HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new FormsIdentity(authTicket), roles);
}
}
See post here: http://abadjimarinov.net/blog/2010/01/24/RenewUserInTheSameRequestInAspdotNET.xhtml
we can use this simply
FormsAuthentication.SetAuthCookie(username, true);
In an ASP.net application I'm using a Login control with a custom membership provider that I wrote. What I want to do is to set Thread.CurrentPrincipal to my custom Principal object, just after the user is authenticated.
I'm using the setter: Thread.CurrentPrincipal and it sets the Principal object for me but, on all the consequent threads this CurrentPrincipal is overridden with the default one.
Here is my code for the Authenticate event of the Login control:
protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
string username = Login1.UserName;
string password = Login1.Password;
if (Membership.ValidateUser(username, password))
{
var login = sender as Login;
var phoenixIdentity = new PhoenixIdentity("B", "Forms" , true);
var principal = new PhoenixPrincipal(phoenixIdentity);
Thread.CurrentPrincipal = principal;
AppDomain.CurrentDomain.SetThreadPrincipal(principal);
HttpContext.Current.User = principal;
e.Authenticated = true;
}
}
For example, imagine that I login with the username A, everything goes well... Validation passes, but I hardcode the user with the username B in the Identity object which is set to the Principal object I set as the CurrentPrincipal object.
When I check which user is set to the CurrentPrincipal Identity at the end of this method it says it's user B. But when I load another page and then check what the Identity of the CurrentPrincipal is, it says it's user A.
So, how can I make my CurrentPrincipal object to be persistent across all other threads, and where/when does this Login control set the CurrentPrincipal object of the Thread?
Tadas is not wrong, FormsAuthentication correctly implemented will not cause this problem.
Your page is accessible even without login, only in the login page, your thread's principle is set manually by you, but when you hit the other URL, it sure doesnt call your login page and remember each page runs on its own different thread. If you request first page and set thread principle and you request second page in same browser instance, it may or may not be the exact same thread.
This is how FormsAuthentication works,
It checks if Auth Cookie is set or not, it then directs user to login page
Login page must validate and set auth cookie, like FormsAuthentication.SetAuthCookie
Before every page access, Step 1 is executed.
After successful validation of Auth Cookie, ASP.NET internally sets the current user and all differnet parameters according to your membership component.
ASP.NET Global.asax file can give you some events where in you can plugin your code to check just after authentication is successful you can change your current user, remember setting your current principle on login page will not help
We had similar issue when we were using session to store certain important information, after auth sessions were not rebuilt, so we wrote a HTTP Module, and in it's init method, we attached AfterRequestAcquired event and in this event you can write your code to instantiate all your important user related variables.
You can handle FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs e) (in Global.asax) and set CurrentPrincipal here.
void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs e)
{
var phoenixIdentity = new PhoenixIdentity("B", "Forms" , true);
var principal = new PhoenixPrincipal(phoenixIdentity);
e.User = principal;
}
This is what I did in FormsAuthentication_OnAuthenticate method:
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket =
FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value);
var myIdentity = new GenericIdentity("B");
var principal = new GenericPrincipal(myIdentity, new string[]{"rola1"});
e.User = principal;
}
catch (Exception ex)
{
// Decrypt method failed.
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not " +
"supported for this application.");
}
it seems that it's working what it should do... It's just that if I put my custom principal/identity pair as e.User, then I have serialization problem which I need to fix next... Thank you guys...
What is the best method for determining if a users browser has cookies enabled in ASP.NET
Set a cookie, force a redirect to some checking page and check the cookie.
Or set a cookie on every pageload, if it's not already set. For instance, I assume this is to check if cookies are supported to display a message when they try to login that they need to enable cookies. Set your login cookie to some default value for guest users if they don't have the cookie set yet. Then on your login page, check for the user cookie, and if it's not set, then display your message.
#Mattew is right the only way to find out is to set a cookie, redirect, then check it.
Here is a C# function to preform that check you can put this in your page load event:
private bool cookiesAreEnabled()
{
bool cookieEnabled = false;
if(Request.Browser.Cookies)
{
//Your Browser supports cookies
if (Request.QueryString["TestingCookie"] == null)
{
//not testing the cookie so create it
HttpCookie cookie = new HttpCookie("CookieTest","");
Response.Cookies.Add(cookie);
//redirect to same page because the cookie will be written to the client computer,
//only upon sending the response back from the server
Response.Redirect("Default.aspx?TestingCookie=1")
}
else
{
//let's check if Cookies are enabled
if(Request.Cookies["CookieTest"] == null)
{
//Cookies are disabled
}
else
{
//Cookies are enabled
cookieEnabled = true;
}
}
}
else
{
// Your Browser does not support cookies
}
return cookieEnabled;
}
You can do it in javascript also, this way :
function cookiesAreEnabled()
{
var cookieEnabled = (navigator.cookieEnabled) ? 1 : 0;
if (typeof navigator.cookieEnabled == "undefined" && cookieEnabled == 0){
document.cookie="testcookie";
cookieEnabled = (document.cookie.indexOf("testÂcookie") != -1) ? 1 : 0;
}
return cookieEnabled == 1;
}
Write a cookie, redirect, see if you can read the cookie.
Well I think if we can save cookie in Global.ASAX session start and read that on page.. isnt that best way?
Essentially the same solution as meda, but in VB.NET:
Private Function IsCookieDisabled() As Boolean
Dim currentUrl As String = Request.RawUrl
If Request.Browser.Cookies Then
'Your Browser supports cookies
If Request.QueryString("cc") Is Nothing Then
'not testing the cookie so create it
Dim c As HttpCookie = New HttpCookie("SupportCookies", "true")
Response.Cookies.Add(c)
If currentUrl.IndexOf("?") > 0 Then
currentUrl = currentUrl + "&cc=true"
Else
currentUrl = currentUrl + "?cc=true"
End If
Response.Redirect(currentUrl)
Else
'let's check if Cookies are enabled
If Request.Cookies("SupportCookies") Is Nothing Then
'Cookies are disabled
Return True
Else
'Cookies are enabled
Return False
End If
End If
Else
Return True
End If
End Function
meda's c# function works though you have to change the line:
HttpCookie cookie = new HttpCookie("","");
to
HttpCookie cookie = new HttpCookie("CookieTest","CookieTest");
You can also check the value of Request.Browser.Cookies. If true, the browser supports cookies.
this is the best way
taken from
http://www.eggheadcafe.com/community/aspnet/7/42769/cookies-enabled-or-not-.aspx
function cc()
{
/* check for a cookie */
if (document.cookie == "")
{
/* if a cookie is not found - alert user -
change cookieexists field value to false */
alert("COOKIES need to be enabled!");
/* If the user has Cookies disabled an alert will let him know
that cookies need to be enabled to log on.*/
document.Form1.cookieexists.value ="false"
} else {
/* this sets the value to true and nothing else will happen,
the user will be able to log on*/
document.Form1.cookieexists.value ="true"
}
}
thanks to Venkat K