Dynamic redirect url from google console - oAuth - asp.net-mvc

I have created a MVC/API project to enable external authentication and worked fine for my local host url. However, I need to achieve the below.
I am supporting multi tenancy (same app service and different DB), so each tenant has to connect different DB based on the custom param in the MVC url
Ex: https://localhost/tenant1, .../tenant2, .../tenant3 etc (not going with separate subdomain at this point)
I am not sure if the Google Console supports the wildcard url as a return ur and not sure how to achieve that in MVC code (Ex:http://localhost/* OR {0} .. something like that. (So dynamic input parameter will be returned back from google)
I am reading and attempting some solutions. Will update the answer here once i get the complete solution. In the meantime if anyone has any suggestions, please help me.
UPDATE 1:
I have updated my source code as follows:
Create session object before redirecting to the external login
System.Web.HttpContext.Current.Session["Tenant"] = "tenantname";
After callback read the tenant details and save in the session for subsequent DB calls based on the tenant name
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
if (System.Web.HttpContext.Current.Session["Tenant"] != null)
{
string sessionObj = System.Web.HttpContext.Current.Session["Tenant"] as String;
}

This is a common requirement, and is easily solved. There are two components.
Firstly, regardless of which of your many URLs your application lives at (myapp.com/tenant1, /tenant2, etc) you have a single redirect URL (eg myapp.com/oauthredirect).
Secondly, when starting the OAuth dance (https://developers.google.com/identity/protocols/OAuth2WebServer#redirecting), you can specify a state parameter which will be passed into your oauthredirect routine (eg. as state=tenant1). You can then use this to create a redirect back to the appropriate site URL once you have finished your user registration tasks.
Be careful when specifying your redirect URLs into the developer console. They must be a character-by-character match with the actual URL. So, foe example, you will need to specify both http://myapp.com/oauthredirect and https://myapp.com/oauthredirect. I've always found it quite useful to create a local entry in /etc/hosts (or the windows equivalent) so your localhost is also resolved by eg. http://test.myapp.com

Authorized redirect URIs For use with requests from a web server. This
is the path in your application that users are redirected to after
they have authenticated with Google. The path will be appended with
the authorization code for access. Must have a protocol. Cannot
contain URL fragments or relative paths. Cannot be a public IP
address.
http://localhost/google-api-php-client-samples/Analytics/Oauth2.php
http://localhost/authorize/
You can have as many of them as you want but the wild card is not going to work.

Related

how to verify referrer inside a MVC or Web Api ajax call

my MVC app has common ajax methods (in web api and regular controller). I'd like to authorize these calls based on which area (view) of my app the call is coming from. The problem I am facing is how to verify the origin of the ajax call.
I realize that this is not easily possible since ajax calls are easy to spoof, but since I have full control of how the view gets rendered (full page source) perhaps there is a way to embed anti-forgery type tokens that could later be verified to a Url Referrer.
Authentication is already handled and I can safely verify the identity of the call, the only problem is verifying which URL (MVC route) the call came from. More specifically, preventing the user from being able to spoof the origin of the ajax call.
I tried creating a custom authorization header and passing it between view render and ajax calls, and that works, but still easy to spoof (since a user could sniff the headers from another part of the site and re-use those). In the end I am not sure how to safely verify that the header has not been spoofed. The only thing that comes to mind is encoding some info about the original context inside the token, and validating it somehow against incoming call context (the one that's passing the token in ajax call).
I see that MVC has AntiForgery token capabilities, but I am not sure if that can solve my problem. If so I'd like to know how it could be used to verify that /api/common/update was called from /home/index vs /user/setup (both of these calls are valid).
Again, i'd like a way to verify which page an ajax call is coming from, and user identity is not the issue.
update
as per #Sarathy recommended I tried implementing anti-forgery token. As far as I can tell this works by adding a hidden field with token on each page, and comparing it to a token set in a cookie. Here is my implementation of custom action filter attribute that does token validation:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.RequestContext.HttpContext.Request;
var fToken = req.Headers["X-Request-Verification-Token"];
var cookie = req.Cookies[AntiForgeryConfig.CookieName];
var cToken = cookie != null
? cookie.Value
: "null";
log.Info("filter \ntoken:{0} \ncookie:{1}", fToken, cToken);
AntiForgery.Validate(cToken, fToken);
base.OnActionExecuting(filterContext);
}
then my anti forgery additional data provider looks like this:
public class MyAntiForgeryProvider : IAntiForgeryAdditionalDataProvider
{
public string GetAdditionalData(System.Web.HttpContextBase context)
{
var ad = string.Format("{0}-{1}",context.Request.Url, new Random().Next(9999));
log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.AdditionalData: {0}", ad);
log.Info("antiforgery AntiForgeryProvider.GetAdditionalData Request.UrlReferrer: {0}", context.Request.UrlReferrer);
return ad;
}
public bool ValidateAdditionalData(System.Web.HttpContextBase context, string additionalData)
{
log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData Request.Url: {0}", context.Request.Url);
log.Info("antiforgery AntiForgeryProvider.ValidateAdditionalData additionalData: {0}", additionalData);
return true;
}
this works, in that i can see correct pages logged in the provider, and anti forgery breaks w/out the tokens.
however, unless i did something wrong, this seems trivial to spoof. for example
if i go to pageA and copy the token form pageB (just the form token, not even the cookie token), this still succeeds, and in my logs i see pageB while executing ajax method from pageA
confirmed that this is pretty easy to spoof.
I am using csrf to generate ajax tokens like this:
public static string MyForgeryToken(this HtmlHelper htmlHelper)
{
var c = htmlHelper.ViewContext.RequestContext.HttpContext.Request.Cookies[AntiForgeryConfig.CookieName];
string cookieToken, formToken;
AntiForgery.GetTokens(c != null ? c.Value : null, out cookieToken, out formToken);
return formToken;
}
I then pass the form token back with each ajax call and have a custom actionfilterattribute where I read/validate it along with cookie token
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var req = filterContext.RequestContext.HttpContext.Request;
var fToken = req.Headers[GlobalConstants.AntiForgeKey];
var cookie = req.Cookies[AntiForgeryConfig.CookieName];
var cToken = cookie != null
? cookie.Value
: "null";
log.Info("MyAntiForgeryAttribute.OnActionExecuting. \ntoken:{0} \ncookie:{1}", fToken, cToken);
AntiForgery.Validate(cToken, fToken);
this all works (changing anything about the token throws correct exception), then in my IAntiForgeryAdditionalDataProvider I can see what it thinks it's processing.
as soon as i override the csrf token from another view, it thinks it's that view. I don't even have to tamper with the UrlReferrer to break this :/
one way this could work if i could force the cookie to be different on every page load
I am assuming you can use IAntiForgeryAdditionalDataProvider for this.
public class CustomDataProvider : IAntiForgeryAdditionalDataProvider
{
public string GetAdditionalData(HttpContextBase context)
{
// Return the current request url or build a route or create a hash from a set of items from the current context.
return context.Request.Url.ToString();
}
public bool ValidateAdditionalData(HttpContextBase context, string additionalData)
{
// Check whether the allowed list contains additional data or delegate the validation to a separate component.
return false;
}
}
Register the provider in App_Start like below.
AntiForgeryConfig.AdditionalDataProvider = new CustomDataProvider();
https://msdn.microsoft.com/en-us/library/system.web.helpers.iantiforgeryadditionaldataprovider(v=vs.111).aspx
Hope this helps in your scenario.
You mentioned in your question that you're looking for Anti-forgery token capabilities.
Hence, I think what you're asking about is an anti-CSRF solution (CSRF=cross site request forgery).
One way to do this is to render a true random number (a one-time token) into your page, then passing it on each request, which can be done by adding a key/value pair to the request header and then checked at the backend (i.e. inside your controller). This is a challenge-response approach.
As you mentioned, in the server-side code you can use
var fToken = req.Headers["X-Request-Verification-Token"];
to get it from the requesting page.
To pass it along from each client AJAX request of the page, you can use
var tokenValue = '6427083747'; // replace this by rendered random token
$(document).ajaxSend(function (event, jqxhr, settings) {
jqxhr.setRequestHeader('X-Request-Verification-Token', tokenValue);
});
or you can set it for each request by using
var tokenValue = '2347893735'; // replace this by rendered random token
$.ajax({
url: 'foo/bar',
headers: { 'X-Request-Verification-Token': tokenValue }
});
Note that tokenValue needs to contain the random number which was rendered by the web server when the web page was sent to the client.
I would not use cookies for this, because cookies don't protect you against CSRF - you need to ensure that the page, which is requesting is the same as the page which was rendered (and hence created by the web server). A page being on a different tab in the same browser window could use the cookie as well.
Details can be found on the OWASP project page, in the OWASP CSRF prevention cheat sheet.
My quick interim solution was to use custom tokens created on each page load (guid which i keep track of in my token cache), which are passed as headers in all ajax calls. Additionally i create a original url hash and combine it into the custom auth token.
in my ajax methods I then extract the hash and compare it with UrlReferrer hash to ensure that hasn't been tampered with.
since the custom token is always different it's less obvious to guess what's going on as token appears to be different on every page load. however this is not secure because with enough effort the url hash can be uncovered. The exposure is somewhat limited because user identity is not the problem so worst case is a given user would gain write access to another section of the site but only as himself. My site is internal and i am auditing every move so any temper attempts would be caught quickly.
I am using both jQuery and angular so appending tokens with all requests like this:
var __key = '#Html.GetHeaderKey()' //helper method to get key from http header
//jQuery
$.ajaxSetup({
beforeSend: function (xhr, settings) {
xhr.setRequestHeader('X-Nothing-To-See-Here', __key); // totally inconspicuous
})
//angular
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Nothing-To-See-Here'] = __key;
});
update
the downside of this approach is that custom tokens need to be persisted across a web farm or app restarts. Based on #Sarathy's idea I am trying to side step this by leveraging MVC anti forgery framework. Basically add/remove my "salt" and let the framework manage the actual token validation. That way it's a bit less to manage for me. Will post more details once i verify that this is working.
So this is going to be one of those "you're doing it wrong" answers that I don't like, and so I apologize up front. In any case, from the question and comments, I'm going to propose you approach the problem differently. Instead of thinking about where did the request come from, think about what is the request trying to do. You need to determine if the user can do that.
My guess as to why this is hard in your case is I think you have made your api interface too generic. From your example api "api/common/update" I'm guessing you have a generic update api that can update anything, and you want to protect updating data X from a page that is only supposed to access data Y. If I'm off base there then ignore me. :)
So my answer would be: don't do that. Change your api around so it starts with the data you want to work with: api/dataX api/dataY. Then use user roles to protect those api methods appropriately. Behind the scenes you can still have a common update routine if you like that and it works for you, but keep the api interface more concrete.
If you really don't want to have an api for each table, and if its appropriate for you situation, perhaps you can at least have an api for protected/admin tables and a separate api for the standard tables. A lot of "if"s, but maybe this would work for your situation.
In addition, if your user can update some dataX but not other dataX, then you will have to do some sort of checking against your data, ideally against some root object and whether your user is authorized to see/use that root object.
So to summarize, avoid an overly generic api interface. By being more concrete you can use the existing security tools to help you.
And good luck!

Log Requests from some Link

How can i log requests that are going on some link?
I need to store requests Headers, Verb (Get or Post etc.), Request Data and Request Body.
It's must be some separate application like Fiddler.
DESC: I have web application. It makes some search. I want to log data of search request using another application which can log any requests for some site (in my case for my web app). How to make it? I make research for solution but find many examples where user can create some Module or Filter which must be included in web application. This case for me is not allowed.
If you have control of both sides, you can basically do whatever you want..
Maybe link to an action first that acts as a tracker:
public ActionResult Track()
{
//get whatever data you want here
//Request.Headers, Request.RequestType ect
//track the data in a database or whatever
SaveSomeData();
//get the original url from a post variable, or querystring, where you put it
var redirectUrl = Request["redirect"];
return Redirect(redirectUrl);
}
Then you would change your links for example a link to http://google.com, would change to
http://mywebsite.com/mycontroller/track?url=http://google.com
Another possible way would be to create a proxy, and monitor the data that goes through it.
Need a better idea of what you need though to help out more.

Handling WebDAV requests on MVC action

I have an existing MVC3 application which allows users to upload files and share them with others. The current model is that if a user wants to change a file, they have to delete the one there and re-upload the new version. To improve this, we are looking into integrating WebDAV to allow the online editing of things like Word documents.
So far, I have been using the .Net server and client libraries from http://www.webdavsystem.com/ to set the website up as a WebDAV server and to talk with it.
However, we don't want users to interact with the WebDAV server directly (we have some complicated rules on which users can do what in certain situations based on domain logic) but go through the previous controller actions we had for accessing files.
So far it is working up to the point where we can return the file and it gives the WebDAV-y type prompt for opening the file.
The problem is that it is always stuck in read-only mode. I have confirmed that it works and is editable if I use the direct WebDAV URL but not through my controller action.
Using Fiddler I think I have found the problem is that Word is trying to talk negotiate with the server about the locking with a location that isn't returning the right details. The controller action for downloading the file is "/Files/Download?filePath=bla" and so Word is trying to talk to "/Files" when it sends the OPTIONS request.
Do I simply need to have an action at that location that would know how to respond to the OPTIONS request and if so, how would I do that response? Alternatively, is there another way to do it, perhaps by adding some property to the response that could inform Word where it should be looking instead?
Here is my controller action:
public virtual FileResult Download(string filePath)
{
FileDetails file = _fileService.GetFile(filePath);
return File(file.Stream, file.ContentType);
}
And here is the file service method:
public FileDetails GetFile(string location)
{
var fileName = Path.GetFileName(location);
var contentType = ContentType.Get(Path.GetExtension(location));
string license ="license";
var session = new WebDavSession(license) {Credentials = CredentialCache.DefaultCredentials};
IResource resource = session.OpenResource(string.Format("{0}{1}", ConfigurationManager.AppSettings["WebDAVRoot"], location));
resource.TimeOut = 600000;
var input = resource.GetReadStream();
return new FileDetails { Filename = fileName, ContentType = contentType, Stream = input };
}
It is still very early days on this so I appreciate I could be doing this in entirely the wrong way and so any form of help is welcome.
In the end it seems that the better option was to allow users to directly talk to the WebDAV server and implement the authentication logic to control it.
The IT Hit server has extensions that allow you to authenticate against the forms authentication for the rest of the site using basic or digest authentication from Office. Using that along with some other customisations to the item request logic gave us what we needed.
This is exactly what i did for a MVC 4 project.
https://mvc4webdav.codeplex.com/

How to avoid open-redirect vulnerability and safely redirect on successful login (HINT: ASP.NET MVC 2 default code is vulnerable)

Normally, when a site requires that you are logged in before you can access a certain page, you are taken to the login screen and after successfully authenticating yourself, you are redirected back to the originally requested page. This is great for usability - but without careful scrutiny, this feature can easily become an open redirect vulnerability.
Sadly, for an example of this vulnerability, look no further than the default LogOn action provided by ASP.NET MVC 2:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid) {
if (MembershipService.ValidateUser(model.UserName, model.Password)) {
FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl)) {
return Redirect(returnUrl); // open redirect vulnerability HERE
} else {
return RedirectToAction("Index", "Home");
}
} else {
ModelState.AddModelError("", "User name or password incorrect...");
}
}
return View(model);
}
If a user is successfully authenticated, they are redirected to "returnUrl" (if it was provided via the login form submission).
Here is a simple example attack (one of many, actually) that exploits this vulnerability:
Attacker, pretending to be victim's bank, sends an email to victim containing a link, like this: http://www.mybank.com/logon?returnUrl=http://www.badsite.com
Having been taught to verify the ENTIRE domain name (e.g., google.com = GOOD, google.com.as31x.example.com = BAD), the victim knows the link is OK - there isn't any tricky sub-domain phishing going on.
The victim clicks the link, sees their actual familiar banking website and is asked to logon
Victim logs on and is subsequently redirected to http://www.badsite.com which is made to look exactly like victim's bank's website, so victim doesn't know he is now on a different site.
http://www.badsite.com says something like "We need to update our records - please type in some extremely personal information below: [ssn], [address], [phone number], etc."
Victim, still thinking he is on his banking website, falls for the ploy and provides attacker with the information
Any ideas on how to maintain this redirect-on-successful-login functionality yet avoid the open-redirect vulnerability?
I'm leaning toward the option of splitting the "returnUrl" parameter into controller/action parts and use "RedirectToRouteResult" instead of simply "Redirect". Does this approach open any new vulnerabilities?
Update
By limiting myself to controller/action routes, I can't redirect to custom routes (e.g. /backend/calendar/2010/05/21). I know that by passing more parameters to the LogOn action, I could get it to work, but I feel like I'll always be revisiting this method -- keeping it up to date with our routing scheme. So, instead of splitting the returnUrl into its controller/action parts, I am keeping returnUrl as-is and parsing it to make sure it contains only a relative path (e.g., /users/1) and not an absolute path (e.g., http://www.badsite.com/users/1). Here is the code I'm using:
private static bool CheckRedirect(string url) {
try {
new Uri(url, UriKind.Relative);
}
catch (UriFormatException e) {
return false;
}
return true;
}
Side note: I know this open-redirect may not seem to be a big deal compared to the likes of XSS and CSRF, but us developers are the only thing protecting our customers from the bad guys - anything we can do to make the bad guys' job harder is a win in my book.
Thanks, Brad
Jon Galloway wrote up an article with a solution for MVC 2 (and 1).
Here's the snippet that should help with your issue:
SECURED (original article updated 2014)
private bool IsLocalUrl(string url)
{
return System.Web.WebPages.RequestExtensions.IsUrlLocalToHost(
RequestContext.HttpContext.Request, url);
}
Yes this is a vulnerability. Before redirecting you need to inspect the returnUrl string parameter by passing it to a Uri object and make sure that the target domain is the same as the requesting domain. You should also take into account the case when returnUrl is a relative address like /admin. No problem in this case as the redirect will be to the same application.
You could always keep a record of the previous page with TempData when the user is not authenticated and use that to redirect to the previous page instead of a url parameter.
As long as you use one of the variants of Redirect that uses controller and action parameters or a route name, you should be alright, provided you have adequate security controls on your controller methods.
The concept being, whatever you use for your redirect must go through the routing engine and be validated by matching a route.
But I suspect that the real vulnerability is Cross-Site Scripting. Unless your malicious user can inject some Javascript into the page, they have no way of manipulating the return Url, or any of its parameters (since you otherwise control all of the server and browser code).

ASP.NET MVC Authorization and hyperlinks

I am using successfully custom authorization in ASP.NET MVC. It simply involves a comparison between User.Identity and the owner of the object in context.
It works like a charm when used in simple conditions. It becomes more complicated when I try to call 2 actions in one web request.
Lets say I want to display an image which would be generated on-the-fly by my application. This image is generated by a controller, thus, it can be referenced by an URL even if it doesn't exist physically. I have decided that the user must be signed in and be the owner to view it, so I apply my authorization mechanizm to it.
Example: <img src="http://myapplication.com/images/generate/3" />
When I include such an image in a page via its action hyperlink, I expect that the authenticated user will still be in context on server side when the image is generating. This is not the case in my tests. The image never displays because my authorization check doesn't work. In the image controller, User.Identity is empty as if the user has not signed it.
In the meantime, the same user is still signed in to the website and can continue to browse with his identity in context... without those images working properly.
I wonder how to make this process work securely...
Thank you very much!
Marc Lacoursiere
RooSoft Computing inc.
Just wondering if you've checked if
Thread.CurrentPrincipal
is also empty in the controller? It should contain the same value.
Another suggestion would be to store the User.Identity value in a session?
You need to set up your identity in global.asax on every request. I'm using a custom Principal and Identity for this.
private void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
{
SetIdentity(new MyIdentity
{ Type = UserType.Inactive, Id = int.MinValue });
}
else
{
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
var identity = Repository.GetIdentity
(authTicket.Name, new HttpRequestWrapper(Request));
SetIdentity(identity);
}
}
}
private void SetIdentity(MyIdentity identity)
{
Context.User = new MyPrincipal { Identity = identity };
Thread.CurrentPrincipal = Context.User;
}
This works, but I don't guarantee it to be secure. You should review this article on FormsAuthentication vulnerabilities prior to going live with this code. You have to understand that this code assumes the cookie is valid and hasn't been hijacked. There are some additional security measures that can be taken to reduce these vulnerabilities which this code doesn't show.
This may be when the site link in browser is http:\www.mysite.com (or http:\subdomain.mysite.com ) and you are using http:\mysite.com\image\5 in your application. Form authentication uses cookies. And these cookies may belong to domains and subdomains.
To find out what is going on I suggest to use FireFox with FireBug installed. Enable Net and Console tab for your site and make a complete refresh of the page. After you'll see requests in one of these tabs (Net tab exactly). At the left of the request you can see 'a plus' button, after you click it you'll see Headers and Response tabs (more detailed description of firebug). Have a look at Headers tab and try to find something like FORMAUTH (or what you've set in config as a forms cookie name). If you not see it - the problem is in domains.

Resources