I need to be able to support "auxiliary" content on a MVC (4) web site -- images, PDFs, videos, etc. Most of this content is provided by the customer using the site so it should not be accessible to unauthenticated users.
(We're using Forms Authentication to generate an authentication ticket. Note, we are not necessarily using the built-in membership/identity providers, as some of our customers want to use authenticate via other means e.g. a federated identity service.)
I've secured the static content by setting up the site to route/authorize all requests through ASP.NET (via runAllManagedModulesForAllRequests) as described here.
I found that I needed to allow anonymous access to the Content and Scripts folders (via location) ... but this configuration works for most content types.
It does not however work for video content, specifically in Internet Explorer. Media Player comes up but reports that it cannot play the file. "The player might not support the file type or coded that was used to create the file."
(Interestingly enough, there's no error in Chrome or Firefox. The video plays in a new tab.)
I'm fairly certain it's Media Player that is the issue. If I add a location element and allow anonymous access to the video, it plays just fine. And we've had similar problems in the past where the root cause ultimately turned out to be security- / authentication- related.
But obviously I can't change Media Player. And I have to support IE. So ... does anyone know of a way to work around this issue programmatically in ASP.NET MVC? Any help would be appreciated.
I seem to recall seeing an SO post about sending the authentication information not just with HTTP requests. I can't locate that post now. But according to Fiddler the request is in fact an HTTP GET. And we're not sending an authentication header, but we are sending an authentication cookie.
Update:
I thought I would be able to adapt the answer to this question.
It involves appending a flag and the authorization cookie value to the content URL, and hooking in to the Application_BeginRequest event.
In the view:
var asset = ...;
var authToken = Request.Cookies[FormsAuthentication.FormsCookieName].Value;
var assetUri = string.Format("~/Media/{0}?requireAuthSync=true&token={1}",
asset.ID, Url.Encode(authToken));
Then in Global.asax.cs:
protected void Application_BeginRequest()
{
if (!string.IsNullOrEmpty(Context.Request["requireAuthSync"]))
{
AuthCookieSync();
}
}
private void AuthCookieSync()
{
var authParamName = "token";
var authCookieName = FormsAuthentication.FormsCookieName;
try
{
if (!string.IsNullOrEmpty(Context.Request[authParamName]))
{
var authCookieValue = Context.Request.QueryString[authParamName];
var authCookie = Context.Request.Cookies.Get(authCookieName)
?? new HttpCookie(authCookieName);
authCookie.Value = authCookieValue;
Context.Request.Cookies.Set(authCookie);
}
}
catch
{
}
}
Using this approach, the video does in fact play in IE.
However, it's now possible to bookmark the video (or other content) and use the bookmark to access the content without being authenticated ... so the site security is effectively broken.
So it seems, in order to preserve site security, I shouldn't be creating the cookie if it doesn't already exist. But with Media Player, creating the cookie is the only way to get the video to play. I appear to be stuck in a Catch-22.
Any insight would be much appreciated.
so the site security is effectively broken
First of all, the site is not broken. Although a user can tag it, the forms cookie will expire (depending on the token validity time you set when you issue the cookie) and the link will stop to work.
But then, note that you don't really have to pass the forms authentication cookie value as the token in the query string.
You could for example generate one-time GUID-like tokens and the very first time the token is used, it is marked at the server side and cannot be used again. Or even you could somehow encrypt or sign the token validitity time in the token so that the link is valid for a short period of time but the token is independent on the forms cookie but you don't need an auxiliary storage of used tokens.
Related
We have a page with several forms. Each has its own #Html.AntiForgeryToken(). On my local machine everything is great.
We deployed to Azure (PAAS), but the __RequestVerificationToken is not being created on every request. Sometime it is there and sometime I get the The required anti-forgery cookie is not present and rarely I get the tokens do not match error.
I'm completely clueless at this point. I can't figure out if there's something wrong in our code or on Azure environment? No ajax in these forms.
We have added the <machineKey> section to our web.config. No caching. Sometimes it occurs on new devices from the first time.
After spending a significant amount of time with investigation, using a combination of Sentry and Azure web server logs, I've found 2 major causes of the mentioned errors:
1) On mobile phones, when the browser is in the background, it may be abruptly stopped by the OS to free up resources. When this happens, usually, the page is stored on the phone's drive, and reloaded from there once the browser is re-opened.
The problem, however, is that by this time, the Anti-Forgery Token, which is a session cookie, has already expired, since this is essentially a new session. So the page loads without an Anti-Forgery Cookie, using HTML from the previous session. This causes the The required anti-forgery cookie is not present exception.
2) While seemingly related, the tokens do not match exception is usually only tangentially related. The cause seems to be user behaviour of opening multiple tabs at the same time.
The Anti-Forgery Cookie is only assigned when a user arrives to a page with a form on it. This means that they can go to your homepage, and have no anti-forgery cookie. Then they can open multiple tabs using middle-click. The multiple tabs are multiple parallel requests, each of them without an anti-forgery cookie.
As these requests don't have an anti-forgery cookie, for each of them, ASP.NET generates a separate pseudo-random token for their cookie, and uses that in the form; however, only the result of the last header received will be retained. This means that all the other pages will have invalid tokens on the page, since their anti-forgery cookie was overridden.
For a solution, I've created a global filter that should ensure that
The Anti-Forgery cookie is assigned on any page, even if the page has no form, and
The Anti-Forgery cookie is not session-bound. It's lifetime should be adjusted to match the user login token, but it should persist between sessions in case a mobile device reloads the page without the session.
The code below is a FilterAttribute that has to be added inside FilterConfig.cs as a global filter. Please do note that, while I do not believe this would create a security hole, I am by no means a security expert, so any input is welcome.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AntiForgeryFilter : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookie = filterContext.HttpContext.Request.Cookies.Get(AntiForgeryConfig.CookieName);
var addCookie = true;
if (string.IsNullOrEmpty(cookie?.Value))
{
cookie = filterContext.HttpContext.Response.Cookies.Get(AntiForgeryConfig.CookieName);
addCookie = false;
}
if (string.IsNullOrEmpty(cookie?.Value))
{
AntiForgery.GetTokens(null, out string cookieToken, out string _);
cookie = new HttpCookie(AntiForgeryConfig.CookieName, cookieToken)
{
HttpOnly = true,
Secure = AntiForgeryConfig.RequireSsl
};
}
cookie.Expires = DateTime.UtcNow.AddYears(1);
if(addCookie) filterContext.HttpContext.Response.Cookies.Add(cookie);
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
I believe your problem comes from having multiple forms with different anti-forgery tokens on one page.
When page is requested you get two different tokens in forms hidden fields but only one token in the cookies.
Form POST contains mismatching tokens that causes an error.
Try how AFT will work for you if page contains only one form. If it works OK then my assumption is correct.
This answer contains possible solution for page with multiple forms however it has some security drawbacks as explained here.
I'm not sure why everything works OK on your localhost.
I've created simple application and tried form POST with correct cookies token but old Form token from previous session. To my surprise such POST successfully passes.
May be asp.net has some special handling for local requests in this case. I haven't found any info on this.
If my answer still doesn't help you could you please provide following data for further analysis:
Original page request with returned HTTP headers and form anti-forgery tokens.
Form POST request with sent HTTP headers.
In our application we have following setup:
Web Application (Angular JS) + Web API
We support external logins Google & Facebook.
We have setup necessary infrastructure with default scopes and wired-up during the Startup. This works fine for login.
For additional features, say user wants to import his/her contacts from Google, we need to get consent again with new scopes. Can someone let me know how to do this?
One way is - to include all necessary scopes during login phase but we want to get consents only for required stuff at required time.
I googled but couldn't get any information on this.
In our specific case of importing contacts from Google a/c, I thought of specifically creating a new controller and start fresh authentication mechanism using GoogleAuthorizationCodeFlow (without OWIN middleware) as explained here https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth but it is in the context of MVC but we need support for Web API. So any pointer is highly appreciated.
Finally got the solution.
Below links helped a lot; my solution is a combination of ideas from below links:
http://www.yogihosting.com/implementing-google-contacts-api-version-3-0-oauth-2-0-in-csharp-and-asp-net/
(note: Google OAuth links mentioned in this article, especially for token generation, have changed. For correct URLs, please see https://developers.google.com/identity/protocols/OAuth2WebServer)
https://www.themarketingtechnologist.co/google-oauth-2-enable-your-application-to-access-data-from-a-google-user/
Above links explain how to get access_token and authorization_code. Once we have those values, we can create UserCredentials that can be used for accessing specific Google APIs as mentioned in the below link:
Upload video to youtube with mvc application (all code behind)
Things to remember:
One ends up contacting Google authorization server twice (once for access token and once for authorization code). In both the cases, redirect_uri must be specified and IT IS IMPORTANT for that redirect_uri to be SAME in both the cases; otherwise it won't work. Also, it is important for that redirect_uri to be mentioned in the Google console where the project/application is defined and registered. I faced issues because of mismatched redirect_uri values and below link helped me in solving it:
Google API token endpoint POST returns Bad Request 400
While accessing Google API we need to include filters. In my case for People API, I needed to get email addresses and without required filters, it was always null/empty. So, below is the code for that:
var peopleService = new PeopleService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = "MyApp",
});
var connList = peopleService.People.Me.Connections.List();
connList.RequestMaskIncludeField = "person.emailAddresses";
connList.PageSize = 500;
ListConnectionsResponse connectionsResponse = connList.Execute();
IList<Person> connections = connectionsResponse.Connections;
Without connList.RequestMaskIncludeField = "person.emailAddresses"; email address is always empty.
Hope this helps someone.
The very first part of this answer on another question explains how an existing MVC site can very quickly have added to it the ability to expose its data (e.g. to a Winforms app requesting the data), all for a couple of lines of code (without having to convert to WCF/Web API and add extra layers - our project is pretty small and basic):
public JsonResult GetCategoryList()
{
var list = //return list
return Json(list, JsonRequestBehavior.AllowGet);
}
So we've tested the above as a quick and easy solution and it's clearly very nearly working because in the stream we get the html source for our MVC app's login.
And indeed if we add the AllowAnonymous annotation we do get the Json stream that we're after.
However we don't want to allow anonymous, we want some protection. Have tried adding:
Dim nc As New NetworkCredential("username", "password")
request.Credentials = nc
just before firing request.GetResponse but that isn't working (this may be completely ignorant but it seemed worth a shot). When I say it isn't working, I mean we go back to getting the login page's html source in the stream.
So how to allow the winforms app to incude some kind of authentication (which will work) with its request for the data? As I say, getting the data is working (proved by AllowAnonymous).
You should separate the authentication code for the web application (the one returning the login) from the one that you are exposing the API.
Looks like you are using forms authentication for the WebSite part and you should keep it that way. However, in the public API GetCategoryList you should either implement a different authentication strategy with ActionFilters for example.
I've been reading quite a few questions here on SO about securing web api's using api keys, tokens, hmac ect and haven't found the answer I am looking for.
I'm working on a MVC4 web application project with internet and intranet sites, web api and Android/iOS applications.
The Web API is to be used by my applications and nobody else as it will be accessing sensitive data.
What would be the best way of securing this api so only my apps can use it? Something that seems like such a simple request is extremely difficult to get started on.
I've looked at the post here on SO using HMAC and a few others but none of them sounded like they would fit here, more than likely I am just missing something.
Is HMAC the way to go or would client certificates be more appropriate for this situation?
Should I use SSL and some sort of API key?
I know the question is a bit vague, I've been staring at it for over an hour trying to figure out how to word what I am thinking so I figured I would just post it and update if needed... :(
I would be more than happy to provide more details upon request.
Generate a key for each of your apps and have them pass the key in each request as a token. Your server can then verify the key and authenticate the request.
Take a look at the Basic Authentication module from the ASP.NET site. The sample uses 'basic' as the authorization scheme but you can change it use 'token' instead.
private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
var authHeader = request.Headers["Authorization"];
if (authHeader != null)
{
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeaderVal.Scheme.Equals("token",
StringComparison.OrdinalIgnoreCase) &&
authHeaderVal.Parameter != null)
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
}
Once you have the Basic Auth module in place you can simply decorate any actions or controllers with the Authorize attribute and it will forward the request to the Basic Auth handlers.
namespace webapi.Controllers
{
[Authorize]
public class SensitiveDataController : ApiController
{
...
}
}
As far as over the wire you MUST use SSL when using basic authentication as your key will be transmitted in plain text.
You can use FormsAuthentication. Encrypt the ticket and ensure machineKey is the same in both the config files. See this and this. This will allow the same user credentials to be shared between web app and api. ASP.NET FAM module will establish the identity in this case.
For api key, look at hawk scheme. It uses shared symmetric key. However, Hawk is feature-complete and until it reaches version 1.0 it is likely to change. Nonetheless, it will give you a good idea of implementing HMAC-based security. I have a .NET implementation here for Hawk. And there is one from Pablo as well. In this case, you will need to write a message handler to establish the identity for the consuming application.
In a general case for a high traffic app, all the above answer have a flaw that many attackers can easily exploit:
With a jail broken iPhone, you can break SSL - not to your server, but when they have your app on their phone, they can at least analyse the packages you send.
The best way to avoid that (in my opinion) is using 'on time passwords' - real on time passwords.
How can you generate these one time passwords?
A1. Get a device_identifier for each device (this could also just be any random number, but you should avoid collisions with other devices' identifiers)
A2. Have an api_key, that you will use for hashing
Now if you want to send a package to your api, you do the following:
B1. Construct your normal package, here is the example of some json payload:
var payload = {"hello":"world"}
B2. Hash your var hashed_payload = hash(payload) using your favourite hashing function
B3. Generate the one time password for this package:
var otp = hash(salt & hashed_payload & device_token & api_key)
Now you have everything you need, to send to the server:
In the headers, you need to send the otp,salt and device_token as well!
On the server, you will do the same steps marked as B1-3 and compare your hashing result with the one provided by the client. After that you have to make sure that you 'ban' this salt for this device_token in order to avoid replay attacks.
This method still has one flaw but requires much more work from attackers:
They can find your api_key in you compiled code.
I'm working on a similar project where I assign unique API keys to each user or client application accessing my API. I'm not a security expert, but I'd recommend that you use SSL and generate unique API keys for both your Android and iOS applications. With SSL, data being transmitted to your API will be encrypted and protected.
I need to create a website with non standard authorizaion logic (or rather not exactly the site. It should be separate Area in existing ASP.NET MVC3 application). Access to most of the pages sould be available only to authorized users. Authorization is carried out on the token passed in the link. After the user arrived to this area, the token should be checked and if it’s valid site will create a session key for 30 minutes (we already have our own mechanisms of session managment and it should be used).
Workflow example :
Third-party website generates a link for user, e.g. https://example.com/securedPage/?accountId=123456&token=XXXXX
Our site check this token (it depends on the page from URL, in this case https://example.com/securedPage/)
If the token is valid, example.com obtains a session key for the user and stores it in cookies.
Then user continues browsing whole website and only session is checked.
I’m new to MVC framework, so I’d like to ask several questions about architecture.
What is an apropriate place for this logic? ActionInvoker, Global.asax etc.?
Currently I'm trying to create my own ActionInvoker and keep this logic there, but I'm afraid that it could be a wrong way.
If I understand correctly you want yo extend the Action of the controller to inject/check your token.
I think the global action filters should help you.