How to use Anti-Forgery Token With ASP.NET Web API without ASP.NET MVC?
Stephen Walther has this article of "Preventing Cross-Site Request Forgery Attacks with ASP.NET MVC" in http://stephenwalther.com/archive/2013/03/05/security-issues-with-single-page-apps ... but his solution includes MVC/Razor and in my front-end I don't planning to include it. And there are abundance articles like it, which the solution is adding #Html.AntiForgeryToken() but this cannot be my solution.
Later, I solved another issue, "the Same Origin policy": http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api, Could this be the solution to prevent the CSRF as well? I don't think so.
My problem was that I did not want to use MVC and just serve static html files backed by a WebApi. Here is what I did (Would this work?) Create a Http module that sets a random cookie value when serving any static file. For example:
public class XSRFModule : IHttpModule {
...
void context_EndRequest(object sender, EventArgs e) {
if (Path.GetExtension(HttpContext.Current.Request.Path) == ".html") {
HttpContext.Current.Response.Cookies.Add(new HttpCookie("XSRF-TOKEN", Guid.NewGuid().ToString()));
}
}
}
Then in your html page, use javascript to add the cookie value to a header when calling your api:
function callApi() {
xhr = new XMLHttpRequest();
xhr.open("GET", "api/data", true);
var regex = /\b(?:XSRF-TOKEN=)(.*?)(?=\s|$)/
var match = regex.exec(document.cookie);
xhr.setRequestHeader("X-XSRF-TOKEN", match[1]);
xhr.send();
}
finally, in your HttpModule, check that the cookie matches the header before processing any calls to your api:
void context_BeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.Path.StartsWith("/api"))
{
string fromCookie = HttpContext.Current.Request.Cookies.Get("XSRF-TOKEN").Value;
string fromHeader = HttpContext.Current.Request.Headers["X-XSRF-TOKEN"];
if (fromCookie != fromHeader)
{
HttpContext.Current.Response.StatusCode = (int)HttpStatusCode.Forbidden;
HttpContext.Current.Response.End();
}
}
}
You need to set the HttpOnly flag to FALSE so that javascript from your domain can read the cookie and set the header. I am not a security expert, so I would like some feedback on this solution from some other members of the community.
EDIT
If you are using OWIN, you can use a global action Filter along with a middleware plugin:
Startup.cs
app.UseStaticFiles(new StaticFileOptions {
OnPrepareResponse = (responseContext) => {
responseContext.OwinContext.Response.Cookies.Append("XSRF-TOKEN", Guid.NewGuid().ToString());
},
FileSystem = "wwwroot"
});
XsrfFilter.cs
public class XsrfFilter : ActionFilterAttribute {
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext) {
string fromCookie = actionContext.Request.Headers.GetCookies("XSRF-TOKEN").FirstOrDefault()["XSRF-TOKEN"].Value;
string fromHeader = actionContext.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
if (fromCookie == fromHeader) return;
actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK);
actionContext.Response.ReasonPhrase = "bad request";
}
}
Related
I have a problem with my Web Api Project.
I have files stored in my Database and want to call them directly in a new window to view/save (URL like : /api/Files/5 - 5 beeing the FileId)
I got everthing working with the Bearer Token for my general AJAX requests with AngularJS for normal Data and it works like a charm. For the file I created a Controller that shows the file in the browser with the corresponding MIME-Type. But now that I changed the action to [Authorize] I get an Access Denied which is correct because I didnt pass an access_token in the HTTP-Header.
I did quite some research if it is possible to pass the Token via the querystring but didn't find anything helpful.
Now my plan is to remove the [Authorize] Attribute from my Controller and try to validate the token myself but I don't know how.
Anyone know how I can get it to work?
I implemented bearer token authentication in my app (AngularJS, WebAPI 2) and I had similar problem - I needed to allow downloading files by clicking on a link. When you click on a link headers are not sent. :(
So, I sent the token value in a query string to download a file
.../mywebapp/api/files/getfile/3?access_token=jaCOTrGsaak6Sk0CpPc1...
and set "Authorization" header to the token value in Startup.Auth.cs. Here is the code:
public void ConfigureAuth(IAppBuilder app)
{
//It needs for file downloads
app.Use(async (context, next) =>
{
if (context.Request.QueryString.HasValue)
{
if (string.IsNullOrWhiteSpace(context.Request.Headers.Get("Authorization")))
{
var queryString = HttpUtility.ParseQueryString(context.Request.QueryString.Value);
string token = queryString.Get("access_token");
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", new[] { string.Format("Bearer {0}", token) });
}
}
}
await next.Invoke();
});
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
This feature is already built in - I wrote about it here:
http://leastprivilege.com/2013/10/31/retrieving-bearer-tokens-from-alternative-locations-in-katanaowin/
For ASP .Net Core I did something like this based on Forward's answer
Extension Method
public static void UseQueryStringBearerValidation(this IApplicationBuilder app)
{
//It needs for file downloads
app.Use(async (context, next) =>
{
if (context.Request.QueryString.HasValue)
{
if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"].ToString()))
{
var queryString = QueryHelpers.ParseQuery(context.Request.QueryString.Value);
var token = queryString["access_token"].ToString();
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", new[] {$"Bearer {token}"});
}
}
}
await next();
});
}
Usage
StartUp.cs -> Configure() method
app.UseCustomExceptionHandler();
app.UseQueryStringBearerValidation(); // <-- add before Jwt Handler
app.UseCustomJwtBearerValidation();
app.AddHttpContextProperties();
app.UseStaticFiles();
app.UseMvc(MiddlewareAppConfiguration.AddRouteMappings);
Although I'm not sure it's a very good idea, you could implementing a DelegatingHandler to achieve what you are looking for.
public class QueryStringBearerToken : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var bearerToken = request.GetQueryNameValuePairs()
.Where(kvp => kvp.Key == "bearerToken")
.Select(kvp => kvp.Value)
.FirstOrDefault();
if(!String.IsNullOrEmpty(bearerToken))
{
request.Headers.Add("Authorization", "Bearer " + bearerToken);
}
return base.SendAsync(request, cancellationToken);
}
}
This handler will look for the query string named "bearerToken" and, if it exists, will add it to the request header for the subsequent handlers / filter to process. You might want to check first if the header is already present and not override in this case. You can add this handler in your configuration phase in the usual fashion:
config.MessageHandlers.Insert(0, new QueryStringBearerToken ());
A request for /YourRoute?bearerToken=theToken will pass in the DelegatingHandler, adding the token passed in the query string to the list of headers in the original request and the regular Bearer Token authentication will look for the header and find it.
Scenario
Lotus Domino form with a button that made an Ajax call to an Xpage, that do some stuff (read a properties file).
Framework: prototype.js
Button code:
var now = new Date()
var n = $H({
........
now: now.getTime()
});
var url = "/" + $F("path") + "/myxpages.xsp";
var myAjax = new Ajax.Request(
url,
{
method: 'post',
parameters: n.toQueryString(),
onComplete: function(response) {
ajaxResult = response.responseText;
}
});
Xpage
myxpages.xsp has this SSJS code on afterPageLoad event
var request = facesContext.getExternalContext().getRequest();
var response = facesContext.getExternalContext().getResponse();
response.setHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
com.org.MyGetProperties.readProperties(request,response);
MyGetProperties class
This class is deployed in WebContent/WEB-INF/classes
public class MyGetProperties {
static PrintWriter out = null;
public static synchronized void readProperties(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
*(DO SOME STUFF HERE)*
out = new PrintWriter(response.getWriter());
// return result
out.println("OK");
} catch (Exception e) {..}
}
}
Sometimes ajaxResult variable in ajax response call is empty, sometimes is "OK", as expected (seems to be something related to cache, but i think i've managed it correctly).
The behavior is different on different production server, i don't know if depends on server configurations.
Could be a PrintWriter problem?
Short answer: don't. Long answer: use the Ajax control. You put that on your page and your URL changes to myxpages.xsp/nameyougavetheajaxcontrolproperty
This way you can be sure not to run foul of any cached result or pending operation. It also has a property where you can specify a Java class directly. That class extends (need to Google that - answered it on SO before) which gives you direct access to request/response
Update:
You need to tell that you are done:
facesContext.responseComplete();
See my original post on XAgents, the revision and some thought on testing.
Investigating the Web API as part of an MVC 4 project as an alternative way to provide an AJAX-based API. I've extended AuthorizeAttribute for the MVC controllers such that, if an AJAX request is detected, a JSON-formatted error is returned. The Web API returns errors as HTML. Here's the AuthorizeAttribute that I'm using with the MVC controllers:
public class AuthorizeAttribute: System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "area", "" },
{ "controller", "Error" },
{ "action", ( filterContext.HttpContext.Request.IsAjaxRequest() ? "JsonHttp" : "Http" ) },
{ "id", "401" },
});
}
}
How could I reproduce this to provide equivalent functionality for the Web API?
I realize that I need to extend System.Web.Http.AuthorizeAttribute instead of System.Web.Mvc.AuthorizeAttribute but this uses an HttpActionContext rather than an AuthorizationContext and so I'm stuck by my limited knowledge of the Web API and the seemingly incomplete documentation on MSDN.
Am I even correct in thinking that this would be the correct approach?
Would appreciate any guidance.
To get the equivalent functionality in a Web API filter you can set the HttpActionContext.Response property to an instance of HttpResponseMessage that has the right redirect status code and location header:
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) {
var response = new HttpResponseMessage(HttpStatusCode.Redirect);
response.Headers.Location = new Uri("my new location");
actionContext.Response = response;
}
I would very much go with Marcin's answer - at the end of the day, he has written the code!
All I would add is that as Marcin is saying, your best bet is to have a dedicated controller to return the errors as appropriate - rather than setting the response code 401 with JSON content in the attribute.
The main reason is that Web API does the content-negotiation for you and if you want to do it yourself (see if you need to serve JSON or HTML) you lose all that functionality.
I'm experimenting with "configuration-less WIF", where I want to accept a SAML2 token that is generated by Windows Azure's AppFabric STS.
What I'm doing is parsing checking the current request for token information, like so:
if (Request.Form.Get(WSFederationConstants.Parameters.Result) != null)
{
SignInResponseMessage message =
WSFederationMessage.CreateFromFormPost(System.Web.HttpContext.Current.Request) as SignInResponseMessage;
var securityTokenHandlers = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
XmlTextReader xmlReader = new XmlTextReader(
new StringReader(message.Result));
SecurityToken token = securityTokenHandlers.ReadToken(xmlReader);
if (token != null)
{
ClaimsIdentityCollection claims = securityTokenHandlers.ValidateToken(token);
IPrincipal principal = new ClaimsPrincipal(claims);
}
}
The code above uses the SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(); colection to verify and handle the SAML token. However: this does not work because obviously the application has not bee nconfigured correctly. How would I specify the follwing configuration from XML programmaticaly on my securityTokenHandlers collection?
<microsoft.identityModel>
<service>
<audienceUris>
<add value="http://www.someapp.net/" />
</audienceUris>
<federatedAuthentication>
<wsFederation passiveRedirectEnabled="true" issuer="https://rd-test.accesscontrol.appfabriclabs.com/v2/wsfederation" realm="http://www.thisapp.net" requireHttps="false" />
<cookieHandler requireSsl="false" />
</federatedAuthentication>
<applicationService>
<claimTypeRequired>
<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />
<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />
</claimTypeRequired>
</applicationService>
<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<trustedIssuers>
<add thumbprint="XYZ123" name="https://somenamespace.accesscontrol.appfabriclabs.com/" />
</trustedIssuers>
</issuerNameRegistry>
</service>
I was struggling with the same and found a working solution in WIF 3.5/4.0. Since maartenba's link seems to be dead, I wanted to post my solution here.
Our requirements were:
Configuration fully in code (as we ship a default web.config with the app)
Maximum allowed .Net version 4.0 (hence I am using WIF 3.5/4.0)
What I used to arrive at the solution:
Information about dynamic WIF configuration provided by Daniel Wu
here.
This
method
to register HTTP modules at runtime, explained by David Ebbo. I
also tried the more elegant method explained by Rick
Strahl,
but that unfortunately did not do the trick for me.
Edit 2016/09/02: instead of adding a separate "pre application start
code" class as in David Ebbo's example, the WIF-related HTTP modules
can also be registered in the static constructor of the
`HttpApplication' class. I have adapted the code to this somewhat
cleaner solution.
My solution needs nothing in web.config. The bulk of the code is in global.asax.cs. Configuration is hard-coded in this sample:
using System;
using System.IdentityModel.Selectors;
using System.Security.Cryptography.X509Certificates;
using System.Web;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Web;
namespace TestADFS
{
public class SessionAuthenticationModule : Microsoft.IdentityModel.Web.SessionAuthenticationModule
{
protected override void InitializePropertiesFromConfiguration(string serviceName)
{
}
}
public class WSFederationAuthenticationModule : Microsoft.IdentityModel.Web.WSFederationAuthenticationModule
{
protected override void InitializePropertiesFromConfiguration(string serviceName)
{
ServiceConfiguration = FederatedAuthentication.ServiceConfiguration;
PassiveRedirectEnabled = true;
RequireHttps = true;
Issuer = "https://nl-joinadfstest.joinadfstest.local/adfs/ls/";
Realm = "https://67px95j.decos.com/testadfs";
}
}
public class Global : HttpApplication
{
static Global()
{
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(SessionAuthenticationModule));
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(WSFederationAuthenticationModule));
}
protected void Application_Start(object sender, EventArgs e)
{
FederatedAuthentication.ServiceConfigurationCreated += FederatedAuthentication_ServiceConfigurationCreated;
}
internal void FederatedAuthentication_ServiceConfigurationCreated(object sender, Microsoft.IdentityModel.Web.Configuration.ServiceConfigurationCreatedEventArgs e)
{
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection coll = store.Certificates.Find(X509FindType.FindByThumbprint, "245537E9BB2C086D3C880982FA86267FBD66B9A3", false);
if (coll.Count > 0)
e.ServiceConfiguration.ServiceCertificate = coll[0];
store.Close();
AudienceRestriction ar = new AudienceRestriction(AudienceUriMode.Always);
ar.AllowedAudienceUris.Add(new Uri("https://67px95j.decos.com/testadfs"));
e.ServiceConfiguration.AudienceRestriction = ar;
ConfigurationBasedIssuerNameRegistry inr = new ConfigurationBasedIssuerNameRegistry();
inr.AddTrustedIssuer("6C9B96D90257B65B6F181C2478D869473DC359EA", "http://NL-JOINADFSTEST.joinadfstest.local/adfs/services/trust");
e.ServiceConfiguration.IssuerNameRegistry = inr;
e.ServiceConfiguration.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
FederatedAuthentication.WSFederationAuthenticationModule.ServiceConfiguration = FederatedAuthentication.ServiceConfiguration;
}
}
}
Usage
My app is asp.net WebForms, running in classic pipeline mode and supports forms authentication as well as ADFS login. Because of that, authentication is handled in a common base class shared by all .aspx pages:
protected override void OnInit(EventArgs e)
{
if (NeedsAuthentication && !User.Identity.IsAuthenticated)
{
SignInRequestMessage sirm = new SignInRequestMessage(
new Uri("https://nl-joinadfstest.joinadfstest.local/adfs/ls/"),
ApplicationRootUrl)
{
Context = ApplicationRootUrl,
HomeRealm = ApplicationRootUrl
};
Response.Redirect(sirm.WriteQueryString());
}
base.OnInit(e);
}
In this code, ApplicationRootUrl is the application path ending in "/" (the "/" is important in Classic pipeline mode).
As a stable implementation for logout in mixed mode was not so easy, I want to show the code for that as well. Technically it works, but I still have an issue with IE immediately logging in again after logging out an ADFS account:
if (User.Identity.IsAuthenticated)
{
if (User.Identity.AuthenticationType == "Forms")
{
FormsAuthentication.SignOut();
Session.Clear();
Session.Abandon();
ResetCookie(FormsAuthentication.FormsCookieName);
ResetCookie("ASP.NET_SessionId");
Response.Redirect(ApplicationRootUrl + "Default.aspx");
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
else
{
FederatedAuthentication.SessionAuthenticationModule.SignOut();
FederatedAuthentication.SessionAuthenticationModule.DeleteSessionTokenCookie();
Uri uri = new Uri(ApplicationRootUrl + "Default.aspx");
WSFederationAuthenticationModule.FederatedSignOut(
new Uri("https://nl-joinadfstest.joinadfstest.local/adfs/ls/"),
uri); // 1st url is single logout service binding from adfs metadata
}
}
(ResetCookie is a helper function that clears a response cookie and sets its expiration in the past)
Just a thought, no idea whether this works: Isn't there a way to get at the actual XML (which is empty in your case) and modify it at runtime through the classes in Microsoft.IdentityModel.Configuration?
Alternatively, some of the things in the XML you can modify at the time the sign-in request is sent out, in the RedirectingToIdentityProvider event by modifying the SignInRequestMessage
FYI: found a solution and implemented it in a module described (and linked) here: http://blog.maartenballiauw.be/post/2011/02/14/Authenticate-Orchard-users-with-AppFabric-Access-Control-Service.aspx
When i try to get uploaded filename from generic handler (upload.ashx) using session its ok, no problem. I can also use webmethod on samepage and uploadify works great, but Session["fileName"] is getting null. Is there anything wrong on my code? Do i only need to use generic handler to get filename?
[WebMethod(EnableSession = true)]
public void LoadPicture(HttpContext context)
{
try
{
HttpPostedFile file = context.Request.Files["Filedata"];
context.Session["fileName"] = file.FileName;
....................Some resize and save image codes.........
context.Response.Write("1");
}
catch (Exception ex)
{
context.Response.Write("0");
}
}
protected void Button1_Click(object sender, EventArgs e)
{
using (_modelService = new ModelService())
{
ModelEntity _models = new ModelEntity();
......some codes....
_models.modelphoto = Session["fileName"].ToString();
_modelService.ModelAdd(_models);
}
}
Uploadify uses Flash. Flash doesn't send cookies. In ASP.NET sessions are tracked by cookies. So, no session with uploadify, sorry.