We have MVC 4 application which is hosted on Web Farm. Site has 5 applications hosted under one domain. These applications communicate with each other. We are implementing Cross Site Request Forgery for our application. We have added AntiForgeyToken(#Html.AntiForgeryToken()) on Layout page. When we try to post data actions across applications using Ajax request, we are facing below exception-
Exception:
The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.
For Ajax request we have added “__RequestVerificationToken” value into prefilter as shown below-
Client side implementation:
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
if (options.type.toLowerCase() == "post") {
if ($('input[name="__RequestVerificationToken"]').length > 0)
jqXHR.setRequestHeader('__RequestVerificationToken',
$('input[name="__RequestVerificationToken"]').val());
}});
On Server side we validated this token as shown below-
string cookie = "";
Dictionary<string, object> cookieCollection = new Dictionary<string, object>();
foreach (var key in HttpContext.Current.Request.Cookies.AllKeys)
{
cookieCollection.Add(key, (HttpContext.Current.Request.Cookies[key]));
}
var res= cookieCollection.Where(x => x.Key.Contains("RequestVerificationToken")).First();
cookie = ((System.Web.HttpCookie)(res.Value)).Value;
string formToken = Convert.ToString(HttpContext.Current.Request.Headers["__RequestVerificationToken"]);
if(string.IsNullOrEmpty(formToken))
{
//To validate HTTP Post request
AntiForgery.Validate();
}
else
{
//To validate Ajax request
AntiForgery.Validate(cookie, formToken);
}
Other configurations which we have done are as below-
We have machine key in config which is same for all applications as well as on all web servers.
We have set AntiForgeryConfig.CookieName = "__RequestVerificationToken" + "_XYZ" cookie name across all applications which is mandatory to access token across applications.
Changes which we tried to resolve this issue-
We tried to post “__RequestVerificationToken” inside each ajax request’s data so that we can access it using Request.Form but no success on this.
We have verified that Content-Type header is appearing with each request.
Please suggest if you have any other way to implement CSRF functionality for Ajax POST requests across multiple applications.
Related
This question already has answers here:
Accessing Session Using ASP.NET Web API
(13 answers)
Closed 3 years ago.
In my Web api when a user login successfully I set session with some values like
HttpContext.Session.SetObject("CurrentUserID", user.Id);
HttpContext.Session.SetObject("CurrentUserRoles",user.Roles);
and just return token and some values to save in cookie
return Ok(new
{
Id = user.Id,
Username = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Token = tokenString,
role = user.Roles
});
But when the client hit api action which has this line
List<string> userRolesList = HttpContext.Session.GetObject<List<string>>("CurrentUserRoles");
Then always get null value even I have added session inside Startup >Configure
like
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
and ConfigureService also
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds( 60 * 60);
options.Cookie.HttpOnly = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
but does work still... Please help.
HTTP is a stateless protocol. Sessions are fake state, enabled by both a server-side and client-side component. The client-side component is a cookie: specifically a Set-Cookie response header. In order for the session to be restored on the next request, the value of this Set-Cookie response header must be sent back via the Cookie request header with each request. A web browser (the client) will do all this automatically, including persisting the cookie locally. However, a thin client like HttpClient, Postman, etc. will not. You would need to independently persist the cookie from the response header and then attach it to each request via the Cookie header in order to maintain the session between requests.
That said, this is a major reason why APIs typically do not, and honestly should not make use of sessions. It's simply a pattern that doesn't make much sense in an API context, and only adds a potential point of failure, since clients must pay attention to the cookie headers, and take manual actions to handle the cookies.
My project contains an ASP.NET MVC application (with no authentication) and two Web APIs.
The first API (Account API) is built on top of Owin middleware, using ASP.NET identity, in order to have a token-based authentication for ASP.NET MVC.
This works very fine for ASP.NET MVC.
But the second API is a data handler API (Data API).
The problem is that I need to authorize the incoming requests on Data API. From the ASP.NET MVC application, I have the following request.
Start Edit 1
// The MVC controller makes a request in order to authenticate the user.
// If the authentication succeeds, an access token is returned.
var authorizationUrl = "my.authorization.api";
HttpResponseMessage result = httpClient.PostAsync(authorizationUrl, content).Result;
// In the Home controller I have these lines of code.
var user = ((ClaimsPrincipal) HttpContext.Current.User);
var token = x.Claims.First(t => t.Type == "AccessToken").Value;
// Now I just need to call the Data API.
var dataUrl = "my.data.api/home";
var request = WebRequest.Create(dataUrl) as HttpWebRequest;
request.Method = WebRequestMethods.Http.Get;
request.ContentType = "application/json";
request.Headers.Add("Authorization", "Bearer " + accessToken);
var response = (HttpWebResponse) request.GetResponse();
The data API has an [Authorize] attribute on HomeController and I receive 401 Unauthorized every time I call any method.
End Edit 1
The questions is: how can I validate the incoming request (on Data API) using the accessToken from request's header (sent from MVC application)?
Thank you!
We have a legacy system written in Core MVC 1, using IdentityServer4 for API access and Identity for user management. The site includes a set of API controllers as well, which a mobile application connects to for authentication and data.
We have been having general stability issues, which we have not been able to get to the bottom of. We have decided to upgrade the system to the latest version of MVC Core and in the process IdentityServer4 requires an upgrade.
The problem is that the authentication pipeline has changed dramatically between versions (Core MVC 1 - 2 and Identity 1 - 2) and we are unable to determine a configuration that works.
In short we need:
Cookie Authentication for web site access
OAuth 2 password grant flow for app access
However, despite this setup working on the legacy version, it does not seem to want to play ball on the newer setup. It seems we can have one or the other, but not both. There doesn't appear to be any example projects available anywhere that demonstrate such a setup.
I understand this setup is not ideal in that these systems should be split out, and I am going to be making a recommendation as such. I have seen hints of routing api requests through a pipeline setup for Bearer authentication using MapFrom but haven't managed to determine a working setup.
UPDATE: Startup.cs
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
var AuthServerConfig = new IdentityServerConfig(Configuration.GetSection("IdentityServer"));
var IdentityCert = AuthServerConfig.GetCerttificate();
var IdentityConfig = services.AddIdentityServer()
.AddInMemoryIdentityResources(AuthServerConfig.GetIdentityResources())
.AddInMemoryApiResources(AuthServerConfig.GetApiResources())
.AddInMemoryClients(AuthServerConfig.GetClients())
.AddAspNetIdentity<ApplicationUser>();
//to secure the API
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = AuthServerConfig.Settings.RootUrl;
options.RequireHttpsMetadata = false;
options.ApiName = AuthServerConfig.Settings.Scope;
});
And in the Configure Method we have:
app.UseIdentityServer();
app.UseAuthentication();
The stage we are at now is that IdentityServer seems to be operational in that a token can be requested. You can call into an API endpoint and gain access so long as that endpoint had the following attribute:
[Authorize(AuthenticationSchemes = "Bearer")]
However, we want the API to be authenticated using both Identity Cookies as well as Bearer tokens, as there is a swagger UI for querying the API when logged in.
Using just the [Authorize] attribute will allow it to be accessed via cookies, but not access tokens through Postman (401)
Not sure if this is solved but what i personally done to enable API to be authenticated via cookie and bearer token is implement both schemes:
e.g.
services.AddAuthentication()
.AddCookie(options => {
options.LoginPath = "/Account/Unauthorized/";
options.AccessDeniedPath = "/Account/Forbidden/";
})
.AddJwtBearer(options => {
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
In the api controller i use [Authorize(AuthenticationSchemes = AuthSchemes)]
[Authorize(AuthenticationSchemes = AuthSchemes)]
public class TodoController: Controller
More details on different schemes and usages of them you can have a look here:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?tabs=aspnetcore2x
Hope that helps.
I have been working on a project with webapi 2 using oauth 2 (openid connect to be precise) bearer tokens to grant access. Now the whole idea is that the bearer tokens are only secure if used with a secure connection.
Until now I have simply not allowed http calls to the webserver which kinda worked since no one could do a http call with a bearer token.
We now have some endpoints that need to be avaible over http (no bearer token/authenticaiton required) and we are going to enable http of course. Now my question is, what is normal in these situations?
Would I have an attribute that I can put on all actions that only accept https?
Can I make that the default behaviour and only put attribute on those that are okay on http?
What is the advice on, is it our responsibility that no one use a oauth token over a non secure line or is the user of the api ?
I believe the right way to do this is to add global action filter which forces you to use HTTPs on all controllers/actions on your Web API. The implementation for this HTTPs action filter can be as the below:
public class ForceHttpsAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var request = actionContext.Request;
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
var html = "<p>Https is required</p>";
if (request.Method.Method == "GET")
{
actionContext.Response = request.CreateResponse(HttpStatusCode.Found);
actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
UriBuilder httpsNewUri = new UriBuilder(request.RequestUri);
httpsNewUri.Scheme = Uri.UriSchemeHttps;
httpsNewUri.Port = 443;
actionContext.Response.Headers.Location = httpsNewUri.Uri;
}
else
{
actionContext.Response = request.CreateResponse(HttpStatusCode.NotFound);
actionContext.Response.Content = new StringContent(html, Encoding.UTF8, "text/html");
}
}
}
}
Now you want to register this globally on WebApiConfig class as the below:
config.Filters.Add(new ForceHttpsAttribute());
As I understand from your question, the number of controllers you want to call them over https are greater than controllers over http, so you want to add override attribute to those http controllers [OverrideActionFiltersAttribute]
Do not forget to attribute your anonymous controllers with [AllowAnonymous] attribute.
But my recommendation is to keep all the communication over https and you just allow anonymos calls.
You can read more about enforcing https on my blog here: http://bitoftech.net/2013/12/03/enforce-https-asp-net-web-api-basic-authentication/
Hope this helps.
Firstly I think you definitely have to make best efforts to ensure the security of that token and so the server should enforce SSL.
We are using web api v1 (infrastructure restrictions :() and we have a global DelegatingHandler that enforces SSL on all requests except for certain uris that are on a whitelist (not the prettiest solution but it works for now).
In web api 2 I reckon you could have a global FilterAttribute to enforce the SSL connectivity and then use the new attribute override feature http://dotnet.dzone.com/articles/new-filter-overrides-feature to create your exceptions - all theory though ! :)
Hope that helps
Garrett
I'm trying to send requests to my MVC3 app, I've tried regular WebRequest, I'm trying it with RestSharp applying correct Authenticator, but it still returns the redirect result of login page?
What am i doing wrong?
upd: How should I do forms authentication with RestSharp? I guess it's possible somehow - just need to play around that cookie...
If you are getting redirected to a login page your mvc 3 app must be setup for forms authentication. Forms authentication will want a cookie sent with the request. If you are using the basic authenticator in RestSharp this will not work. I assume that you are using the MVC controller to provide a REST API that you are trying to call.
One option is to upgrade to MVC 4 and use the ASP.NET Web API to develop your REST API's. The authorization behavior is a little different in an ASP.NET Web API in that it will return an HTTP 401 error instead of doing a redirect. And you can customize the AuthorizationAttribute to pull the information out of the HTTP header for basic authentication and authorization.
Another option is if the action on the controller does not require authentication/authorization you can put the AllowAnonymousAttribute on the method.
To pass the Forms authentication you gotta get the cookie and stick it to RestSharp's cookie container. To get the cookie you can use just regular WebRequest.
private Cookie GetAuthCookie(string user, string pass)
{
var http = WebRequest.Create(_baseUrl+"Users/Login") as HttpWebRequest;
http.AllowAutoRedirect = false;
http.Method = "POST";
http.ContentType = "application/x-www-form-urlencoded";
http.CookieContainer = new CookieContainer();
var postData = "UserName=" + user + "&Password=" + pass + "&RememberMe=true&RememberMe=false&ReturnUrl=www.google.com";
byte[] dataBytes = System.Text.Encoding.UTF8.GetBytes(postData);
http.ContentLength = dataBytes.Length;
using (var postStream = http.GetRequestStream())
{
postStream.Write(dataBytes, 0, dataBytes.Length);
}
var httpResponse = http.GetResponse() as HttpWebResponse;
return httpResponse.Cookies[FormsAuthentication.FormsCookieName];
}