In our system we use ASP.Net WebForms pages.
We have a self registration web page in our system where a user can come along and register to start using our system.
We have recently discovered that one of our clients are posting form data to that page from their website which in turn submits the form on the page.
We want to stop this from happening. We don't mind if they embed the page into an IFrame because it won't work correctly anyway. But we need to stop them from posting form data to that page from their system / website.
Is there an attribute that we can set or something which stop external posting to that page? Or perhaps an other alternative, Ajax seems to jump to mind?
Not quote sure what to search for, did a quick search online but couldn't find anything.
Reason for wanting to Prevent this:
When something goes wrong on that page, the user trying to register does not get any error messages back from our system so we are getting a ton of support about this. Surely we can add functionality to support but we would rather not. Also want to prevent bot attacks on that page because each submit will create a user if they do not exist.
Starting with Visual Studio 2012, Microsoft added built-in CSRF protection to new web forms application projects. To utilize this code, add a new ASP .NET Web Forms Application to your solution and view the Site.Master code behind page. This solution will apply CSRF protection to all content pages that inherit from the Site.Master page.
The following requirements must be met for this solution to work:
All web forms making data modifications must use the Site.Master page. All requests making data modifications must use the ViewState. The web site must be free from all Cross-Site Scripting (XSS) vulnerabilities. See how to fix Cross-Site Scripting (XSS) using Microsoft .Net Web Protection Library for details. and also see it
public partial class SiteMaster : MasterPage
{
private const string AntiXsrfTokenKey = "__AntiXsrfToken";
private const string AntiXsrfUserNameKey = "__AntiXsrfUserName";
private string _antiXsrfTokenValue;
protected void Page_Init(object sender, EventArgs e)
{
//First, check for the existence of the Anti-XSS cookie
var requestCookie = Request.Cookies[AntiXsrfTokenKey];
Guid requestCookieGuidValue;
//If the CSRF cookie is found, parse the token from the cookie.
//Then, set the global page variable and view state user
//key. The global variable will be used to validate that it matches in the view state form field in the Page.PreLoad
//method.
if (requestCookie != null
&& Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
{
//Set the global token variable so the cookie value can be
//validated against the value in the view state form field in
//the Page.PreLoad method.
_antiXsrfTokenValue = requestCookie.Value;
//Set the view state user key, which will be validated by the
//framework during each request
Page.ViewStateUserKey = _antiXsrfTokenValue;
}
//If the CSRF cookie is not found, then this is a new session.
else
{
//Generate a new Anti-XSRF token
_antiXsrfTokenValue = Guid.NewGuid().ToString("N");
//Set the view state user key, which will be validated by the
//framework during each request
Page.ViewStateUserKey = _antiXsrfTokenValue;
//Create the non-persistent CSRF cookie
var responseCookie = new HttpCookie(AntiXsrfTokenKey)
{
//Set the HttpOnly property to prevent the cookie from
//being accessed by client side script
HttpOnly = true,
//Add the Anti-XSRF token to the cookie value
Value = _antiXsrfTokenValue
};
//If we are using SSL, the cookie should be set to secure to
//prevent it from being sent over HTTP connections
if (FormsAuthentication.RequireSSL &&
Request.IsSecureConnection)
responseCookie.Secure = true;
//Add the CSRF cookie to the response
Response.Cookies.Set(responseCookie);
}
Page.PreLoad += master_Page_PreLoad;
}
protected void master_Page_PreLoad(object sender, EventArgs e)
{
//During the initial page load, add the Anti-XSRF token and user
//name to the ViewState
if (!IsPostBack)
{
//Set Anti-XSRF token
ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
//If a user name is assigned, set the user name
ViewState[AntiXsrfUserNameKey] =
Context.User.Identity.Name ?? String.Empty;
}
//During all subsequent post backs to the page, the token value from
//the cookie should be validated against the token in the view state
//form field. Additionally user name should be compared to the
//authenticated users name
else
{
//Validate the Anti-XSRF token
if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
|| (string)ViewState[AntiXsrfUserNameKey] !=
(Context.User.Identity.Name ?? String.Empty))
{
throw new InvalidOperationException("Validation of
Anti-XSRF token failed.");
}
}
}
}
It's not that hard to roll something basic, you can generate a Guid into a hidden field, then check you get the same one back, it's not as secure as a CAPTCHA, but would prevent random things posting to you without filling the form in, but wouldn't prevent scripts that do actually fill the form in and post from the page.
<asp:Hidden ID="Validation" runat="server" />
Page Load:
var guid = Guid.NewGuid();
Validation.Text = guid.ToString();
Session["Token"] = guid;
Then on postback check you get the same one back...
Alternatively, take a look at the Captcha NuGet package. It works on both webforms and MVC.
Show a Captcha:
<%# Register Assembly="BotDetect" Namespace="BotDetect.Web.UI"
TagPrefix="BotDetect" %>
[…]
<BotDetect:Captcha ID="SampleCaptcha" runat="server" />
<asp:TextBox ID="CaptchaCodeTextBox" runat="server" />
Validate post:
if (IsPostBack)
{
// validate the Captcha to check we're not dealing with a bot
bool isHuman = SampleCaptcha.Validate(CaptchaCodeTextBox.Text);
CaptchaCodeTextBox.Text = null; // clear previous user input
if (!isHuman)
{
// TODO: Captcha validation failed, show error message
}
else
{
// TODO: Captcha validation passed, proceed with protected action
}
}
Examples from the documentation.
Related
I want to create a login page that contain user and password with ASP.NET Core 5.
How can I prevent login with one user in different browser?
I would suggest at login you create a unique token that is stored on the server and passed back to the client as either an encrypted cookie value or if you are using Claims authentication as a claim. You then write a filter to test that the sent token matches the server side stored value on Authentication. You may want to store and update a datetime on the token - similar to a sliding cache item - so that you can expire the token.
When another browser or login occurs and the token is within a certain date then you can either suppress the login attempt or generate a new token which would logout the first browser user.
An example filter is shown below.
public class SignOnCheckerFilter : IAuthorizationFilter
{
public SignOnCheckerFilter (){}
public void OnAuthorization(AuthorizationFilterContext context)
{
bool checkOk = false;
if (context.HttpContext.User.Identity.IsAuthenticated)
{
//read cookie or claims
//check match
//if match success
checkOk = true;
}
//return if checkOk
if(checkOk) return;
//return forbidden result if invalid match (you could redirect the user to a logout page?)
context.Result = new ForbidResult();
}
}
I have a login page that allows the user to login. In the HandlieValidSubmit() event I check if username and password matches with the value stored in database.
If everything is fine I want to store some data into the usercookie before redirecting to another site. This is the html/blazor-Code:
<EditForm>
<!-- standard form-controls here -->
</EditForm>
#code {
private Models.LoginUser _loginUser = new Models.LoginUser();
private EditContext _editContext;
private void HandleValidSubmit()
{
if (UserApi.Login(_loginUser.Mail, _loginUser.Password, out string error, out Guid? guid))
{
NaviationManager.NavigateTo($"/manage/{guid}");
}
}
}
the Cookie is set from within the Login-Function and looks like this:
public DateTime SetCookie<T>(T data, TimeSpan expiration, bool httpOnly = true, bool secure = true)
{
DateTime expireDate = DateTime.Now.Add(expiration);
if (data == null) return DateTime.Now;
Type dataType = typeof(T);
var response = _httpContextAccessor.HttpContext.Response;
CookieOptions cookieOptions = new CookieOptions
{
HttpOnly = httpOnly,
Secure = secure,
Expires = expireDate
};
foreach (var property in dataType.GetProperties())
{
var storeInCookieAttribute = property.GetCustomAttribute<StoreInCookieAttribute>();
if (storeInCookieAttribute == null) continue;
response.Cookies.Append(BuildCookieKey(dataType.Name, property.Name), property.GetValue(data) as string, cookieOptions);
}
return expireDate;
}
IMHO this is the standard "how-to-write-cookies-in-netcore" - way.
When I try to write the cookie I receive the error:
"The response headers cannot be modified because the response has already started."
I understand what this error wants to tell me. Alas I do not really know how to prevent this. I expected that at this point the response should not have started at all.
Is there another event than HandleValidSubmit() I need to use instead? Or can I just clear the Response before writing the cookie without bad side effects?
Blazor Server App is websocket-based application, not HTTP-based one, so the HttpContext service is not available.
When you create a Blazor Server App with support for IdentityUI, you get in the default template a component ( AuthorizeView ) that enables login and logout. When you click on the "Login" button, you are being redirected to a Login page where you can enter your credentials. The Login page is actually a Razor Page, not part of the Blazor App, meaning that you are no longer in the realm of Blazor, and here in this new realm (The Razor Page), the HttpContext is available, you don't even have to use the HttpContextAccessor, as the HttpContext is provided as a property in the PageModel object. After the user has been logged in, cookies created, etc., he is redirected to Blazor.
This is how you can do it. Just emulate this procedure... Create a Razor Page, where you can do all that stuff. You may also pass a return url, so that you'll be redirected to a specific Component page instead of the the Index Component Page.
Note: HandleValidSubmit() is a method that is called if your forms component elements have passed validation. It has got nothing to do with the issue in question.
Note: To use the NavigationManger.NavigateTo method to navigate to external location (outside of the Blazor App realm), add a second boolean parameter with the value true.
Note: This may help you how to cope . There are also many answers related to the current subject and authentication with OpenID Connect, Okta, IdentityUI, etc. Just look for them if you are in need.
Note: Are you aware that you can store data in the local storage and session storage ?
Hope this helps...
I have programmed a Remember Me check box to store the username and password in cookies if the box is checked. My problem is, that if they check it and then re-launch the application the username and password auto fill, but the checkbox does not stay checked.
I have not figured out a way to do this yet...
I will post my code below:
if (!IsPostBack)
{
if (Request.Cookies["UserName"] != null && Request.Cookies["Password"] != null)
{
userNameTxtBox.Text = Request.Cookies["UserName"].Value;
passwordTxtBox.Attributes["value"] = Request.Cookies["Password"].Value;
}
}
if (chkBoxRememberMe.Checked)
{
Response.Cookies["UserName"].Expires = DateTime.Now.AddDays(30);
Response.Cookies["Password"].Expires = DateTime.Now.AddDays(30);
}
else
{
Response.Cookies["UserName"].Expires = DateTime.Now.AddDays(-1);
Response.Cookies["Password"].Expires = DateTime.Now.AddDays(-1);
}
Response.Cookies["UserName"].Value = myUserName
string passwordEncrypted = Crypto.Sha256(myPassword);
Response.Cookies["Password"].Value = passwordEncrypted;
The above code works as it should. But I have a remember me check box that if it is checked I would like to save it to the cookies and leave it checked until the user unchecks it....
Any ideas?
If the checkbox should be checked by default any time the cookie is present, then I imagine you can just set its checked state here:
if (Request.Cookies["UserName"] != null && Request.Cookies["Password"] != null)
{
userNameTxtBox.Text = Request.Cookies["UserName"].Value;
passwordTxtBox.Attributes["value"] = Request.Cookies["Password"].Value;
chkBoxRememberMe.Checked = true; // <-- here
}
Note that there are probably a couple of problems you're overlooking here...
If you always check the checkbox by default when the cookie is present, you don't give the user any way to uncheck the checkbox. The user would need to manually delete the cookie from their browser, which isn't a very great user experience. (Not to mention many users won't be aware of how to do that.)
Storing a user's password in plain text is grossly irresponsible. Please stop doing that immediately. Instead, generate some kind of unique token to identify the user and store that token in the cookie. The server-side code would then validate the token against known tokens associated with known users. Never, ever store the user's password in plain text anywhere.
Not only is what you're doing terribly bad in terms of security, you're also reinventing the wheel.
ASP.Net already supports out of the box Forms authentication, a default membership provider, properly encrypted/hashed authentication cookies and even a logon control with a remember me checkbox!
You would add the control to your ASP.Net logon page with markup something like this:
<asp:Login ID="LoginControl1" runat="server" CreateUserUrl="Register.aspx"
VisibleWhenLoggedIn="False" TextLayout="TextOnTop" RememberMeSet="True">
(We've set the DisplayRememberMe property to true. This instructs the logon control to display the "Remember me next time" check box and the membership provider to send the persistent authentication cookie to the user's browser upon successful logon.)
Before you start trying that however you should probably read "Walkthrough: Creating a Web Site with Membership and User Login" on MSDN.
If the session has expired and the user clicks on a link to another webform, the asp.net authentication automatically redirect the user to the login page.
However, there are cases when the user does not click on links to other webforms. For example: edit link in gridviews, when using AutoCompleteExtender with textboxes and the application attempts to get the information, and basically, in every case when a postback is done and the event is not automatically handled by the asp.net authentication.
What is the best way to handle these exceptions?
UPDATE: I have just modified the question title: forms authentication timeout, instead of the initial session timeout. Thanks for making me aware of this difference.
UPDATE: I have just created a new question with the specific problem I am facing: How to handle exception due to expired authentication ticket using UpdatePanel?. Surprisingly, I have not found much information about it. I would really appreciate your help.
This is why many systems include timers on the page to give approximate timeout times. This is tough with interactive pages. You really need to hook ajax functions and look at the return status code, which is a bit difficult.
One alternative is to use code based on the following which runs early in the page lifecycle and perform an ajax redirect to a login page. Otherwise you are stuck trying to intercept the return code from ajax and in asp.net where the ajax is done 'for you' (ie not a more manual method like jQuery) you lose this ease of detection.
http://www.eggheadcafe.com/tutorials/aspnet/7262426f-3c65-4c90-b49c-106470f1d22a/build-an-aspnet-session-timeout-redirect-control.aspx
for a quick hack you can try it directly in pre_init
http://forums.asp.net/t/1193501.aspx
Edit
what is wanted are for forms auth timeouts, not session timeouts. Forms auth timeouts operate on a different scale than session timeouts. Session timeouts update with every request. Forms auth tickets aren't actually updated until half of the time goes by. So if you have timeouts set to an hour and send in one request 25 minutes into it, the session is reset to an hour timeout, the forms auth ticket isnt touched and expires in 35 minutes! To work around this, sync up the session timeout and the forms auth ticket. This way you can still just check session timeouts. If you don't like this then still - do the below and sync up the timeouts and then parse the auth ticket and read its timeout. You can do that using FormsAuthentication.Decrypt - see:
Read form authentication cookie from asp.net code behind
Note that this code requires that upon login you set some session value - in this case its "UniqueUserId". Also change the login page path below to fit yours.
protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
//Only access session state if it is available
if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
{
//If we are authenticated AND we dont have a session here.. redirect to login page.
HttpCookie authenticationCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authenticationCookie != null)
{
FormsAuthenticationTicket authenticationTicket = FormsAuthentication.Decrypt(authenticationCookie.Value);
if (!authenticationTicket.Expired)
{
if (Session["UniqueUserId"] == null)
{
//This means for some reason the session expired before the authentication ticket. Force a login.
FormsAuthentication.SignOut();
Response.Redirect("Login.aspx", true);
return;
}
}
}
}
}
If you're using Forms Authentication, the user will be redirected to the login page when the Forms Authentication ticket expires, which is not the same as the Session expiring.
You could consider increasing the Forms Authentication timeout if appropriate. Even to the extent of using a persistent cookie. But if it does expire, there's no real alternative to redirecting to the login page - anything else would be insecure.
One way to deal with Session timeouts is to use Session as a cache - and persist anything important to a backing store such as a database. Then check before accessing anything in Session and refresh if necessary:
MyType MyObject
{
get
{
MyType myObject = Session["MySessionKey"] as MyType
if (myObject == null)
{
myObject = ... get data from a backing store
Session["MySessionKey"] = myObject;
}
return myObject;
}
set
{
Session["MySessionKey"] = value;
... and persist it to backing store if appropriate
}
}
If you're using a master page or a base page, I would add some logic to one of the events in the page lifecycle to check whether the session is new:
protected void Page_Load(object sender, EventArgs e)
{
if (Session.IsNewSession)
{
//do whatever you need to do
}
}
aware that there are a lot of questions relating to Forms Authentication and the persistence of cookies, but having spent most of a day delving around, I'm still having difficulties.
My Problem
I am working on a web app (VS2010 but webapp is f/w 3.5) which restricts access to certain parts of the app to authenticated users (whereas other parts are open). My problem is that my authentication cookies do not appear to be persisting after I close the browser.
My Approach
I have written a simple login.aspx page which is configured in web.config as follows:
<authentication mode="Forms">
...and the individual pages' behaviour are declared like so:
<location path="ClientManageAccount.aspx">
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
</location>
...which works fine in every respect EXCEPT for these cookie shenanigans...
I create the authentication cookie manually once I have authenticated my user's login & password against the database (which works fine) in the login.aspx page. If the user selects the 'keep me logged in' checkbox, the cookie is generated using this method:
private void GenerateAuthenticationCookie(int expiryInMinutes, Guid userGuid)
{
DateTime cookieExpiration = DateTime.Now.AddMinutes(expiryInMinutes); // change to months for production
var authenticationTicket =
new FormsAuthenticationTicket(
2,
userGuid.ToString(),
DateTime.Now,
cookieExpiration,
true,
string.Empty,
FormsAuthentication.FormsCookiePath);
// ticket must be encrypted
string encryptedTicket = FormsAuthentication.Encrypt(authenticationTicket);
// create cookie to contain encrypted auth ticket
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.Expires = authenticationTicket.Expiration;
authCookie.Path = FormsAuthentication.FormsCookiePath;
// clear out existing cookie for good measure (probably overkill) then add
HttpContext.Current.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
HttpContext.Current.Response.Cookies.Add(authCookie);
}
The objective here is that I store a user Guid in the auth cookie, which I will then use to restore a user object into session (this is also in the login.aspx page, and my thinking is that I'd like to pluck the user guid from the auth cookie that I have created, and use it to stuff the corresponding user record into session and redirect to the requested page):
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
TryAutoLogin();
}
}
private void TryAutoLogin()
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
if (cookie != null)
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
if (ticket != null)
{
if (ticket.Name.Length > 0)
{
try
{
Guid userGuid = new Guid(ticket.Name);
KUser user = UserFunctions.GetUserFromUserGuid(userGuid);
if (user != null) Session["User"] = user;
FormsAuthentication.RedirectFromLoginPage(userGuid.ToString(), true);
}
catch (Exception anyException)
{
// don't do anything for now - do something smart later :-) }
}
}
}
}
Finally, here is the code for the login button on my login.aspx page:
protected void Submit_OnClick(object sender, EventArgs e)
{
long userId = 0;
UserAuthenticationStatus status;
status = (UserAuthenticationStatus)UserFunctions.UserAuthenticates(EmailAddress.Text, Password.Text, ref userId);
switch (status)
{
case UserAuthenticationStatus.Authenticated:
//email address and password match, account ok, so log this user in
KUser user = UserFunctions.GetUser(userId);
Session["User"] = user;
if (ChkRememberMe.Checked)
{
GenerateAuthenticationCookie(15, user.UserGuid); // 15 minutes
FormsAuthentication.RedirectFromLoginPage(user.UserGuid.ToString(), true);
}
else
{
FormsAuthentication.RedirectFromLoginPage(user.UserGuid.ToString(), false);
}
break;
case UserAuthenticationStatus.AuthButLocked:
// email/pwd match but account is locked so do something
ShowLockedAccountMessage();
break;
case UserAuthenticationStatus.EmailFoundIncorrectPassword:
case UserAuthenticationStatus.EmailNotFound:
case UserAuthenticationStatus.Unknown:
// either the email wasn't found, or the password was incorrect or there was some other problem
// present message stating this and offer chance to register
ShowFailedLoginMessage();
break;
default:
ShowUnavailableMessage();
break;
}
}
As you can see, there's nothing particularly complex going on, but despite the fact that the authCookie which is created in GenerateAuthenticationCookie(..) being created correctly (as far as I can tell) it does not persist.
One thing I have noticed is that if I place some code into the Application_AuthenticateRequest method in global.asax.cs, such as:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
if (cookie != null)
{
int x = 4; // just a dummy line so that I can set a breakpoint
}
}
...that breakpoint is sometimes hit following a new browser session, although it stops being hit once I navigate away from the startup page (in this case a dummy start.aspx page used purely for dev & testing).
So, apologies for the long question, but I'm truly suffering here.
Things I have checked/tried
Ensuring that the code is being executed - YES
Browser settings - i.e. no deletion of cookies on exit - CONFIRMED NO DELETION
Trying different timeouts - e.g. equal or different to the web.config timeout, doesn't seem to matter...
...and of course at least twenty or thirty different previous questions, but to no avail.
System/Dev Environment
Windows 7 64-bit, VS2010 (but proj is a 3.5), SQL Server 2008 Express.
On my dev server, this problem remains so I'm not sure it's necessarily environmental - that machine is a WS2008R2 box running SQL 2008R2 - and the same problem remains.
Does anyone, anywhere, have any ideas for things I can try here? I have a hunch I could get this working by intercepting the initial Application_AuthenticateRequest hit in global.asax.cs and stuffing something into session state to mark as 'authenticated' (to avoid an expensive authentication attempt every time that method is called, which turns out to be several times per page.
Thanks in advance,
John
OK, having spent all that time writing that, I had a moment of clarity and realised that (a) I didn't need to be doing any of that checking on the Page_Load() as (if this were working properly) the login.aspx page wouldn't be called at all, and (b) I ought to have been able to get to the cookie from the Session_Start - which is where I relocated the TryAutoLogin code.
This in itself was a step forward, but despite retrieving the cookie and therefore the user guid from it, I found that by I was still getting punted back to the login.aspx page.
It was at this point I recalled the fact that I have a parent master page and two child master pages - one which I set for non-authentication pages (e.g. homepage) and one for those pages requiring authentication. I vaguely recalled a problem with session timeouts and had placed the following code in the OnInit override:
if (Session["User"] == null)
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
Response.End();
}
...which in itself wasn't so bad (and avoided a nasty bug on timeouts) but also on the start.aspx page, I found this gem:
Session.Clear();
...in the Page_Load!
So, what was happening was that I was inadvertently clearing the session into which I had placed my newly recovered user record. Which meant that the authorisation master page's OnInit override was then detecting the absence of the user object and - ta dah! - signing the user out, which in turn removes the authorisation cookie...
So, a bit of wiring and some sleuthing later, and I can put this one to bed.
Thanks for reading (even if I did figure it out on my own)... :)