I have a .NET Webforms site thanks needs to post to my MVC Application which currently sits inside the Webform site as a separate application.
The Webform application need to POST some sensitive values to the MVC Application.
Is there a way to generate a AntiForgeryToken() in my WebForms Application so it can be passed with the form post.
Otherwise does anyone know of any other custom anti forgery code that will allow me to do something similar to the MVC's AntiForgeryValidation.
Implementing it yourself is not too difficult.
Generate a GUID
Put it in a hidden field
Also put it in Session or Cookie (in the latter case, with some anti-tamper protection)
At the start of processing the form compare the field and stored token.
(If you look at the implementation of MVC, there is very little more to it. A few helper methods is all you need.)
This is an old question, but the latest Visual Studio 2012 ASP.NET template for web forms includes anti CSRF code baked into the master page. If you don't have the templates, here's the code it generates:
Protected Sub Page_Init(sender As Object, e As System.EventArgs)
' The code below helps to protect against XSRF attacks
Dim requestCookie As HttpCookie = Request.Cookies(AntiXsrfTokenKey)
Dim requestCookieGuidValue As Guid
If ((Not requestCookie Is Nothing) AndAlso Guid.TryParse(requestCookie.Value, requestCookieGuidValue)) Then
' Use the Anti-XSRF token from the cookie
_antiXsrfTokenValue = requestCookie.Value
Page.ViewStateUserKey = _antiXsrfTokenValue
Else
' Generate a new Anti-XSRF token and save to the cookie
_antiXsrfTokenValue = Guid.NewGuid().ToString("N")
Page.ViewStateUserKey = _antiXsrfTokenValue
Dim responseCookie As HttpCookie = New HttpCookie(AntiXsrfTokenKey) With {.HttpOnly = True, .Value = _antiXsrfTokenValue}
If (FormsAuthentication.RequireSSL And Request.IsSecureConnection) Then
responseCookie.Secure = True
End If
Response.Cookies.Set(responseCookie)
End If
AddHandler Page.PreLoad, AddressOf master_Page_PreLoad
End Sub
Private Sub master_Page_PreLoad(sender As Object, e As System.EventArgs)
If (Not IsPostBack) Then
' Set Anti-XSRF token
ViewState(AntiXsrfTokenKey) = Page.ViewStateUserKey
ViewState(AntiXsrfUserNameKey) = If(Context.User.Identity.Name, String.Empty)
Else
' Validate the Anti-XSRF token
If (Not DirectCast(ViewState(AntiXsrfTokenKey), String) = _antiXsrfTokenValue _
Or Not DirectCast(ViewState(AntiXsrfUserNameKey), String) = If(Context.User.Identity.Name, String.Empty)) Then
Throw New InvalidOperationException("Validation of Anti-XSRF token failed.")
End If
End If
End Sub
The C# version of Ian Ippolito answer here:
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)
{
// The code below helps to protect against XSRF attacks
var requestCookie = Request.Cookies[AntiXsrfTokenKey];
Guid requestCookieGuidValue;
if (requestCookie != null && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
{
// Use the Anti-XSRF token from the cookie
_antiXsrfTokenValue = requestCookie.Value;
Page.ViewStateUserKey = _antiXsrfTokenValue;
}
else
{
// Generate a new Anti-XSRF token and save to the cookie
_antiXsrfTokenValue = Guid.NewGuid().ToString("N");
Page.ViewStateUserKey = _antiXsrfTokenValue;
var responseCookie = new HttpCookie(AntiXsrfTokenKey)
{
HttpOnly = true,
Value = _antiXsrfTokenValue
};
if (FormsAuthentication.RequireSSL && Request.IsSecureConnection)
{
responseCookie.Secure = true;
}
Response.Cookies.Set(responseCookie);
}
Page.PreLoad += master_Page_PreLoad;
}
protected void master_Page_PreLoad(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Set Anti-XSRF token
ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
ViewState[AntiXsrfUserNameKey] = Context.User.Identity.Name ?? String.Empty;
}
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.");
}
}
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
WebForms has a pretty similar analog in Page.ViewStateUserKey. By setting that to a per-user value (most choose HttpSessionState.SessionId), WebForms will validate the ViewState1 as part of the MAC check.
overrides OnInit(EventArgs e) {
base.OnInit(e);
ViewStateUserKey = Session.SessionId;
}
1 There are scenarios where ViewStateUserKey will not help. Mainly, they boil down to doing dangerous things with GET requests (or in Page_Load without checking IsPostback), or disabling ViewStateMAC.
You can use reflection to get at the MVC methods used to set the cookie and matching form input used for the MVC validation. That way you can have an MVC action with [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken] attributes that you can post to from a WebForms generated page.
See this answer: Using an MVC HtmlHelper from a WebForm
Related
I have a multi-tenant website (e.g. several different sites, each with it's own domain, all in the same project, separated using MVC areas), where the authentication cookie has the domain manually set when the user logs in so that it is available to all subdomains (but not the various other sites in the project, this is not an SSO).
So a user logins a x.foo.com, the cookie domain is set for foo.com, so that it also works at y.foo.com and z.foo.com. However, because of the other domains being served from the same project the auth cookie domain cannot be set in the web.config in the usual manner, instead it is set manually when the user logins in like so:
public HttpCookie GetAuthenticationCookie(string username)
{
var cookieDomain = UrlHelper.GetTopAndSecondLevelDomain();
var authenticationCookie = FormsAuthentication.GetAuthCookie(username, false);
authenticationCookie.Domain = cookieDomain;
return authenticationCookie;
}
This works fine, but of course can cause a problem when the cookie is automatically refreshed for sliding expiration. So we have an HTTP module which is hooked into the PostRequestHandlerExecute event of our MVC app to look for auth cookies that were set into the response during the request, and overriding the domain:
public class AuthenticationCookieDomainInterceptorModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += UpdateAuthenticationCookieExpiry;
}
private void UpdateAuthenticationCookieExpiry(object sender, EventArgs e)
{
var app = (HttpApplication) sender;
var cookieDomain = UrlHelper.GetTopAndSecondLevelDomain();
var authenticationCookie = GetCookieFromResponse(app.Context.Response, FormsAuthentication.FormsCookieName);
if (authenticationCookie != null)
{
if (authenticationCookie.Domain == null || !string.Equals(authenticationCookie.Domain, cookieDomain, StringComparison.InvariantCultureIgnoreCase))
{
authenticationCookie.Domain = cookieDomain;
}
}
}
private HttpCookie GetCookieFromResponse(HttpResponse response, string cookieName)
{
var cookies = response.Cookies;
for (var i = 0; i < cookies.Count; i++) {
if (cookies[i].Name == cookieName)
{
return cookies[i];
}
}
return null;
}
}
This is also works fine unless the request is to our ServiceStack front end which we use to handle our AJAX requests. In that case the module fires as normal, picks up the cookie if its been set, changes the domain as it should, but when the response is sent back to the client the changes to the cookie are ignored.
Is there any reason why the cookie changes wouldn't be saved to the response in this scenario? My guess would be something to do with the fact ServiceStack uses an HttpHandler to hook in the request cycle in the first place, so we are not going through the normal MVC request life-cycle.
[This question relates to ASP.NET MVC4, and it is about best-practice approach - so please, don't suggest hacks.]
I want to authenticate users using an auth token sent in the request URL. It works similarly to a password reset token, except in this case it does not go to a reset page but instead grants access to some portion of the site. The idea is to send the URL with the auth token to a verified email address of the user. Users can click the link and perform some actions without typing their password.
Out-of-the-box, ASP.NET has the [Authorize] attribute and the SimpleMembershipProvider - these seem to work great, but they do some voodoo magic under the hood (like auto-generating database tables), so I don't know how to extend them to add this link-based auth token.
I don't expect an exact answer, but please do point me to the right direction.
Thanks!
Uf, broad question. But I will try at least to direct you to a right direction.
So first if suggest that you use Forms Authentication as a base, but you will have to customize using of it. And I presume that you do not want to use cookies for the authentication as this is native behaviour of the Forms Authentication.
The most important point you should consider to have it you custom query string token based authentication.
Create a login action and in this action you will authorize the user, if he have granted access you ask FormsAuthentication to create AuthCookie. For the further on you just take the httpCookie.Value as your auth token that you will carry in query string.
You need to implement the Application_BeginRequest in the Global.asax that will handle this query string tokens and translate it into the cookie. With this approach you can leverage all the ASP.NET Forms Authentication infrastructure.
This is quite high level picture w/o code. If you need more detail help I can also provide it to you.
You should just use a regular Action that accepts HttpGet.
Upon receiving the token, immediately invalid it so it can't be used again.
Also, only accept tokens that are within your pre-defined range of time period, like 24 or 72 hours.
Thank you Peter for idea.
If smb need to create JWT token authorization for old ASP.NET MVC5.I wrote small example. I don't serialize cookie to JWT. I create a JWT and after I am checking it in the BeginRequest. If everything is ok, I create a cookie and set it to the httpContext.Request. I used authentication mode="Forms" for application and it require cookies.
For create JWT token:
const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
[AllowAnonymous]
[HttpPost]
public ActionResult LoginJWT(LoginViewModel model)
{
ActionResult response = null;
if (ModelState.IsValid)
{
if (true) //todo: check user login&password
{
var payload = new Dictionary<string, object>
{
{ "iss", "subject" },
{ "sub", "api" },
{ "exp", DateTimeOffset.UtcNow.AddHours(2).ToUnixTimeSeconds()},
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds()},
{ "jti", Guid.NewGuid() },
{ "uid", "64" } //custom field for identificate user
};
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(payload, secret);
response = Content(token);
}
else
{
response = new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Login or password are not found");
}
}
else
{
response = new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Errors in Model");
}
return response;
}
For check JWT token in Global.asax:
public override void Init()
{
this.BeginRequest += this.BeginRequestHandler;
base.Init();
}
private void BeginRequestHandler(object sender, EventArgs e)
{
var bearerToken = this.Context.Request.Headers["Authorization"];
if (bearerToken != null)
{
var token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
int userId = 0;
try
{
IJsonSerializer serializer = new JsonNetSerializer();
var provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
var json = decoder.DecodeToObject<IDictionary<string, string>>(token, secret, verify: true);
if (json.TryGetValue("uid", out var uid))
{
userId = Convert.ToInt32(uid);
}
}
catch (TokenExpiredException)
{
Console.WriteLine("Token has expired");
}
catch (SignatureVerificationException)
{
Console.WriteLine("Token has invalid signature");
}
if (userId != 0)
{
// check user by id, if found create cookie.
}
}
}
I used:
jwt-dotnet/jwt library 7.2.1
I have 2 websites, actually they are the same. The purpose is in that one of them is for internet users and the second one is for local use.
Now they are hosted on the same IIS server on my localhost machine.
When I open this two websites and trying to get action result which is marked with [ValidateAntiForgeryToken] I have an issue that in my cookies i have cookies for my localhost site and there is a cookie with name "RequestVerificationToken_Lw" which is anti-forgery protection key.
And the problem is in that both sites are using the same cookie to store this key. And so if did smth on one web site I get anti-forgery error when I'm trying to do smth on the other.
How can I change cookie domain or any other solutions to split cookies?
Thank You!
Well, lets see what the ValidateAntiforgeryTokenAttribute does (Reflector/ILSpy is your friend) :
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
if (httpCookie == null || string.IsNullOrEmpty(httpCookie.Value))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
AntiForgeryData antiForgeryData = this.Serializer.Deserialize(httpCookie.Value);
string text = filterContext.HttpContext.Request.Form[antiForgeryTokenName];
if (string.IsNullOrEmpty(text))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
AntiForgeryData antiForgeryData2 = this.Serializer.Deserialize(text);
if (!string.Equals(antiForgeryData.Value, antiForgeryData2.Value, StringComparison.Ordinal))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
string username = AntiForgeryData.GetUsername(filterContext.HttpContext.User);
if (!string.Equals(antiForgeryData2.Username, username, StringComparison.OrdinalIgnoreCase))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
if (!this.ValidateFormToken(antiForgeryData2))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
}
Okay, it is obvious, that the cookie name for the token is made from application path:
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
So, you create your own filter, just copy-paste this code, and change that to respect also port (or whatever by what you distinguish your applications):
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath + filterContext.HttpContext.Request.Url.Port);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
This way the cookie name ("RequestVerificationToken_Lw") will vary by port too.
And of course we cant forget to change this cookie name while creating the token too. Unfortunately, you will need to copy-paste "re-implement" 2 things here - first the AntiForgeryToken extension method to call your own AntiForgeryWorker, and then the AntiForgeryWorker itself - just override the method GetAntiForgeryTokenAndSetCookie, it is the same stuff as before :
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(httpContext.Request.ApplicationPath);
Well, it seems a mess, and its definitely not a DRY solution, but if you really really want this, you can have it done in few minutes. Just use reflector and copy-paste :)
I'm using MvcRecaptcha to prevent bot posts for a complex unauthenticated client form on an ASP.NET MVC 2.0 site.
I only want to require one correct CAPTCHA entry from an unauthenticated client, even if some of the form's inputs are incorrect.
I have tried using a Session["CaptchaSuccess"] = true; variable to suppress Html.GenerateCaptcha() in my view following a successful entry, but the presence of the [CaptchaValidator] attribute on my [HttpPost] view causes an error because it naturally requires some ReCaptcha form inputs.
What is the simplest way to achieve this reliably, including on mobile browsers?
Solved by modifying the [CaptchaValidatorAttribute] OnActionExecuting method, where CaptchaSuccessFieldKey refers to the constant string value "CaptchaSuccess":
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
bool? bCaptchaSuccess = filterContext.HttpContext.Session[CaptchaSuccessFieldKey] as bool?;
if (bCaptchaSuccess.HasValue && bCaptchaSuccess.Value)
{
filterContext.ActionParameters["captchaValid"] = true;
}
else
{
var captchaChallengeValue = filterContext.HttpContext.Request.Form[ChallengeFieldKey];
var captchaResponseValue = filterContext.HttpContext.Request.Form[ResponseFieldKey];
var captchaValidtor = new Recaptcha.RecaptchaValidator
{
PrivateKey = ConfigurationManager.AppSettings["ReCaptchaPrivateKey"],
RemoteIP = filterContext.HttpContext.Request.UserHostAddress,
Challenge = captchaChallengeValue,
Response = captchaResponseValue
};
var recaptchaResponse = captchaValidtor.Validate();
// this will push the result value into a parameter in our Action
filterContext.ActionParameters["captchaValid"] = recaptchaResponse.IsValid;
}
base.OnActionExecuting(filterContext);
// Add string to Trace for testing
//filterContext.HttpContext.Trace.Write("Log: OnActionExecuting", String.Format("Calling {0}", filterContext.ActionDescriptor.ActionName));
}
I am using the following code to set a cookie in my asp.net mvc(C#) application:
public static void SetValue(string key, string value, DateTime expires)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
HttpCookie cookie = new HttpCookie(key, value) { Expires = expires };
_response.Cookies.Set(cookie);
}
I need to delete the cookies when the user clicks logout. The set cookie is not removing/deleting with Clear/Remove. The code is as below:
public static void Clear()
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
_request.Cookies.Clear();
_response.Cookies.Clear();
}
public static void Remove(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
if (_request.Cookies[key] != null)
{
_request.Cookies.Remove(key);
}
if (_response.Cookies[key] != null)
{
_response.Cookies.Remove(key);
}
}
I have tried both the above functions, but still the cookie exists when i try to check exist.
public static bool Exists(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
return _request.Cookies[key] != null;
}
What may be problem here? or whats the thing i need to do to remove/delete the cookie?
Clearing the cookies of the response doesn't instruct the browser to clear the cookie, it merely does not send the cookie back to the browser. To instruct the browser to clear the cookie you need to tell it the cookie has expired, e.g.
public static void Clear(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_response = httpContext.Response;
HttpCookie cookie = new HttpCookie(key)
{
Expires = DateTime.Now.AddDays(-1) // or any other time in the past
};
_response.Cookies.Set(cookie);
}
The Cookies collection in the Request and Response objects aren't proxies for the cookies in the browser, they're a set of what cookies the browser sends you and you send back. If you remove a cookie from the request it's entirely server side, and if you have no cookies in the response you're just not going to send any thing back to the client, which won't change the set of cookies in the browser at all.
To delete a cookie, make sure that it is in the response cookie collection, but has an expiration time in the past.
Just to add something else I also pass the value back as null e.g.
public static void RemoveCookie(string cookieName)
{
if (HttpContext.Current.Response.Cookies[cookieName] != null)
{
HttpContext.Current.Response.Cookies[cookieName].Value = null;
HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Now.AddMonths(-1);
}
}
The best way to implement this is to use a tool like Reflector and see how the System.Web.Security.FormsAuthentication.SignOut method implements removing the authentication cookie.
In Reflector, open up System.Web and navigate to the FormsAuthentication object and find the SignOut method. Right click on it and select "Disassemble" (Choose your language from the menu).
VB.NET
Public Shared Sub SignOut()
FormsAuthentication.Initialize
Dim current As HttpContext = HttpContext.Current
Dim flag As Boolean = current.CookielessHelper.DoesCookieValueExistInOriginal("F"c)
current.CookielessHelper.SetCookieValue("F"c, Nothing)
If (Not CookielessHelperClass.UseCookieless(current, False, FormsAuthentication.CookieMode) OrElse current.Request.Browser.Cookies) Then
Dim str As String = String.Empty
If (current.Request.Browser.Item("supportsEmptyStringInCookieValue") = "false") Then
str = "NoCookie"
End If
Dim cookie As New HttpCookie(FormsAuthentication.FormsCookieName, str)
cookie.HttpOnly = True
cookie.Path = FormsAuthentication._FormsCookiePath
cookie.Expires = New DateTime(&H7CF, 10, 12)
cookie.Secure = FormsAuthentication._RequireSSL
If (Not FormsAuthentication._CookieDomain Is Nothing) Then
cookie.Domain = FormsAuthentication._CookieDomain
End If
current.Response.Cookies.RemoveCookie(FormsAuthentication.FormsCookieName)
current.Response.Cookies.Add(cookie)
End If
If flag Then
current.Response.Redirect(FormsAuthentication.GetLoginPage(Nothing), False)
End If
End Sub
With the above as an example, I was able to create a common method called RemoveCookie() in a shared assembly, code is below:
VB.NET
''' <summary>
''' Method to remove a cookie
''' </summary>
''' <param name="key">Key</param>
''' <remarks></remarks>
Public Shared Sub RemoveCookie(ByVal key As String)
' Encode key for retrieval and remove cookie
With HttpContext.Current
Dim cookie As New HttpCookie(.Server.UrlEncode(key))
If Not IsNothing(cookie) Then
With cookie
.HttpOnly = True
.Expires = New DateTime(&H7CF, 10, 12)
End With
' Remove from server (has no effect on client)
.Response.Cookies.Remove(.Server.UrlEncode(key))
' Add expired cookie to client, effectively removing it
.Response.Cookies.Add(cookie)
End If
End With
End Sub
Having tested this using FireBug and the Cookie add-in for FireBug (in FireFox), I can attest that the cookie immediately gets removed.
Any questions, feel free to message me.
After playing around with this for some time and trying all of the other answers here I discovered that none of the answers here are totally correct.
The part that is correct is that you have to send an expired cookie to effect the deletion. The part that nobody else picked up on (but is demonstrated in the Microsoft code posted by Ed DeGagne) is that the cookie options for the deletion must match exactly the cookie options that were used to set the cookie in the first place.
For example if you originally created the cookie with the HttpOnly option then you must also set this option when deleting the cookie. I expect the exact behavior will vary across browsers and probably over time, so the only safe option that will work long-term is to make sure that all of the cookie options in the deletion response match exactly the cookie options used to create the cookie originally.
Response.Cookies["key"].Expires= DateTime.Now;