In several early previews of ASP.NET MVC, arguments to controller methods would be resolved by inspecting the query string, then the form, then the cookies and server variables collections, as documented in this post from Stephen Walther.
For example, this code used to work:
public class MyController : Controller {
// This should bind to Request.Cookies["userId"].Value
public ActionResult Welcome(int userId) {
WebUser wu = WebUser.Load(userId);
ViewData["greeting"] = "Welcome, " + wu.Name;
return(View());
}
}
but now running against the release candidate, it throws an exception because it can't find a value for userId, even though userId definitely appears in the request cookies.
Was this change covered anywhere in the release notes? If this is a change to the framework, is there now a recommended alternative to binding cookies and server variables in this way?
EDIT: Thanks to those of you who have responded so far. I may have picked a bad example to demonstrate this; our code uses cookies for various forms of "convenient" but non-essential persistence (remembering ordering of search results, that kind of thing), so it's by no means purely an authentication issue. The security implications of relying on user cookies are well documented; I'm more interested in current recommendations for flexible, easily testable techniques for retrieving cookie values. (As I'm sure you can appreciate, the above example may have security implications, but is very, very easy to test!)
I believe it was the security implications that persuaded them to take these out:
The comments in Stephen Walther's post ASP.NET MVC Tip 15, leading to Phil Haack's posting User Input in Sheep's Clothing, especially his comment here:
#Troy - Step one is to dissuade devs from that line of thinking in the first place. ;) Step one prime (in parallel) is for us to remove the possibility of this line of thinking in this case.
The larger point still stands, we can make this change (after discussing it, we probably will), but that doesn't mean that it's suddenly safe to trust action method parameters.
Coupled with the complications of how you would call these methods from the various action builder classes.
I can't seem to find any explicit documentation one way or another about the controllers behaving like this other than Stephen's post, so I guess it was "quietly dropped".
I don't believe the cookies are checked anymore, and I'm not sure if it is intentional or not.
In an app against the RC I wrote recently I used the CookieContainer code from this post and a custom authorize attribute on classes like this:
public class LoginRequiredAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
IAppCookies a = new AppCookies(new CookieContainer());
return a.UserId != null; /* Better checks here */
}
}
My AppCookies.cs just has a method for UserId like this (auto resolves to int/null for you):
public int? UserId
{
get { return _cookieContainer.GetValue<int?>("UserId"); }
set { _cookieContainer.SetValue("UserId", value, DateTime.Now.AddDays(10)); }
}
Then just make sure your web.config is setup to point to your login page like this:
<authentication mode="Forms">
<forms loginUrl="~/Login"/>
</authentication>
This means in my controller to get a UserId I need to do something like this to retrieve my cookie:
[LoginRequiredAttribute]
public class RandomController : Controller
{
BaseDataContext dbContext = new BaseDataContext();
private readonly IAppCookies _cookies = new AppCookies(new CookieContainer());
public ActionResult Index()
{
return View(new RandomViewData(dbContext, _cookies.UserId));
}
}
Besides the obvious security implications, why would you need to pass the cookie through in the route anyway?
Surely you would be better off having an Authorize attribute on the action, indicating that the user should be authenticated before the action is executed.
At the end of the day, I think (hope) Microsoft has closed this as it's quite a large security issue. If this is the case, you should consider rewriting your application to comply with this.
Related
I'm trying to get social authentication working in an asp.net-hosted Nancy web app using the WorldDomination SimpleAuthentication plugin for Nancy. TL;DRs skip to the question bolded at the bottom of the question.
Both are pretty nice, but there is a big documentation gap between the authentication process (well covered) and identifying the authenticated user during requests other than the initial authenticate request (nothing).
Nancy provides for basic and forms authentication via additional packages, and the hooks they provide are pretty straight forward. WorldDomination does not provide much information past the actual authentication process. There seems to be a distinct lack of Happy Path for the normal "who is the user making this request" process that has to happen every time a user hits the server.
I've been spending a fair amount of time to figure this part out, but my research hasn't led me to any obvious solutions. The WD demo apps are bereft of request code other than authentication requests, and the codebase doesn't appear to contain anything dealing with the normal request cycle.
My best guess is that I need to integrate with forms auth, implementing Nancy's forms auth hooks and using what I get back from WD to populate my own types.
This doesn't exactly seem like the happiest of happy paths. In fact, it seems to be more of a "do lots of work you lazy bastard" path.
What, exactly, is the recommended happy path for integrating WorldDomination's social OAuth authentication providers and Nancy? I'm concentrating on the standard "who is this person that requests of me" page lifecycle part here.
Bonus points (from my hordes of sockpuppet accounts I will create for the purpose) for how this happy path handles users logging out as well!
With Simple Authentication, we simply handle the authentication with a provider in a simple way. Every provider has slightly different implementations, different naming, different promises, so we can to consolidate all that into Simple Authentication and make it easier for a developer to implement into their website.
Thats why the Nancy.SimpleAuthentication package exists. Because we know how Nancy works we have simplified the integration into Nancy by creating the modules for you to handle redirection, authentication callback, etc.
The problem is, we simply do not know how you authenticate a user against your website.
We can handle the whole forms auth scenario ourselves, and I actually plan to in the future. (have to implement claims first which I'm 60% way through), but it will still at bare minimum require you to implement the IAuthenticationCallbackProvider
public class Test : IAuthenticationCallbackProvider
{
public dynamic Process(
NancyModule nancyModule,
AuthenticateCallbackData model)
{
//Query for the database user based on the Provider / Id
//Authenticate the user
//Call LoginWithoutRedirect, and redirect how you want...
// or LoginWithRedirect
return nancyModule.Negotiate.WithView("AuthenticateCallback")
.WithModel(model);
}
}
This class is required in order for you to authenticate the user against your database.
The thing we thought about tho is 95% of the time the user putting in the authentication, most likely already has some form of authentication already. Usually Forms Auth.
So assuming you've pulled in SimpleAuthentication, and wired up your IAuthenticationCallbackProvider class. All you really need to do is implement the Forms Auth stuff, which is pretty much 1 class, and a method call.
In the provider you need to call the LoginWithoutRedirect method so that Nancy can create an auth cookie.
Then you need to setup the IUserMapper class to tell Nancy how to get the user from the Database. If you're using RavenDB this would look something like:
public class DatabaseUser : IUserMapper
{
public IDocumentStore DocumentStore { get; set; }
public DatabaseUser(IDocumentStore documentStore)
{
DocumentStore = documentStore;
}
public IUserIdentity GetUserFromIdentifier(
Guid identifier,
NancyContext context)
{
using (var session = DocumentStore.OpenSession())
{
var member = session.Query<Member>()
.SingleOrDefault(x => x.Identifier == identifier);
if (member == null)
return null;
return new UserIdentity
{
UserName = member.DisplayName,
Claims = new []
{
"NewUser",
"CanComment"
}
};
}
}
}
Configured in the bootstrapper like:
protected override void ConfigureRequestContainer(
TinyIoCContainer container,
NancyContext context)
{
base.ConfigureRequestContainer(container, context);
container.Register<IUserMapper, DatabaseUser>();
}
protected override void RequestStartup(
TinyIoCContainer container,
IPipelines pipelines,
NancyContext context)
{
base.RequestStartup(container, pipelines, context);
var formsAuthConfiguration = new FormsAuthenticationConfiguration
{
RedirectUrl = "~/login",
UserMapper = container.Resolve<IUserMapper>(),
};
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
}
And that's really it...
I personally don't think it's a lot of code that you have to write. Both Nancy and Simple Authentication have done most of the leg work for you :)
I hope we can make SimpleAuthentication even easier in the future by removing the need for the Forms Auth, but for now I think we have a pretty good solution.
Helpful links:
http://www.philliphaydon.com/2012/12/18/forms-authentication-with-nancyfx/
http://www.philliphaydon.com/2013/01/31/oauth-with-nancyfx-and-world-domination-authentication/
The 2nd link for World Domination, although there's a bit of renaming, it's mostly the same. I do plan to do an updated blog post and revamp the wiki when we have polished off Claims.
I hope that helps you.
Edit:
I've made note to create a more end-to-end solution demo project.
In my MVC4 app, there are some actions that need to behave differently depending on whether you are logged in (FormsAuthentication in my case) or not.
For example, I have an AccountController that has a method "RenderAccountAndProfile". If logged out, the corresponding partial view displays a log in prompt and button. If a user is logged in, the user's profile links appear, alongside a log out button.
The approach I've taken thus far in projects is to simply have an if statement along the lines...
if (HttpContext.User.Identity.IsAuthenticated)
{
...
}
else
{
...
}
However, I've just created what I think is a reasonably elegant alternative to this approach.
I have created a new attribute called AnonymousUsersOnly, which is very simple:
public class AnonymousUsersOnlyAttribute : System.Web.Mvc.ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(System.Web.Mvc.ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
return !controllerContext.HttpContext.User.Identity.IsAuthenticated;
}
}
My AccountController class is decorated with the Authorize attribute. This then enabled me to have the following code:
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
[AnonymousUsersOnly]
[ActionName("RenderAccountAndProfile")]
public ActionResult RenderAccountAndProfile_Anonymous()
{
// create a "logged out" view model
return Content("**NOT LOGGED IN** - LOG IN HERE");
}
[ActionName("RenderAccountAndProfile")]
public ActionResult RenderAccountAndProfile_Authorized()
{
// create a "logged in" view model
return Content("**LOGGED IN** - LOG OUT");
}
}
I quite like this approach because my action methods conform to the Single Responsibility Principle. Each method now only deals with either a logged in situation or a logged out situation. I don't need any "if" statements directing the traffic any more.
This ought to make Unit Testing easier too, since each method is now only concerned with one outcome, not two. We can write unit tests to test each outcome separately, calling different methods.
Clearly, I can't have two methods with the same signature so that's why I have to use the ActionName attribute.
I would appreciate your critique here. Do you think this is an elegant solution or not? What are the pros and cons to this approach? And what security implications/risks could there be with this?
The problem you have here is a strategy pattern problem. And you've implemented a (non-standard) strategy pattern, with a pretty clever implementation. I worry that it is too clever. That cleverness makes what the code is doing a lot less obvious to the uninitiated.
Offhand, I'd favor not bothering. I often write controllers as very thin adapters over domain objects/services. Therefore, I'm willing to take a pragmatic approach to design perfection in the controller. In deciding between slight design problems and obvious code, always choose obvious code.
If you have thicker controllers, or some other reason to be really concerned about this problem here, you might consider a more traditional strategy pattern, perhaps aided by an abstract factory that provides different strategy implementations based on authentication state. This meets your design objectives, and will be more immediately familiar to other programmers (if they know design patterns).
All that being said, I don't think keeping your clever solution will hurt anything that much. I'd be tempted to change the name; Deny seems like a weird verb there to me. Perhaps AnonymousUsersOnly, that would be a little more communicative to future programmers.
I much better choose to (pseudocode) :
PartialView UserProfile() { ... }
PartialView Login() { ... }
and in view :
if (User.IsAuthenticated) {
#Html.Action("UserProfile")
} else {
#Html.Action("Login")
}
which can also be a DisplayTemplate, helper, or whatever you like so you just end up using
#Html.DisplayFor(m=> User)
Preamble: this is a bit of a philosophical question. I'm looking more for the "right" way to do this rather than "a" way to do this.
Let's imagine I have some products, and an ASP.NET MVC application performing CRUD on those products:-
mysite.example/products/1
mysite.example/products/1/edit
I'm using the repository pattern, so it doesn't matter where these products come from:-
public interface IProductRepository
{
IEnumberable<Product> GetProducts();
....
}
Also my Repository describes a list of Users, and which products they are managers for (many-many between Users and Products). Elsewhere in the application, a Super-Admin is performing CRUD on Users and managing the relationship between Users and the Products they are permitted to manage.
Anyone is allowed to view any product, but only users who are designated as "admins" for a particular product are allowed to invoke e.g. the Edit action.
How should I go about implementing that in ASP.NET MVC? Unless I've missed something, I can't use the built-in ASP.NET Authorize attribute as first I'd need a different role for every product, and second I won't know which role to check for until I've retrieved my Product from the Repository.
Obviously you can generalise from this scenario to most content-management scenarios - e.g. Users are only allowed to edit their own Forum Posts. StackOverflow users are only allowed to edit their own questions - unless they've got 2000 or more rep...
The simplest solution, as an example, would be something like:-
public class ProductsController
{
public ActionResult Edit(int id)
{
Product p = ProductRepository.GetProductById(id);
User u = UserService.GetUser(); // Gets the currently logged in user
if (ProductAdminService.UserIsAdminForProduct(u, p))
{
return View(p);
}
else
{
return RedirectToAction("AccessDenied");
}
}
}
My issues:
Some of this code will need to be repeated - imagine there are several operations (Update, Delete, SetStock, Order, CreateOffer) depending on the User-Products relationship. You'd have to copy-paste several times.
It's not very testable - you've got to mock up by my count four objects for every test.
It doesn't really seem like the controller's "job" to be checking whether the user is allowed to perform the action. I'd much rather a more pluggable (e.g. AOP via attributes) solution. However, would that necessarily mean you'd have to SELECT the product twice (once in the AuthorizationFilter, and again in the Controller)?
Would it be better to return a 403 if the user isn't allowed to make this request? If so, how would I go about doing that?
I'll probably keep this updated as I get ideas myself, but I'm very eager to hear yours!
Thanks in advance!
Edit
Just to add a bit of detail here. The issue I'm having is that I want the business rule "Only users with permission may edit products" to be contained in one and only one place. I feel that the same code which determines whether a user can GET or POST to the Edit action should also be responsible for determining whether to render the "Edit" link on the Index or Details views. Maybe that's not possible/not feasible, but I feel like it should be...
Edit 2
Starting a bounty on this one. I've received some good and helpful answers, but nothing that I feel comfortable "accepting". Bear in mind that I'm looking for a nice clean method to keep the business logic that determines whether or not the "Edit" link on the index view will be displayed in the same place that determines whether or not a request to Products/Edit/1 is authorised or not. I'd like to keep the pollution in my action method to an absolute minimum. Ideally, I'm looking for an attribute-based solution, but I accept that may be impossible.
First of all, I think you already half-way figured it, becuase you stated that
as first I'd need a different role for every product, and second I won't know which role to check for until I've retrieved my Product from the Repository
I've seen so many attempts at making role-based security do something it was never intended to do, but you are already past that point, so that's cool :)
The alternative to role-based security is ACL-based security, and I think that is what you need here.
You will still need to retrieve the ACL for a product and then check if the user has the right permission for the product. This is so context-sensitive and interaction-heavy that I think that a purely declarative approach is both too inflexible and too implicit (i.e. you may not realize how many database reads are involved in adding a single attribute to some code).
I think scenarios like this are best modeled by a class that encapsulates the ACL logic, allowing you to either Query for decision or making an Assertion based on the current context - something like this:
var p = this.ProductRepository.GetProductById(id);
var user = this.GetUser();
var permission = new ProductEditPermission(p);
If you just want to know whether the user can edit the product, you can issue a Query:
bool canEdit = permission.IsGrantedTo(user);
If you just want to ensure that the user has rights to continue, you can issue an Assertion:
permission.Demand(user);
This should then throw an exception if the permission is not granted.
This all assumes that the Product class (the variable p) has an associated ACL, like this:
public class Product
{
public IEnumerable<ProductAccessRule> AccessRules { get; }
// other members...
}
You might want to take a look at System.Security.AccessControl.FileSystemSecurity for inspiration about modeling ACLs.
If the current user is the same as Thread.CurrentPrincipal (which is the case in ASP.NET MVC, IIRC), you can simplyfy the above permission methods to:
bool canEdit = permission.IsGranted();
or
permission.Demand();
because the user would be implicit. You can take a look at System.Security.Permissions.PrincipalPermission for inspiration.
From what you are describing it sounds like you need some form of user access control rather than role based permissions. If this is the case then it needs to be implemented throughout your business logic. Your scenario sounds like you can implement it in your service layer.
Basically you have to implement all functions in your ProductRepository from the perspective of the current user and the products are tagged with permissions for that user.
It sounds more difficult than it actually is. First off you need a user token interface that contains the user information of uid and role list (if you want to use roles). You can use IPrincipal or create your own along the lines of
public interface IUserToken {
public int Uid { get; }
public bool IsInRole(string role);
}
Then in your controller you parse the user token into your Repository constructor.
IProductRepository ProductRepository = new ProductRepository(User); //using IPrincipal
If you're using FormsAuthentication and a custom IUserToken then you can create a Wrapper around the IPrincipal so your ProductRepository is created like:
IProductRepository ProductRepository = new ProductRepository(new IUserTokenWrapper(User));
Now all your IProductRepository functions should access the user token to check permissions. For example:
public Product GetProductById(productId) {
Product product = InternalGetProductById(UserToken.uid, productId);
if (product == null) {
throw new NotAuthorizedException();
}
product.CanEdit = (
UserToken.IsInRole("admin") || //user is administrator
UserToken.Uid == product.CreatedByID || //user is creator
HasUserPermissionToEdit(UserToken.Uid, productId) //other custom permissions
);
}
If you wondering about getting a list of all products, in your data access code you can query based on permission. In your case a left join to see if the many-to-many table contains the UserToken.Uid and the productId. If the right side of the join is present you know the user has permission to that product and then you can set your Product.CanEdit boolean.
Using this method you can then use the following, if you like, in your View (where Model is your Product).
<% if(Model.CanEdit) { %>
Edit
<% } %>
or in your controller
public ActionResult Get(int id) {
Product p = ProductRepository.GetProductById(id);
if (p.CanEdit) {
return View("EditProduct");
}
else {
return View("Product");
}
}
The benefit to this method is that the security is built in to your service layer (ProductRepository) so it is not handled by your controllers and cannot be bypassed by your controllers.
The main point is that the security is placed in your business logic and not in your controller.
The copy paste solutions really become tedious after a while, and is really annoying to maintain. I would probably go with a custom attribute doing what you need. You can use the excellent .NET Reflector to see how the AuthorizeAttribute is implemented and perform your own logic to it.
What it does is inheriting FilterAttribute and implementing IAuthorizationFilter. I can't test this at the moment, but something like this should work.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class ProductAuthorizeAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
object productId;
if (!filterContext.RouteData.Values.TryGetValue("productId", out productId))
{
filterContext.Result = new HttpUnauthorizedResult();
return;
}
// Fetch product and check for accessrights
if (user.IsAuthorizedFor(productId))
{
HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
cache.SetProxyMaxAge(new TimeSpan(0L));
cache.AddValidationCallback(new HttpCacheValidateHandler(this.Validate), null);
}
else
filterContext.Result = new HttpUnauthorizedResult();
}
private void Validate(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
// The original attribute performs some validation in here as well, not sure it is needed though
validationStatus = HttpValidationStatus.Valid;
}
}
You could probably also store the product/user that you fetch in the filterContext.Controller.TempData so you can fetch it in the controller, or store it in some cache.
Edit: I just noticed the part about the edit link. The best way I can think of is factoring out the authorization part from the attribute and make a HttpHelper for it that you can use in your view.
I tend to think that authorization is part of your business logic (or at least outside of your controller logic anyway). I agree with kevingessner above, in that the authorization check should be part of the call to fetch the item. In his OnException method, you could show the login page (or whatever you have configured in the web.config) by something like this:
if (...)
{
Response.StatusCode = 401;
Response.StatusDescription = "Unauthorized";
HttpContext.Response.End();
}
And instead of making UserRepository.GetUserSomehowFromTheRequest() calls in all the action methods, I would do this once (in an override of the Controller.OnAuthorization method for example), then stick that data somewhere in your controller base class for later use (e.g. a property).
I think that it's unrealistic, and a violation of the separation of concerns, to expect to have controller/model code control what the view renders. The controller/model code can set a flag, in the view model, that the view can use to determine what it should do, but I don't think that you should expect a single method to be used by both controller/model and view to control both access to and rendering of the model.
Having said that you could approach this in either of two ways -- both would involve a view model that carries some annotations used by the view in addition to the actual model. In the first case, you can use an attribute to control access to the action. This would be my preference, but would involve decorating each method independently -- unless all of the actions in a controller have the same access attributes.
I've developed a "role or owner" attribute for just this purpose. It verifies that the user is in a particular role or is the owner of the data being produced by the method. Ownership, in my case, is controlled by the presence of a foreign key relationship between the user and the data in question -- that is, you have a ProductOwner table and there needs to be a row containing the product/owner pair for the product and current user. It differs from the normal AuthorizeAttribute in that when the ownership or role check fails, the user is directed to an error page, not the login page. In this case, each method would need to set a flag in the view model that indicates that the model can be edited.
Alternatively, you could implement similar code in the ActionExecuting/ActionExecuted methods of the controller (or a base controller so that it applies consistently across all controllers). In this case, you would need to write some code to detect what kind of action is being executed so you know whether to abort the action based on the ownership of the product in question. The same method would set the flag to indicate that the model can be edited. In this case, you'd probably need a model hierarchy so you could cast the model as an editable model so that you can set the property regardless of the specific model type.
This option seems more coupled to me than using the attribute and arguably more complicated. In the case of the attribute you can design it so that it takes the various table and property names as attributes to the attribute and uses reflection to get the proper data from your repository based on the attribute's properties.
Answering my own question (eep!), Chapter 1 of Professional ASP.NET MVC 1.0 (the NerdDinner tutorial) recommends a similar solution to mine above:
public ActionResult Edit(int id)
{
Dinner dinner = dinnerRepositor.GetDinner(id);
if(!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
return View(new DinnerFormViewModel(dinner));
}
Asides from making me hungry for my dinner, this doesn't really add anything as the tutorial goes on to repeat the code implementing the business rule immediately in the matching POST Action Method, and in the Details view (actually in a child partial of the Details view)
Does that violate SRP? If the business rule changed (so that e.g. anyone who had RSVP'd could edit the dinner), you'd have to change both GET and POST methods, and the View (and the GET and POST methods and View for the Delete operation too, although that's technically a seperate business rule).
Is pulling the logic out into some kind of permissions arbitrator object (as I've done above) as good as it gets?
You're on the right track, but you can encapsulate all of the permission check into a single method like GetProductForUser, which takes a product, user, and the required permission. By throwing an exception that's caught in the controller's OnException handler, the handling is all in one place:
enum Permission
{
Forbidden = 0,
Access = 1,
Admin = 2
}
public class ProductForbiddenException : Exception
{ }
public class ProductsController
{
public Product GetProductForUser(int id, User u, Permission perm)
{
Product p = ProductRepository.GetProductById(id);
if (ProductPermissionService.UserPermission(u, p) < perm)
{
throw new ProductForbiddenException();
}
return p;
}
public ActionResult Edit(int id)
{
User u = UserRepository.GetUserSomehowFromTheRequest();
Product p = GetProductForUser(id, u, Permission.Admin);
return View(p);
}
public ActionResult View(int id)
{
User u = UserRepository.GetUserSomehowFromTheRequest();
Product p = GetProductForUser(id, u, Permission.Access);
return View(p);
}
public override void OnException(ExceptionContext filterContext)
{
if (typeof(filterContext.Exception) == typeof(ProductForbiddenException))
{
// handle me!
}
base.OnException(filterContext);
}
}
You just have to provide ProductPermissionService.UserPermission, to return a user's permission on a given product.By using a Permission enum (I think I've got the right syntax...) and comparing permissions with <, Admin permissions imply Access permissions, which is pretty much always right.
You can use a XACML based implementation. This way you can externalize authorization and also have a repository for your policies outside of your code.
I understand that TempData is designed to work only between a single page request. But I think have a situation I think breaks the intended functionality.
I use Controllers in preference to Handlers for delivering images. I don't now if that is best practice, but it works very well for me. The problem though, is that each call to one of the image Actions obviously uses up a TempData credit.
Is there a way in MVC to say "This Controller/Action is out of the scope of normal page requests" and therefore either persist the TempData or remove itself from the TempData life cycle completely?
Rich
My solution has been to create an Attribute that persists the TempData across page requests. My initial reaction to this is "yuck", but I want to effectively exclude any controllers decorated with the Attribute from the TempData lifecycle.
using System.Web.Mvc;
namespace K3R.Web.Mvc.Filters {
public sealed class PersistTempDataAttribute : ActionFilterAttribute {
public PersistTempDataAttribute() { }
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var tempData = filterContext.Controller.TempData;
if (tempData == null || tempData.Count == 0)
return;
string[] keys = new string[tempData.Keys.Count];
tempData.Keys.CopyTo(keys, 0);
foreach (var key in keys)
tempData[key] = tempData[key];
}
}
}
Any feedback on a better solution would be appreciated.
Rich
I'm not sure of the exact answer to this as I'm pretty new to MVC myself. However, I have heard of people having trouble with AJAX requests killing off the TempData before they wanted it to as well.
I guess you could implement your own Session based system to store this information?
I'm sure someone else will have a more complete answer for you soon, though!
I do not completely understand your way of doing it and why but I would rather go with handlers. Or if you stick to controller actions, I can say that this worked for me fine without any issues for delivering images on-the-fly and I used FileContentResult in my controller actions.
Like this:
return File(imageBytes, imageType);
You get the bytes out of datastore or somewhere..
hope it helps
This seems like a pretty stupid question, but I'm trying to figure out the best way to do this. Would you simply redirect to a /Logout page and have the controller call the FormsAuthentication.SignOut function?
That was my first thought, but then I wondered if it could be abused by third party websites. Let's say someone just decides to post a link to your /Logout page. The user would get signed out of your application. Is there a good way to prevent that?
If you are concerned about a user getting accidentally logged out of you application through the use of a malicious link, you can check the Referrer to make sure that the logout is coming from your site (or is NULL in the case where the user simply types the URL in).
I actually don't worry about this since logging someone out is annoying but not necessarily a security risk.
Such a malicious link would be an example of a class of security vulnerabilities known as cross site request forgery, CSRF. A logout link is relatively harmless, but a remote site could set up a number of hidden forms and post them to your site to perform any action possible through POST.
The most common counter-measure is to include a challenge, a random hidden value in each form, and then check for that value. Checking the referer header could work, but note that some browsers don't send referer at all.
Read more: http://en.wikipedia.org/wiki/Cross-site_request_forgery
This is an old question, but here is a modern example with MVC:
[Authorize]
public RedirectResult Logout()
{
FormsAuthentication.SignOut();
return this.Redirect("/");
}
You can ensure that the Logout action is only able to be called by somebody who is logged in by applying the Authorize attribute to it.
This is what I use.
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
Seems to work fine.
Third party websites are only going to log themselves out. So they wouldn't be achieving anything different from actually clicking Logout.
The new ASP.net MVC Beta contains an AccountController, which may be worth looking at, as it essentially implements everything from Registration to Login/Logout to Forgot Password functionality. Not sure how good it is, but a good starting Point for sure.
Derive from ActionResult
public class LogoutResult : ActionResult
{
private readonly IAuthenticationService _authenticationService;
private readonly IWebContext _context;
public LogoutResult(IAuthenticationService authenticationService, IWebContext context)
{
_authenticationService = authenticationService;
_context = context;
}
public override void ExecuteResult(ControllerContext context)
{
_authenticationService.Logout();
_context.Abandon();
_context.Redirect("~/");
}
}
You should look for a cookie or something that identifies the client as the true user.