MVC3 application and keeping track of what page the user initially entered - asp.net-mvc-3

and thanks for taking the time to read my question. We will have visitors to the site that might arrive at another user's profile page. We will then encourage them to login to the site, or register as new if they are not currently members. These actions move them pretty far away from that initial page/URL they stated on, but we want to be able to "jump them" back to that page after completing login/sign-up by "keeping track" where they began. (NOTE: we're using the standard Microsoft membership provider classes, if it matters.) The "jumping back" part seems straightforward, it is the "keeping track" part that is the problem.
An illustration/example:
A visitor arrives at the site after clicking: www.mysite.com/profiles/ID=108
The visitor then leaves that page to login. How can we best capture the ID=108 somehow, so that there is a good option for the (logged-in) user to return to that profile page? I understand from reading that cookies are not optimal, but some of the other options seem to throw a monkey wrench into my thinking since the user is not yet logged-in, etc.
Any concrete steps that I can take to address this in the best MVC3 way?
EDIT:
public ViewResult MyProfile(int? id)
{
HttpCookie cookie = new HttpCookie("MySiteCookie");
cookie.Value = id.ToString();
cookie.Expires = DateTime.Now.AddYears(1); //or whatever time is appropriate
System.Web.HttpContext.Current.Response.Cookies.Add(cookie);
//do some other stuff
}
At the other end, to make use of the cookie, I've put it into the menu (temporarily) in such a way:
var MyProfileId = "";
if (Request.Cookies["MySiteCookie"] != null)
{HttpCookie cookie = request.Cookies["MySiteCookie"];
MyProfileId = Server.HtmlEncode(cookie.Value);}
and then you put it into your link

You have a couple of options:
1) You can use Session in your Controller to store the value:
Session.Remove("ID");
Session.Add("ID", "108")
And retrieve in the called Controller.
ID = Session["ID"];
2) You can pass the ID=108 on the query string from the Login Controller:
return RedirectToAction("Edit", "Profile", new { ID = "108" });

I understand from reading that cookies are not optimal
IMHO cookies are the best way to approach this. When an anonymous user lands on the profiles page simply emit a cookie containing the id of the profile that he is currently viewing. Then later when he successfully logs in read this cookie value to obtain the id and construct the redirect link and redirect him to this link.
Another possibility is to use server side session but I am mentioning this just as an alternative to cookies and not at all as something that I would recommend.

You can make the redirection to the login action adding a Url to a query string param.
Let say: www.mysite.com/login?ReturnUrl='www.mysite.com/profiles/ID=108'
I think that this is the default membership provider behaviour, but you can get the ReferrerUrl to place it in the query string on your own.

Related

Global.asax Session_Start doesn't fire on IIS 7.5 until Second Request

Experiencing really weird behavior on a site I maintain, https://conservationx.com/
1) Global.asax Session_Start, simply checks for a cookie of a specific name (visitor) and if not present, we issue one.
protected void Session_Start()
{
var req = HttpContext.Current.Request;
var cookie = request.Cookies["visitor"];
if (cookie == null)
{
var cookie = new HttpCookie("visitor", sguid);
cookie.Shareable = false; // Do not store in outputcache
cookie.Expires = DateTime.UtcNow.AddYears(1);
response.SetCookie(cookie);
2) In local Debug, I can visit and step into it just fine, first request - Incognito window consistently gets me Set-Cookie header on first request.
3) Once deployed, I will never, ever see that Cookie issued on first request. I consistently see it on second request.
4) In addition to seeing Set-Cookie header not appear until the second request in DevTools Network tab, I've also added logging to the Session_Start body, and wrapped it all in try/catch, logging any exceptions. That likewise consistently fails to fire on first visit. This particular site checks if you're logged in once the page is done loading, with a call to /account/navaccountinfo - fresh browsers/Incognito windows will always fail to get a cookie set landing on any page on the site, then see the cookie finally set on that secondary load, and sure enough our logs are filled with cookies being set on the request to that secondary request URL.
Is this a known issue with deploying ASP.Net MVC to IIS 7.5? We are using OWIN with Identity Framework if that has any impact?
Speculating, I wonder if this is related:
OutputCache VaryByCustom cookie value
I set cookie.Shareable = false because the visitor Id is meant to be unique per browser. Can't be giving it out to multiple people via the Server's OutputCache. The initial page visited, like / or /about, has an OutputCacheAttribute set and is visited via GET. The followup, /account/navaccountinfo, is visited via POST, and so, obviously never cached. So I wonder if this is actually a bad interaction between OutputCache and cookie.Shareable = false.
This is not a GOOD answer, but this is an answer and I'm eagerly interested in other more elegant solutions.
The Problem
We don't cache much or at all in Dev, we do on the Server, thus the Session not firing until a Post fired - the user was seeing a quick cached response from IIS, which means most of the Asp.Net pipeline never spins up, especially Session_Start. Posts always bust through the cache and voila, second call - which was always a Post - always saw the cookie set, and always saw a log entry for the Session.
In addition, the fact that setting HttpCookie.Shareable = false essentially throws a firebomb into your caching by default is very poorly documented, and by not using much caching in Dev, we weren't really seeing the major damage this flag does.
The Bad Solution (Elegant Proposals Welcome)
We're already making heavy use of OutputCache VaryByCustom, where we do some somewhat creative things like serving the general public one version of the site, and logged-in members each their own. The details of how this works are beyond the scope of this issue, but suffice to say we use VaryByCustom in Global.asax:
public override string GetVaryByCustomString(HttpContext context, string key)
{
return VaryByCustomKeyHandler.HandleCacheKey(key, context, Application);
}
The key is just our compile-safe way of indicating what kind of variance we want for a Controller Action, and I'll skip the details of that, but, the HandleCacheKey method now looks like this:
string uidInSetVisitorCookie = context.Request.ServerVariables["SetVisitorCookie"];
if (uidInSetVisitorCookie != null)
return uidInSetVisitorCookie; // Force vary
So, if the user is supposed to receive a Cookie, we basically tell the OutputCache that this user and this user alone should get their very own copy of this page, for now. We do that by providing a cache key that happens to be their randomly generated Uid. That Uid is generated in BeginRequest, which, I've verified, does fire even when OutputCache is ultimately going to handle the Response:
protected void Application_BeginRequest(object sender, EventArgs ev)
{
var req = (Brass9.Web.Visiting.Req)HttpContext.Current.Request;
string uid = AnonCookieManager.O.GetUid(req);
bool hadNoCookie = String.IsNullOrEmpty(uid);
if (hadNoCookie)
{
uid = (Brass9.ShortGuid.NewGuid()).ToString();
AnonCookieManager.O.PutCookie(HttpContext.Current.Response, uid);
req.ServerVariables["SetVisitorCookie"] = uid;
}
So there's a number of light library calls in there, but quickly:
Req is a simple wrapper class for the 2 annoying Request classes Asp.Net uses that have a ton of overlap yet fail to share a base class.
GetUid() gets the visitor cookie out of the Request cookies, if any.
ShortGuid is a lot like this
Most importantly, PutCookie() has been modified to participate in this workaround, like so:
var cookie = new HttpCookie(CookieName, sguid);
//cookie.Shareable = false;
// Cookies shouldn't be shared, but, we signal to vary the OutputCache instead
// of blowing up OutputCache
cookie.Expires = DateTime.UtcNow.AddYears(1);
response.SetCookie(cookie);

Can CakePHP offer stricter user authentication (continuous throughout session)?

I am trying to create a suitable authentication check for a CakePHP service. Currently it appears that the user session is created initially during login, but never checked beyond this during a single session.
eg. Renamed the username, changing the password or ID in the user's database entry has no effect on the session.
Is there a preferred method for this type of, constantly checked, authentication? Essentially the user should be confirmed access at every request.
My current solution would involve extending the AuthComponent and storing a hash of the user data (including the encrypted password) and checking this at every request. I also considered storing the session ID in this same token, but noticed that CakePHP does not even use the session_start() function.
This functionality appears necessary for me, and I would have thought others would also require such a solution. I have yet to find Cake documentation or community solutions similar to what I need.
Well, you can use isAuthorized() function from AuthComponent. It's being called with every request.
public function isAuthorized($user){
return true; //allow the user to see the page
}
You can debug($user) to see the actual data and if you want "new" information from your database, you can always get them like this:
public function isAuthorized($user){
$current_user_from_database = $this->User->findById($user['id']);
if($current_user_from_database['User']['username'] != $user['username']){
$this->Session->setFlash('You\'ve changed the username. Please, login again.');
$this->redirect($this->Auth->logout);
return false;
}
return true;
}
Look at the API for more info and from the PDF book. You can look at this video about AuthComponent too. It's great.
If you need any more information or help, feel free to ask.
Btw. you have to configure AuthComponent in your Controller if you want isAuthorized() function to get called with every request.
If Session.timeout will work correctly with a setting of zero minutes, you're set. http://book.cakephp.org/2.0/en/development/sessions.html

ASP.Net MVC 3 Querystring Parameter

I am developing an ASP.Net MVC 3 Web Application. Within some of my Views I display tabular data to the user, and beside each record in the table there is an Edit link. When the user clicks this link it takes them to an edit page where they can edit and update the record.
My issue is that once the user clicks the edit link, the URL becomes something like this
http://www.mytestsite.com/myData/edit/3
The '3' is the ID of the record to be updated, however, there is nothing stopping the user from changing the '3' to another digit, and this then means they can edit potentially a record which does not belong to them.
Does anyone have a solution on how I can prevent this from happening?
Thanks for you help.
You need to introduce Authentication and Authorisation into your application. Here is one article of many out there on how to get started with this. You will additionally need to work out how to store logged on user identity and then how to attach this to the record when it was created in the first place. You must then validate, on the server, that the subsequent edit request is being made by the user who created the record in the first place (or by a user who has a role on your system which allows them to do this, such as an Administrator).
Even if the ID wasn't being displayed on the URL a malicious user could still manipulate the HTTP Request to pass an ID of their choice. In any secure system you should always, always, always validate that the currently logged on user genuinely has permission to carry out the requested action. You should never rely on what comes back from the browser to determine this (aside from the authentication context which is managed securely by the MVC framework. Usually).
I believe you should have the information about who have the edit permission on this purticular resource, in your tables. Ex : in your table you might have the "CreatedById" column where you store the ID of the user who created this record. Now in your edit action method, you check the "CreatedById" of the current Item is same as of the "UserId" of the Current user (you maye get this from the session, if you stored it there). Something like this.
public ActionResult Edit(int id)
{
int currentUserID=1; // TO DO : get this value from session or somewhere
ProductVieWModel product=myRepo.GetProduct(id);
if(product!=null)
{
if(product.CreatedById==currentUserID)
{
return View(product);
}
else
{
return View("NotAutherized");
}
}
return View("ProdcutNotFound");
}
You should try using the GUID data type as it helps in these kind of situations, and the user cannot easily guess the next value

MVC 3 Page navigation security rules

I have a MVC3 application which follows PRG pattern, I am looking for a best way to define navigation rules to my application. For example I have application with pages A, B, C and D. Lets say A is a logon page. After user logon successfully, user will be redirected to page B. Now I do not want let the user to type url of page C in the address bar and get access to page C (Page C should be accessible only after POST ing page B or from Page D back button) I have to setup similar rules for all other pages as well (lets say when user is in page D, should not allow them to get into Page B)
currently I have one option where I can check the #Request.UrlRefferer property to get the source of the every request and decide which page to redirect to. I am not sure this is a best solution.
Appreciate your feedbacks!!
Do not base your security on this. Use the [Authorize] attribute to define security. The UrlReferrer can easily be forged as well.
Why are you trying to limit this? If you have a business reason the user most go through a particular flow, then consider either a cookie, session, or database entry to note their current 'completion' status - IE some somewhat persistent method to determine this. You could also form a token based on say - a session id - that gets passed into each page. If the token exists and matches the user's current session , then load the page for them. Of course this could be forged if the user understands this - but if you are simply trying to ensure the proper flow then this is a way as well. The user would not get a link with the current session id in it until they hit the prior step.
If you don't want a particular page to be accessible via the URL, one option available is to make sure there's no way to access the page via a URL. To access the page, make a POST action that will return a view rather than a redirect. This would mean the view your POST action returns will be shown on a page with the URL of the previous page. For exameple:
Page A URL is /login and after logging in, the user is redirected to Page B. The URL is now /home. Page B sends a POST request and the contents of the page becomes Page C but the URL still remains as /home. The only way to view the contents of Page C would be to visit Page B and send a POST request.
This breaks the PRG pattern but that's one option.
There is one more alternative, store the current permissions of the user indicating which page they're allowed to enter and check if the user is authorized to view a page before executing the action. You could place the code in an ActionAttribute which you can apply to your action methods or entire controllers. If you'd like a more detailed explanation of this technique, leave me a comment and I'll write up another answer describing this technique in more detail.
Here's a quick proof-of-concept of the technique described above:
public class PermissionsNeeded : ActionFilterAttribute
{
string expectedPermission;
public PermissionsNeeded(string permission)
{
expectedPermission = permission;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var currentPermissions = filterContext.HttpContext.Session["CurrentPermissions"] as IEnumerable<string> ?? new List<string>();
// If user does NOT have permission to access the action method
if(!currentPermissions.Contains(expectedPermission)
{
throw new HttpException(403, "User is not authorized to view this page");
}
}
}
class YourController : Controller
{
[HttpPost]
public ActionResult PageB()
{
var currentPermissions = Session["CurrentPermissions"] ?? new List<string>();
currentPermissions.Add("PostedFromPageB");
Session["CurrentPermissions"] = currentPermissions;
return RedirectToAction("PageC");
}
[PermissionsNeeded("PostedFromPageB")
public ActionResult PageC()
{
return View();
}
}
Currently, the custom attribute will only accept one permission at a time which is a simply limitation to rectify. You'd be responsible for removing the permissions stored in the Session when you feel the user shouldn't have certain permissions anymore. I threw an HttpException that return a 403 status code (unauthorized access) but if you'd instead like to return an ActionResult such as a RedirectToRoute or a View, you could set a value to the filterContext.Result property.
I end up doing this task as below:
On successful completion of every page, save the page Name in database
When user request for a new page, simply check the page they have completed last from database and decide what to do.
I chooses this approach simply because it will definitely help during Problem Solving/Troubleshooting once application is in Production, which is huge for me.
Thanks everyone for your responses!

Salesforce Session variables, set and get variables in Session

I want to be able to read / write some variables to the current session in my Salesforce site pages.
I have a site built using Salesforce Sites, I need to store/retrieve some values across all the pages (consider that I am building something similar to a shopping cart).
However I cant find any good example on how to read and write variables to the session (anonymous user).
I am using Visualforce pages with several controllers built in Apex.
Regards
If you are building something like a shopping cart, or a "wizard" where you need to keep controller variables in context from one page view to another, then the best way to do this in VisualForce is to use the same controller.
When the user submits a form ( through actionFunctions, commandButtons, or commandLinks, etc.), and your controller returns a page Reference, the view state is preserved if the new visual force page uses the same controller.
In this way, you could, for example, have the user enter their name and email address using apex:inputField tags on page one. They navigate to page two, which uses the same controller as page one, and the page could reference the same controller variables. Essentially, the controller is still in scope, and so are all the variables that were updates.
Example:
Page one:
<apex:page controller="myController">
Please enter your name <apex:inputText value="{!shopper_name}"/>
<br/>
<apex:commandButton action="{!pageTwo}" value="Click for page two"/>
</apex:page>
Page two:
<apex:page controller="myController">
You entered: <apex:outputText value="{!shopper_name}" />.
</apex:page>
Controller:
public class myController {
public string shopper_name { get; set; }
public myController() {
shopper_name = null;
}
}
Custom settings are cached at the application level, maybe that's why it was suggested in the link above. I'm not sure if I'd recommend that approach, but you might be able to get it to work.
If you create a Custom Setting named "SessionData", and add your custom fields (that represent the data you want to store in session), you could save data to it like this:
Database.SaveResult result = Database.insert(new SessionData__c(YourFieldHere='Your value here etc'));
System.debug(result.getID());
Then use the resulting custom setting ID to store in a cookie. While custom settings can be accessed using normal SOQL, the advantage is that the data is cached and can be accessed like this:
if (SessionData__c.getAll().containsKey('unique ID from cookie here'))
{
System.debug(SessionData__c.getInstance('unique ID from cookie here').YourFieldHere);
}
Keep in mind that custom settings weren't really designed for this, so you'll need to periodically purge old custom settings data, as normal session management systems do.
See the Apex Custom Settings documentation for more details.
I think Visualforce View State might be useful to you:
Visualforce pages that contain a form component also contain an encrypted, hidden form field that encapsulates the view state of the page. This view state is automatically created, and as its name suggests, it holds the state of the page - state that includes the components, field values and controller state.
You should use Javascript cookies for this.
You could also use Apex cookies, but then you'd need to make sure that each request hits the server (and not the caching layer).
for Apex Cookie you can use following code:
//Setting Cookie
public void setCookie() {
Cookie userCookie = new Cookie('CookieName', fieldValueToBeStoredAsCookie, null, 315569260, false); //Here 315569260 represents cookie expiry date = 10 years. You can set this to what ever expiry date you want. Read apex docs for more details.
ApexPages.currentPage().setCookies(new Cookie[] {
userCookie
});
}
//Reading Cookie
Cookie cookie = ApexPages.currentPage().getCookies().get('CookieName');
if (cookie != null) {
String fieldValueToBeStoredAsCookie = cookie.getValue();
}

Resources