How to set a default initialization cookie in ASP.NET MVC - c#

I was wondering if anyone can shed some light on cookie management. More precisely I would like to know how I could set up an initial cookie/s when a user start a session in a website.
Currently the ASP.NET_SessionId cookie is located on user computers when they navigate to the domain. I would like to set up an additional cookie with details of languageid and countryid with default parameters the first time the user navigate to the site.
Do anyone knows if there is any technique to do this, such as through web.config set up or placing cookie details using layout.cshtml like
Response.Cookies["language"].Value = "1";
Response.Cookies["country"].Value= "7";
or similar?, any option will be appreciated.

You could do this in an action filter:
public class LocalizationAwareAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var httpContext = filterContext.HttpContext.Current;
if (!httpContext.Cookies.Keys.Contains("language"))
{
httpContext.Response.AppendCookie(new HttpCookie("language", 1));
}
if (!httpContext.Cookies.Keys.Contains("country"))
{
httpContext.Response.AppendCookie(new HttpCookie("country", 7));
}
}
}
The filter can be applied globally, so you don't have to worry about remembering it on each action method or controller.

I haven't worked much with cookies in ASP.NET MVC, but the way I'd do it would be to have a little block of code in Global.asax or a separate base controller that gets executed on every request. This code would check the HttpContext for the existence of such a cookie, and if it didn't exist, it would create one. I'll work up a code sample and will update this answer soon.

Related

Global redirect back to front page if claim is empty or does not exist (NOT identity verification)

Just to be clear: I am NOT talking about claims-based identity validation.
I am building an app in which I make fine use of Identity 2.2 to provide validation. It is sufficient for my needs.
My problem is that once a user logs in, only the first page is widely accessible without storing additional information in the user’s “session”. In particular, when the user clicks on a major internal item (for sake of convenience, let’s call this a “customer module”, the Guid for that customer is stored in a claim held by the user. That way, the user can move from page to page and still have that same customer’s data brought up on every page regardless of what chunk of data the page was meant to display. This claim is only refreshed with something different when they return to the main page and click on another customer.
For security’s sake I would like to ensure that if a claim gets accidentally dropped or set to empty, the user gets shunted back to the main page regardless of where they are in the system, and preferably without having to put code in each and every page action of every controller.
Suggestions? Or am I completely wrong by making use of claims? Because it’s still early enough in the project to make a u-turn if the advantages of a different method are compelling enough.
EDIT:
Just to let people know my solution: Because only one group of people will be accessing this application (the users that interact with companies, this app is to record the interactions and “company information”), I decided to use a base controller. The users would be able to log on and view lists of companies without coming across any page that derived from BaseController, but once they chose a Company to work with, they needed to have Claims set to be able to maintain page-by-page contact with this company’s information. This information would be reset only when they chose a different company, but since there was always a chance that a claim could be disabled, I needed something to automagically redirect them back to the list of companies to re-set the claims. A BaseController that was employed by only those pages where information specific to one company would be displayed was the perfect solution.
A base controller is simple. Just create a controller called BaseController and you’re off to the races. Change any controller that needs to work with this base controller such that they are public class YourOtherController : BaseController.
I initially tried to make an Initialize method to handle everything, but ran into a rather big problem: I was unable to successfully both access and write to my Claims. As in, I was able to either read my claims but not make use of my ClaimWriter extension, or I was able to make use of my ClaimWriter extension but be unable to read claims in the first place. Since Initialize is wayyyy too low in the stack to actually do both these things, I abandoned it and went for an OnActionExecuted method, which ended up being successful. My code ended up being this:
public class BaseController : Controller {
private ApplicationDbContext db = new ApplicationDbContext();
protected override void OnActionExecuted(ActionExecutedContext filterContext) {
base.OnActionExecuted(filterContext);
var principal = ClaimsPrincipal.Current.Identities.First();
var company = User.GetClaimValue("CWD-Company");
var prospect = User.GetClaimValue("CWD-Prospect");
if(string.IsNullOrEmpty(company)) {
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Redirect("/");
filterContext.HttpContext.Response.End();
}
if(!string.IsNullOrEmpty(company) && string.IsNullOrEmpty(prospect)) {
var id = new Guid(company);
var prospecting = db.Prospecting
.Where(x => x.CompanyId.Equals(id))
.Select(x => x.ProspectingId)
.ToList().SingleOrDefault();
if(prospecting.Equals(Guid.Empty)) { // null prospecting
User.AddUpdateClaim("CWD-Prospecting", "");
} else { // fill prospecting
User.AddUpdateClaim("CWD-Prospecting", Convert.ToString(prospecting));
}
}
}
}
I am probably going to change the if(prospecting.Equals(Guid.Empty) part of the Prospecting section to automagically create the first entry in the db (with all null values except for the ProspectingId and the CompanyId, of course), but this is what works for now.
That's a fine use of claims you describe, no need a u-turn. What you need is a MVC filter, authorisation filter. Something like this:
public class MyAuthorisationFilter : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var principal = HttpContext.Current.User as ClaimsPrincipal;
if(!principal.Claims.Any(c => c.Type == "My Claim Name"))
{
// user has no claim - do redirection
// you need to create 'AuthenticateAgain' route to your table of routes
// or you can do other means of redirection
filterContext.Result = new RedirectToRouteResult("AuthenticateAgain", new RouteValueDictionary());
}
}
}
Then you can add it globally in your filters configuration, but you'll have to exclude your authorisation page from this filter. Or apply on per controller basis - whenever this needs to happen.
This is very basic form of filter - a lot of checks are stripped out, but it gives a general direction how to proceed.
Update
This is a good article about Authorise attribute.
Here use of AllowAnonymous attribute is explained
The way you use it - depends on your scenario. In most cases when you only expose a login page to the world - it is sufficient to add this attribute as a global filter (see second link, part about RegisterGlobalFilters) and then sprinkle [AllowAnonymous] on top of controllers/actions which should be exposed without authentication.
Another approach is to have a base controller that has your attribute applied. And then all your controllers inherit from this base controller. This is more sufficient when global filter does not cut it: cases when you expose different pages to different users - think companies and customers. Your controllers for companies will inherit CompaniesBaseController that has [CompaniesAuthFilter] and customers will be inheriting from CustomersBaseController with [CustomersAuthFilter].

How does WebSecurity.IsAuthenticated check if you're logged in or not?

This is probably a stupid question, but I've looked pretty hard on Google and couldn't come up with the answer.
I'm creating a website where the database is in another continent, so speed is a crucial issue.
From what I understand,
WebSecurity.Login(form.userName, form.password))
initially checks the database it's initially set up with and logs you in if the username and pw are correct.
Now for every backend function I'm writing, I'm sticking a
[Authorize]
attribute and a
if (WebSecurity.IsAuthenticated)
{ .... }
before performing any action. So does the WebSecurity.IsAuthenticated check the database at all to check if it is logged in or not? I just need to know for speed reasons.
Also is it redundant to put the [Authorize] and WebSecurity.IsAuthenticated in EVERY backend method?
Thanks for any help and opinions
So does the WebSecurity.IsAuthenticated check the database at all to check if it is logged in or not?
No, it just checks if the principal object in current request has the authentication flag set to true.
Usually the principal object is set by an authentication module, there are few different modules. Most use cookies to persist the information of the authenticated user (e.g. Forms, SessionAuthentication) and if the cookie is present and it's valid, the module sets the principal for the request which you can get by calling:
HttpContext.Current.User
in any method of your code (assuming the call is made from a web app that sets the HttpContext.Current).
Some authentication modules can rely on other authentication factors, for example the Windows authentication relies on NTLM/Kerberos protocols which in turn rely on specific headers rather than cookies.
Also is it redundant to put the [Authorize] and WebSecurity.IsAuthenticated in EVERY backend method
Yes and no.
What you most probably mean by "every backend method" is you mean a controller/action method in an MVC app. If this is so then, yes, you don't have to repeat both in a controller/action method.
But in any other method in your backend that is not a controller/action, the WebSecurity.IsAuthenticated still works while the Action attribute doesn't.
Thus, if by "every method" you literally mean every possible method, then the answer is no, these two are not redundant. One works always, the other - only in MVC controllers.
In addition to what #Wiktor Zychla has said, it is useful to note that WebSecurity.IsAuthenticated is functionally identical to Request.IsAuthenticated; to be precise, it's a wrapper:
namespace WebMatrix.WebData
{
//other class content omitted for brevity
public static class WebSecurity
{
//Context
internal static HttpContextBase Context
{
get { return new HttpContextWrapper(HttpContext.Current); }
}
//Request
internal static HttpRequestBase Request
{
get { return Context.Request; }
}
//WebSecurity.IsAuthenticated
public static bool IsAuthenticated
{
get { return Request.IsAuthenticated; }
}
}
}
So if you put all of the above together, WebSecurity.IsAuthenticated is equal to HttpContextWrapper(HttpContext.Current).Request.IsAuthenticated under the hood.
Aside: As correctly stated in the other answers and comments, [Authorize] is used to specify access to controller and action methods:
Specifies that access to a controller or action method is restricted to users who meet the authorization requirement. (source)
So IsAuthenticated works on both action methods and non-action methods, while [Authorize] only works on controllers and action methods, making it indeed redundant to use both on action methods.

Need recommendation for global, targeted redirects on ASP.NET MVC site for multiple differing conditions

I'm working on an ASP.NET MVC application where administrators can add new users and flag them to complete additional information before they can use other features of the site. For example, we have a "ForcePasswordReset" bool, and a requirement to complete Security Questions. We're not using Active Directory for these users.
Ultimately this is the behavior I'd like to implement:
Direct any logged in user who is required to change password to the
ChangePassword view. And if that user clicks on other links, funnel
him back to the ChangePassword view.
Same scenario for users who must change their security questions.
Initially I placed the checks directly into a Login Controller ActionResult. But this only accounts for the Login action. An abbreviated code sample is below.
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
// ...
// Does the user need to complete some missing information?
if (externalUser.IsSecurityQuestionInfoComplete == false)
return RedirectToAction("ChangeSecurityQuestions", "MyInfo");
if (externalUser.ForcePasswordReset)
return RedirectToAction("ChangePassword", "MyInfo");
// Login was successful
return RedirectToLocal(returnUrl);
}
}
One problem with this approach is that there are other hyperlinks presented in those targeted views, where the user could navigate away from the targeted interface. So for example, a user directed to the ChangeSecurityQuestions view could just click away from it.
Logged-in users can change those settings at any time. I could create duplicate views for changing passwords and security questions that are fashioned just for this scenario, where the user is being forced to update these values. In the interest of staying DRY and reducing maintenance, I'd like to use the same views for both scenarios (users who just want to edit that info, and users who are being forced to edit that info). But, trying to stay DRY in this respect may be wrongheaded if the alternative is worse.
I started to write a method within a helper class to divert these users, trying something like this.
/// <summary>
/// Check scenarios where the ExternalUser needs to complete some missing information, and divert as necessary.
/// </summary>
public static void divertExternalUserAsRequired(Controller controller, ExternalUser externalUser)
{
if (externalUser.IsSecurityQuestionInfoComplete == false)
return controller.RedirectToAction("ChangeSecurityQuestions", "MyInfo");
if (externalUser.ForcePasswordReset)
return controller.RedirectToAction("ChangePassword", "MyInfo");
}
But that RedirectToAction is inaccessible due to protection level. Moreover, that doesn't appear to be recommended (Is it possible to use RedirectToAction() inside a custom AuthorizeAttribute class?). And I don't like the idea of junking up my controllers by pasting this check all over the place.
UtilityHelpers.divertExternalUserAsRequired(this, externalUser);
What is the recommended approach to handling this scenario? I would perfer something that's more globally implemented, where the check can run when any relevant view loads.
Thanks for your help.
If I'm understanding your question correctly then you've got a few options available to you.
One option is to check the necessary conditions within Application_BeginRequest in your Global.asax.cs class. This method is called at the very beginning of every request and if the condition fails then you can load a different controller action like so:
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (!externalUser.IsSecurityQuestionInfoComplete)
{
var routeData = new RouteData();
routeData.Values["action"] = "MyInfo";
routeData.Values["controller"] = "ChangeSecurityQuestions";
RequestContext requestContext = new RequestContext(new HttpContextWrapper(Context), routeData);
IController errorController = new ChangeSecurityQuestionsController();
errorController.Execute(requestContext);
requestContext.HttpContext.Response.End();
}
}
Another option available to you is to create and register a global action filter. As you mentioned in your question, you don't like the idea of littering your controllers with these condition checks. By registering a global action filter your controllers can remain completely unaware of the action filter being performed against it. All you need to do is register your action filter within Global.asax.cs like so:
protected void Application_Start()
{
...
GlobalFilters.Filters.Add(new SecurityQuestionCompleteFilter());
...
}
I hope this helps.

MVC 3 How can I make a user view a warning/disclaimer screen

I thought this would be very simple but I'm struggling a little. I'm working on a project for a client using MVC 3 that requires users to agree to certain conditions before using the site. I have created a standard agree/disagree screen which is loaded when first coming into the site, but if a user types a address to a different part of the site they can bypass the conditions for example www.test.com loads the conditions but if the user types www.test.com/home they bypass the conditions.
How can I make sure they have agreed to the conditions before they can get anywhere else on the site? I have been trying a session variable, which I think is the way to go, but is there a way to check this variable on every page request without having to write a check into every Controller Action on the site?
You could make a custom attribute and add it to the top of the Controller.
For example:
[AgreedToDisclaimer]
public ActionResult LoadPage()
{
return View();
}
Which would only load the view if the AgreedToDisclaimer returns true.
public class AgreedToDisclaimerAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
// logic to check if they have agreed to disclaimer (cookie, session, database)
return true;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 by default - see HttpUnauthorizedResult.cs.
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "action", "ActionName" },
{ "controller", "ControllerName" },
{ "parameterName", "parameterValue" }
});
}
}
http://msdn.microsoft.com/en-us/library/dd410209(v=vs.90).aspx
http://msdn.microsoft.com/en-us/library/system.web.mvc.authorizeattribute.handleunauthorizedrequest.aspx
There are two approaches for the issue:
If this condition alter the site (like StackOverflow's notifications, that show at the top), but do not prevent you from using it, then I think it should be solved in presentation logic ( thus in the view, if you have real view an not just glorified template ).
Session variable in this case is just another part of model layer's state. When view instance requests data from the model layer, it is informed, that there is new notification for the user. This would mean that each view would have an ability to add representation of this notification to the current response. Preferably, by choosing to use one additional template, when assembling the output. This functionality would be shared between all views and thus could be implemented in base class, providing one single point of change.
If this acceptance of disclaimer is mandatory for everyone (like "I am 18 or older" in adult-themed sites), then the best option, in this case, would be to check, if user has agreed to condition in the routing mechanism. I am not sure how much control you have over request routing in ASP.NET MVC, but that again would provide you with as single point of change.
If decision is made in routing mechanism, it would also mean, that you put the condition outside the standard MVC triad entirely.

MVC Equivalent of Page_Load

I have a session variable that is set in my MVC application. Whenever that session expires and the user tries to refresh the page that they're on, the page will throw an error because the session is not set anymore.
Is there anywhere I can check to see if the session is set before loading a view? Perhaps putting something inside the Global.asax file?
I could do something like this at the beginning of EVERY ActionResult.
public ActionResult ViewRecord()
{
if (MyClass.SessionName == null)
{
return View("Home");
}
else
{
//do something with the session variable
}
}
Is there any alternative to doing this?
What would the best practice be in this case?
If it's in one controller, you can do this:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
... // do your magic
}
It will fire before on any action execution. You can't return a view from there though, you'll have to redirect to anything that returns action result, e.g:
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Shared" }, { "action", "Home" } });
But, obviously, that should redirect to the action in the controller that's not affected by the override, otherwise you have a circular redirect. :)
First, you should redirect to Home, not return the Home View, otherwise you have the weird situation of the home page showing up despite the Url being somewhere else.
Second, Session will never be Null, because a new session gets created when the old one expires or is reset. You would instead check for your variable and if THAT is null, then you know the session is new.
Third, If you app depends on this session data, then I would not use a session at all. Are you using this to cache data? If so, then using Cache may be a better choice (your app gets notified when cache items expire).
Unfortunately, this is probably a case of The XY Problem. You have a problem, and you believe Session solves your problem, but you're running into a different problem with Session, so you are asking how to solve your session problem rather than how to solve the problem Session is trying to solve.
What is the real problem you are trying to solve with this?
EDIT:
Based on your comment below, why don't you pass the customer number on the url:
http://website/Controller/ViewRecord/3
public ActionResult ViewRecord(int id)
{
// do whatever you need to do with the customer ID
}

Categories