The EU General Data Protection Regulation (GDPR) will come into effect from 25th May 2018. One can read in detail here. This time it has to be all opt-in and they have very heavy fine (€20 million or 4% of global earning!).
Since, it has to be all opt-in(at least in our case), we have decided user accepts our cookies to receive our services.
We will not be logging out current users to give us concept, however, we will present them consent page when they come into one of our sites. If they say yes then we will save an "accept-cookie" or else they won't be able to come into our sites. Afterwards, whenever a use logs into our site, we check the existence of this cookie.
My idea in implementing this solution is to intercept the user request and check the existence of accept-cookie and redirect to the requested resource or controller in our case as we will asp.net mvc accordingly.
My question is can I do this using RegisterRoutes to route request to a controller and if yes, redirect to the requested controller?
What about this solution? Though, the solution is for different aspect. I have modified the variables name from language to consent to make it more meaningful(not trying to copy):
public class EnsureLanguagePreferenceAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var euCookie = filterContext.HttpContext.Request.Cookies["ConsentCookies"];
if (euCookie == null)
{
// cookie doesn't exist, redirect use to a View showing
//all the cookies being saved in client machine
// and to take user consent(accept or deny)
}
// do something with euCookie
base.OnActionExecuting(filterContext);
}
}
As this rule comes into effect on 25th May 2018, it would be nice to hear your idea regarding different kind of implementation.
Finally, I came up with something that I wanted--intercepting user request and redirecting based upon a certain cookie. This can be used as a nuget as we have multiple applications and saving cookies could be done from one of the application. As it is made as an action filter attribute, it can be place above controller:
[MyAcceptCookieCheck]
public class HomeController : Controller
This makes it easy to implement across all application and operations regarding saving cookies will be done from the one of the application so that it will be easy to make any changes i.e., only from one place.
public class MyAcceptCookieCheck : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookies = filterContext.HttpContext.Request.Cookies["OurAcceptCookie"];
var values = filterContext.RouteData.Values.Values;
originalRequest = filterContext.HttpContext.Request.Url.AbsoluteUri;
RouteValueDictionary requestOrigin = new RouteValueDictionary { {
"url", originalRequest } };
if (cookies == null && !values.Contains("CookieConsent")) //so that it won't loop endlessly
{
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
//filterContext.Result = new RedirectResult(urlHelper.Action("CookieConsent", "Home"));
filterContext.Result = new RedirectResult(urlHelper.Action("CookieConsent","Cookie",requestOrigin ,"https","www.my-domain.com/mysitename"));
}
else if(cookies != null)
{
string controllerName = filterContext.RouteData.Values["controller"].ToString();
string actionName = filterContext.RouteData.Values["action"].ToString();
UrlHelper urlHelper = new UrlHelper(filterContext.RequestContext);
filterContext.Result = new RedirectResult(urlHelper.AbsolutePath(actionName, controllerName));
}
}
}
Code for AbsolutePath (courtesy):
public static string AbsolutePath(this UrlHelper url, string actionName, string controllerName, object routeValues = null)
{
string scheme = url.RequestContext.HttpContext.Request.Url.Scheme;
return url.Action(actionName, controllerName, routeValues, scheme);
}
Now, I can redirect all requests without having that particular cookie to a cookie consent page and show user all the details about cookies being used and ask for permission to save "ConsentCookie".
Related
I need to create a single page that is going to be a part of an existing ASP.NET MVC website that is only open or accessible if came from allowed websites (Referrer). Our partners are planning
to load this page using an iFrame. This page allows any user to quickly enter the details and save into
our database.
example: www.mysecuredwebsite.com/publicpage
Since this is going to be public single page only and no logins, the only security is
only allow if the referrer is in the list of allowed sites.
Example:
www.abc.com
www.randomwebsite.com
www.amazingsite.com
Any request from any other sites will be redirected to an error page.
What is the best approach to this problem? I'm thinking of creating a custom attribute that will be used
to decorate the controller which then reads
a list of allowed sites from the app.config orany type of config.
Or maybe from the SQLDB since the site is using SQLDB? The list I think will be frequently change
depending on the growing number of clients. It should be easily configurable and does not require
redeployment.
Any thoughts? Thanks!
You can use two methods
Method 1: Use ActionFilterAttribute.
Method 2: Use the Application_BeginRequest method in the Global.asax file.
Note: It is better to put the allowed urls in the database so that it can be easily changed.
I will explain the first method
The following filter checks whether UrlReferrer is allowed. If it
is not allowed, it will be referred to the error page.
add UrlReferrerFilterAttribute
using System.Web.Mvc;
using System.Web.Routing;
namespace yournamespace
{
public class UrlReferrerFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string urlReferrer = string.Empty;
if (filterContext.HttpContext.Request.UrlReferrer != null)
{
urlReferrer = filterContext.HttpContext.Request.UrlReferrer.AbsoluteUri;
if(!db.UrlReferrerTable.Any(a => a.Url == urlReferrer))
{
var values = new RouteValueDictionary(new
{
action = "ErrorPage",
controller = "Home"
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
base.OnActionExecuting(filterContext);
}
}
}
now use
[UrlReferrerFilter]
public ActionResult YourAction()
{
//..................
//..................
}
So I have two custom authorize attributes: 1) is to redirect the user to login whenever a session has expired or not authenticated; 2) is currently in progress.
The idea for the second custom authorize attribute is to redirect the user to the same page before he/she navigated to the next page or prevent from redirecting to the next page request. Let say the code is
public class CustomAuth2Attribute : AuthorizeAttribute
{
private const string _errorController = "Error";
public override void OnAuthorization(AuthorizationContext filterContext)
{
var controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
var action = filterContext.ActionDescriptor.ActionName;
var area = "";
if (filterContext.RouteData.DataTokens.ContainsKey("area"))
area = filterContext.RouteData.DataTokens["area"].ToString();
if (controller == _errorController)
{
return;
}
// checking the user identity whether the user is allowed to access this page
// then redirect to the previous page before this request and add flash note: "not allowed to access the content"
}
}
The idea is if the user do not have access to a certain page I do not flag this as not authorize instead I should be returning them to the page they were before with the note message.
Also tried the below code:
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new
{
controller,
action,
area
}));
I'm getting too many redirects which is because I'm referencing the current controller, action, and area instead of the previous one. I also tried getting the UrlReferrer value but this is always null.
Any way I can achieve this? Any help is appreciated. Thank you in advance.
You can override HandleUnauthorizedResult for that:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.UrlReferrer.ToString());
}
I have overridden the HandleUnauthorizedRequest method in my asp.net mvc application to ensure it sends a 401 response to unauthorized ajax calls instead of redirecting to login page. This works perfectly fine when I run it locally, but my overridden method doesn't get called once I deploy to IIS. The debug point doesn't hit my method at all and straight away gets redirected to the login page.
This is my code:
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
filterContext.Result = new JsonResult
{
Data = new
{
success = false,
resultMessage = "Errors"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.HttpContext.Response.End();
base.HandleUnauthorizedRequest(filterContext);
}
else
{
var url = HttpContext.Current.Request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
filterContext.Result = new RedirectResult(ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url);
}
}
}
and I have the attribute [AjaxAuthorize] declared on top of my controller. What could be different once it's deployed to IIS?
Update:
Here's how I'm testing, it's very simple, doesn't even matter whether it's an ajax request or a simple page refresh after the login session has expired -
I deploy the site onto my local IIS
Login to the website, go to the home page - "/Home"
Right click on the "Logout" link, "Open in a new tab" - This ensures that the home page is still open on the current tab while
the session is logged out.
Refresh Home page. Now here, the debug point should hit my overridden HandleUnauthorizedRequest method and go through the
if/else condition and then redirect me to login page. But it
doesn't! it just simply redirects to login page straight away. I'm
thinking it's not even considering my custom authorize attribute.
When I run the site from visual studio however, everything works fine, the control enters the debug point in my overridden method and goes through the if/else condition.
When you deploy your web site to IIS, it will run under IIS integrated mode by default. This is usually the best option. But it also means that the HTTP request/response model isn't completely initialized during the authorization check. I suspect this is causing IsAjaxRequest() to always return false when your application is hosted on IIS.
Also, the default HandleUnauthorizedRequest implementation looks like this:
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
filterContext.Result = new HttpUnauthorizedResult();
}
Effectively, by calling base.HandleUnauthorizedRequest(context) you are overwriting the JsonResult instance that you are setting with the default HttpUnauthorizedResult instance.
There is a reason why these are called filters. They are meant for filtering requests that go into a piece of logic, not for actually executing that piece of logic. The handler (ActionResult derived class) is supposed to do the work.
To accomplish this, you need to build a separate handler so the logic that the filter executes waits until after HttpContext is fully initialized.
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new AjaxHandler();
}
}
public class AjaxHandler : JsonResult
{
public override void ExecuteResult(ControllerContext context)
{
var httpContext = context.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
if (request.IsAjaxRequest())
{
response.StatusCode = (int)HttpStatusCode.Unauthorized;
this.Data = new
{
success = false,
resultMessage = "Errors"
};
this.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
base.ExecuteResult(context);
}
else
{
var url = request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
url = ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url;
var redirectResult = new RedirectResult(url);
redirectResult.ExecuteResult(context);
}
}
}
NOTE: The above code is untested. But this should get you moving in the right direction.
Today I've been trying to program a little bit in the MVC 4 Facebook API developed by Microsoft (based on the example of: http://www.asp.net/mvc/tutorials/mvc-4/aspnet-mvc-facebook-birthday-app)
So far I managed to manipulate the MyAppUser model, etc. Everything working fine and as intended. I only have a slight problem when I'm switching through controllers.
Is there any way to retain the FacebookContext object through controllers?
Unfortunately the above example (from Microsoft) only loads MyAppUser in the Home controller as follows:
[FacebookAuthorize("email", "user_photos")]
public async Task<ActionResult> Index(FacebookContext context) {
if (ModelState.IsValid) {
var user = await context.Client.GetCurrentUserAsync<MyAppUser>();
return View(user);
}
return View("Error");
}
What should I do if I use another controller in the application? How can I obtain a FacebookContext reference to get the user?
Things I tried:
Putting FacebookContext context into the other Controller (is always null)
Putting the FacebookContext object into Session or ViewBag - no avail, and sounds way too dirty anyway.
Am I missing something crucial here?
I just wanted to have a different Controller with a couple of actions to manage a User's profile, which would be done completely separately from Facebook's data (via a database hosted locally.) The only reason I need to load the Context is to get the current user's e-mail address to create their account on that basis.
Any help would be greatly appreciated as I've spent quite a considerable amount of time trying to fix it.
My example controller could be:
public ActionResult Manage()
{
var user = await context.Client.GetCurrentUserAsync<Models.MyAppUser>();
if (MyDALFunction.GetUserByMail(user.Email) == null) {
// Create user functions, create a ViewModel, pass it on and do some editing.
}
return View(user);
}
This is how I solved this:
First, in Home Controller I save access token to TempData
public async Task<ActionResult> Index(FacebookContext context)
{
if (ModelState.IsValid)
{
this.TempData["accessToken"] = context.AccessToken;
Then I read it in another action in different controller. If access token is empty, it means that user is not logged in, so I redirect him to Home controller.
var accessToken = TempData["accessToken"] as string;
if (string.IsNullOrEmpty(accessToken))
{
//if access token is null or user is not logged in, redirect to home controller
return RedirectToAction("Index", "Home");
}
else
{
var fb = new Facebook.FacebookClient(accessToken);
var me = fb.Get("me") as Facebook.JsonObject; //current logged user
var userFacebookId = me["id"].ToString();
Instead of "id", you can read email.
EDIT:
Retrieving accessToken from TempData returned null, when i tried to do that in another controller. It would be better to store it in Session instead.
Where to store Facebook access token in ASP.NET MVC?
Sorry, for answering too late.. but if i understood your question correctly then i think you are trying to get the FacebookContext object in you Action Method when post-back occurs. If so, then.. In your .cshtml try to put
<a target="_top" href="#GlobalFacebookConfiguration.Configuration.AppUrl#Url.Action("Manage", new { friendId = friend.Id })" role="button" class="btn btn-success">
and then make you action method like...
public ActionResult Manage(string friendId, FacebookContext context)
{
var friend = await context.Client.GetFacebookObjectAsync<MyAppUserFriend>(friendId);
// var user = await context.Client.GetCurrentUserAsync<Models.MyAppUser>();
if (MyDALFunction.GetUserByMail(friend.Email) == null) {
// Create user functions, create a ViewModel, pass it on and do some editing.
}
return View(user);
}
But Make sure that your MyAppUserFriend model have the Email attribute..
If you wanted any thing else then please provide some detail of you Model and your View
I have a subscription based MVC 2 application with the basic .NET Membership service in place (underneath some custom components to manage the account/subscription, etc). Users whose accounts have lapsed, or who have manually suspended their accounts, need to be able to get to a single view in the system that manages the status of their account. The controller driving that view is protected using the [Authorize] attribute.
I want to ensure that no other views in the system can be accessed until the user has re-activated their account. In my base controller (from which all my protected controllers derive) I tried modifying the OnActionExecuting method to intercept the action, check for a suspended account, and if it's suspended, redirect to the single view that manages the account status. But this puts me in an infinite loop. When the new action is hit, OnActionExecuting gets called again, and the cycle keeps going.
I don't really want to extend the [Authorize] attribute, but can if need be.
Any other thoughts on how to do this at the controller level?
EDIT: in the base controller, I was managing the redirect (that subsequently created the redirect loop) by modifying the filterContext.Result property, setting it to the RedirectToAction result of my view in question. I noticed everytime the loop occurs, filterContext.Result == null. Perhaps I should be checking against a different part of filterContext?
Ok, so here's my solution in case it helps anyone else. There's got to be a more elegant way to do this, and I'm all ears if anyone has a better idea.
In my BaseController.cs:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
ViewData["CurrentUser"] = CurrentUser; // this is a public property in the BaseController
if (CurrentUser != null && CurrentUser.Account.Status != AccountStatus.Active)
{
// if the account is disabled and they are authenticated, we need to allow them
// to get to the account settings screen where they can re-activate, as well as the logoff
// action. Everything else should be disabled.
string[] actionWhiteList = new string[] {
Url.Action("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" }),
Url.Action("Logoff", "Account")
};
var allowAccess = false;
foreach (string url in actionWhiteList)
{
// compare each of the whitelisted paths to the raw url from the Request context.
if (url == filterContext.HttpContext.Request.RawUrl)
{
allowAccess = true;
break;
}
}
if (!allowAccess)
{
filterContext.Result = RedirectToAction("Edit", "AccountSettings", new { id = CurrentUser.Account.Id, section = "billing" });
}
}
base.OnActionExecuting(filterContext);
}