How to properly sign out user - asp.net

I have a made a ASP.NET membership provider, it works well but I notice that if the user changes passwords signs out and then tries to sign in again it fails, this also happends if the user has two or more accounts and signs out with one and tries to sign in with the other. If the user clears cookies in the browser he/she can sign in again, so it seems that when the user signs out the cookies are not deleted for some reason. Here is my sign out code:
void ClearAuthenticationCookie()
{
var cookie1 = new HttpCookie(FormsAuthentication.FormsCookieName, String.Empty) { Expires = DateTime.Now.AddYears(-1) };
Response.Cookies.Add(cookie1);
}
protected void Page_Load(object sender, EventArgs e)
{
FormsAuthentication.SignOut();
ClearAuthenticationCookie();
FormsAuthentication.RedirectToLoginPage();
}

I figured it out now and it was really silly; the FormsAuthentication.RedirectToLoginPage() from the sign out page puts /Login.aspx?ReturnUrl=%2fAccount%2fLogout.aspx in the URL, so if you try to sign in it redirects back to the sign out page again. I should have seen this earlier, sorry for wasting your time :(

try adding this on page load of your signout page:
Session.Clear();

Related

Preventing FormsAuthentication expiry time from increasing

I have a relatively simple WebForms-based site using Forms Authentication:
<authentication mode="Forms">
<forms loginUrl="login.aspx" defaultUrl="secure/home.aspx" name=".AdminSite" />
</authentication>
As it's not explicitly mentioned, slidingExpiration is set to true by default, and thus a user is not logged off as long as they're still navigating around the site.
However, I'd like a specific page to not increment the expiry time. Is this possible, either within web.config or in code? The only suggestions I've seen mention setting slidingExpiration to false, which would apply side-wide.
The authentication cookie is set using:
FormsAuthentication.RedirectFromLoginPage(username, False)
and therefore altering the authentication cookie itself isn't practical.
The sliding expiration is achieved by the FormsAuthentication module by re-issuing the cookie when necessary. To prevent the sliding, you need to prevent the cookie renewal from happening.
This can be done by simply removing the FormsAuthentication cookie from the response.
Below is the code behind from a very simple web form. The aspx page has a div that shows the output from the Page_Load event.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
testDiv.InnerHtml = "Hi, cookie is: " + HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName].Value;
testDiv.InnerHtml += "<br />";
var ticket = FormsAuthentication.Decrypt( HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName].Value);
testDiv.InnerHtml += "Expires: " + ticket.Expiration.ToString("yyyy-MM-dd HH:mm:ss");
if(Response.Cookies.AllKeys.Contains(FormsAuthentication.FormsCookieName))
testDiv.InnerHtml += "<br />Forms auth is trying to update the cookie in this response";
}
protected void Page_Prerender(object sender, EventArgs e)
{
if (Response.Cookies.AllKeys.Contains(FormsAuthentication.FormsCookieName))
Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
}
}
The Page_Prerender event removes the FormsAuthentication cookie from the response if it's present, thereby preventing the sliding.
I tested this by setting the timeout for the FormsAuthentication to two minutes. Then I start debug and log in. Then I keep refreshing the page in question.
Since FormsAuthentication doesn't update the cookie unless half the expiration time has gone, what happens is that for the first minute, the page will keep showing the same encrypted cookie and the same expires time. After a bit more than one minute, the page will be reporting that FormsAuthentication is trying to renew the cookie. But the Page_Prerender removes the cookie so it doesn't get sent. After another minute you will be redirected to the login page.
Testing the same but removing the Page_Prerender method show that the cookie is changed and the expires time updated after about one minute.
You could set the response cookie's expiry date to the expiry date of the request cookie, effectively overwriting what the system is doing for that specific page.
After some thought, I deviated from trying to alter the cookie or create a second cookie or override the cookie by changing the Session.Timeout. I think it may actually be easier to use a timer, using System.Timers. The methods for the timer can always be put into a separate class if you like.
using System.Timers;
public partial class MyPage:Page
{
private System.Timers.Timer timer;
protected void Page_Load(object sender, EventArgs e)
{
SetTimer();
}
private void SetTimer()
{
// Interval is set in milliseconds- set as you please.
timer = new System.Timers.Timer(1000 * 60);
timer.Elapsed += OnTimedEvent;
timer.AutoReset = true;
timer.Enabled = true;
}
// In this handler, stop the timer and call a method to clear all cookies.
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
timer.Stop();
ClearAllCookies();
}
// Method to clear all cookies. There may be a simpler way to do this, you are vague about your cookies, so I supplied a clear all.
public void ClearAllCookies()
{
HttpCookie cookie;
string cookieName;
int cookieCnt = Request.Cookies.Count;
for(int i = 0; i < cookieCnt; i++)
{
cookieName = Request.Cookies[i].Name;
cookie = new HttpCookie(cookieName);
// This causes the cookie to expire
cookie.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(cookie);
}
Response.Redirect("LogIn.aspx");
}
}
Edit
Or use a method that logs the user out. Either way, you will end the session without having to fiddle with the user's authentication for the duration of the remainder of the website, except to end it if the session times out on this particular page.
public void ForceLogOff(){
Session.Clear();
Session.Abandon();
Session.RemoveAll();
// Do here whatever you need to do.
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
Response.Redirect("LogIn.aspx");
}
How you end the session is up to you. This provides you a way to override the sliding expiration issue, and set a custom timeout from within one page only.

ASP.NET membership - Redirect users with unpaid invoice

I'm working on a custom implementation of ASP.NET membership, which uses my own database tables. Everything works as it should, but I need to redirect customers, which have not paid their invoice, to a payment page. This should not only happen on login, but also for users which already are logged in, so if an invoice is registered as "not paid" while the user is logged in, then the user must be redirected to the payment page, the next time they load a page.
Can this be done?
I did something similar to this using a HttpModule. What you want to do is handle the PreRequest event and if they are authenticated and if so make your unpaid invoice check and redirect as necessary.
e.g.
protected void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
if (HttpContext.Current.Request.Path != "/UnPaid.aspx" && HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// You don't want to make this check for every resource type
bool isPage = HttpContext.Current.Request.Path.EndsWith(".aspx") || HttpContext.Current.Request.Path == "/";
if (isPage)
{
bool isPaid = false; // Make isPaid check here
if (!isPaid)
{
// Optional pass ina return url for after the invoice is paid
string returnUrl = HttpUtility.UrlEncode(HttpContext.Current.Request.RawUrl, HttpContext.Current.Request.ContentEncoding);
HttpContext.Current.Response.Redirect(string.Concat("/UnPaid.aspx?ReturnUrl=", returnUrl), true);
}
}
}
}
}
I wouldn't let the membership provider know this information. It is job of your application to know this, not your security model. It may be as simple as adding/removing a role, but that's not ideal either.
You can do that on global.asax using the Application_AuthenticateRequest
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
string cTheFile = HttpContext.Current.Request.Path;
if(!cTheFile.EndsWith("ThePaymentPage.aspx"))
{
if(HttpContext.Current.User != null
&& HttpContext.Current.User.Identity != null
&& HttpContext.Current.User.Identity.IsAuthenticated)
{
// check here if need to redirect or not.
if(NeedPayment())
{
HttpContext.Current.Responce.Redirect("ThePaymentPage.aspx");
}
}
}
}
This is called on every page, so maybe you can add some more checks and make it real fast. Other checks can be if the page ends on .aspx
How about having inheritance.
You can "inject" a BasePage between Page class and your ASPX code behind class.
This way, you will have access to your business logic classes and then you can decide, on each request, where the user should be re-directed.
Me too agree on the point that this logic should be handled by your applications business logic instead of the security model.
Hope this helps.

How to use DotNetOpenAuth to login to websites?

I want to do is, if the users are logged into gmail and if they go to my website they automatically get logged in.
I am doing it in the following way... maybe there is a better way of doing it.
In my website I have a place for uses to give their gmail address so my website knows gamil address of the registered user.
So when they go to my website I want to know whether they are logged into gmail and what is their gmail address.
How should I find this information using DotNetOpenAuth?
I found following code from the web and it is authenticating the user. But i have to press the button and go to gmail login every time.
if the user is already using gmail I don’t have to ask the user for login i can use it.
How do i modify this code to achieve that?
static string openidurl = "https://www.google.com/accounts/o8/id";
protected void Page_Load(object sender, EventArgs e)
{
//The Response
OpenIdRelyingParty openid = new OpenIdRelyingParty();
var response = openid.GetResponse();
if (response != null)
{
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
var fetch = response.GetExtension<FetchResponse>();
string email = "";
if (fetch != null)
{
email = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);
}
break;
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
using (OpenIdRelyingParty openid = new OpenIdRelyingParty())
{
IAuthenticationRequest request = openid.CreateRequest(openidurl);
var fetch = new FetchRequest();
fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
request.AddExtension(fetch);
// Send your visitor to their Provider for authentication.
request.RedirectToProvider();
}
}
It sounds like what you're asking for is "single-sign-on", where a visitor to your site who is already logged into Google is immediately logged into your site when they first visit it, rather than after clicking a "Google Login" button on your site.
The short answer is you can't do this. The longer answer is that you can get close.
The first and hard restriction is that first-time visitors to your site will never get automatically signed in, because Google and the user don't yet trust your site. Every user has to explicitly log in once, with Google asking the user "do you want to log into this site and remember this choice?" If they say yes, then in the future when the user is already logged into Google and visits your site, they can click the Google Login button on your site and they'll never see Google -- they'll just be immediately logged into your site.
So the next question is how do you remove the requirement on the user to click "google Login". You can accomplish this by when an unauthenticated user visits your site, you can immediately redirect them to your log in page, which will immediately initiate the "Google Login" flow (the OpenIdRelyingParty.CreateRequest(google).RedirectToProvider() call), using "immediate mode". This will fail if the user isn't logged into Google and trust your site, but the impact will be the user won't see a Google login screen if they do trust your site, but will rather be immediately logged in.
You might find my answer useful: What OpenID solution is really used by Stack Overflow?
I've also made a simple blog post about it: http://codesprout.blogspot.com/2011/03/using-dotnetopenauth-to-create-simple.html
My examples are with MyOpenID, but gmail should work the same way. The OpenID provider basically takes care of the log in, including the case when they're already logged in with the provider.
Update:
In an ASP.NET (in this case ASP.NET MVC) application you would create a cookie when the user is successfully logged in and you would check the cookie to determine if the user is logged in. As I said, please see the links above for detailed code examples and an explanation of how it all works. Here are two code samples from the Controller where I demonstrate how to check if the user is logged in:
// **************************************
// URL: /User/LogIn
// **************************************
public ActionResult LogIn()
{
if (User.Identity.IsAuthenticated) // <--- CHECKS IF THE USER IS LOGGED IN
{
return RedirectToAction("Profile", "User");
}
Identifier openID;
if (Identifier.TryParse(Request.QueryString["dnoa.userSuppliedIdentifier"], out openID))
{
return LogIn(new User { OpenID = openID }, Request.QueryString["ReturnUrl"]);
}
else
{
return View();
}
}
[HttpPost]
public ActionResult LogIn(User model, string returnUrl)
{
string openID = ModelState.IsValid?model.OpenID:Request.Form["openid_identifier"];
if (User.Identity.IsAuthenticated)//<--- CHECKS IF THE USER IS LOGGED IN
{
return RedirectToAction("Profile", "User");
}
else if (!string.IsNullOrEmpty(openID))
{
return Authenticate(openID, returnUrl);
}
else if(ModelState.IsValid)
{
ModelState.AddModelError("error", "The OpenID field is required.");
}
// If we got this far, something failed, redisplay form
return View(model);
}

Custom authentication module inheriting IHttpModule issue

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

How to handle "Remember me" in the Asp.Net Membership Provider

Ive written a custom membership provider for my ASP.Net website.
Im using the default Forms.Authentication redirect where you simply pass true to the method to tell it to "Remember me" for the current user.
I presume that this function simply writes a cookie to the local machine containing some login credential of the user.
What does ASP.Net put in this cookie? Is it possible if the format of my usernames was known (e.g. sequential numbering) someone could easily copy this cookie and by putting it on their own machine be able to access the site as another user?
Additionally I need to be able to inercept the authentication of the user who has the cookie. Since the last time they logged in their account may have been cancelled, they may need to change their password etc so I need the option to intercept the authentication and if everything is still ok allow them to continue or to redirect them to the proper login page.
I would be greatful for guidance on both of these two points. I gather for the second I can possibly put something in global.asax to intercept the authentication?
Thanks in advance.
For me the solution was differentiating between a browser-session auth cookie (not to be confused with the asp.net session cookie) and a persistent one - setting a low expiration will create a persistent cookie meaning it gets remembered when the browser is closed and re-opened within the expiration time. The following works for me:
public void SetAuthenticationCookie(LoginView loginModel)
{
if (!loginModel.RememberMe)
{
FormsAuthentication.SetAuthCookie(loginModel.Email, false);
return;
}
const int timeout = 2880; // Timeout is in minutes, 525600 = 365 days; 1 day = 1440.
var ticket = new FormsAuthenticationTicket(loginModel.Email, loginModel.RememberMe, timeout);
//ticket.
string encrypted = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encrypted)
{
Expires = System.DateTime.Now.AddMinutes(timeout),
HttpOnly = true
};
HttpContext.Current.Response.Cookies.Add(cookie);
}
FormsAuthentication and MembershipProviders are two completely different things, still they are made to work with each other very well. If you have written a persistent cookie ["Remember Me"] then next time, you can simply call Membership.GetUser() which will return you the MembershipUser instance of the currently logged in user or null if no user is logged in.
So first time when user arrives and authenticates with "Remember Me", you shall write a persistent cookie as following.
FormsAuthentication.RedirectFromLoginPage(strUserName, true);
Assuming user does not logout and leaves webpage and comes back after sometime. You can simply call MembershipUser.GetUser() as following and check if the user is already logged from the persistent cookie written by FormsAuthentication.
MembershipUser someUser = Membership.GetUser();
if(someUser == null)
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
}
else
{
//Take where logged in users go.
}
You can do this check on your Login page itself or main landing page to intercept the User account to check if he needs to change the password or if the account is disabled as in your case.
EDIT
There are two ways to do this.
1.) Check for authentication as mentioned above in Session_Start event in global.asax and set a session key that becomes available on all pages for that particular session.
2.) Another way is too keep a common application wide common PageBase class that inherits from System.Web.UI.Page and acts as base page class for all your asp.net pages. On the Page Load of the common PageBase class check for the authentication as mentioned above. You will have to carefully write conditional redirection in this case since this might head towards infinite redirection with no end since it will run on Page_Load of all page from the common PageBase class.
public class PageBase : System.Web.UI.Page
{
/// <summary>
/// Initializes a new instance of the Page class.
/// </summary>
public Page()
{
this.Load += new EventHandler(this.Page_Load);
}
private void Page_Load(object sender, EventArgs e)
{
try
{
AuthenticateUser();
}
catch
{
//handle the situation gracefully.
}
}
private AuthenticateUser()
{
MembershipUser someUser = Membership.GetUser();
if(someUser == null)
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
}
else
{
//Take where logged in users go.
}
}
}
//in your asp.net page code-behind
public partial class contact : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
}
}

Resources