I have an ASP.Net MVC 4 Website. When I started this site I had little to no web programming experience, and I don't believe I set up all of it appropriately.
I have pages, such as Home, Login, Register, which I consider public,
pages like, VerifyPIN and AccountCreated, which I consider internal
and pages like Dashboard and Profile, which are private.
I feel public pages should be accessed by anonymous users, but Login and Register should not be accessible once a user logs in.
Internal pages I want to only be available upon server redirect and never through the browser, aka I don't want a user to be able to type in www.MySite.com/AccountCreated.
And private pages are for a logged in user only.
I feel I have private pages working fine, but I don't know how to limit access to the other pages as described above. Perhaps my notion to not allow a logged in user to access the log in page is miss-found.
My site is relatively small due to the ajax nature of the site. I use [AllowAnonymous] on all public pages but then a logged in user can still access those and I am not sure how to prevent that.
My questions are,
How can I prevent a user from accessing a page via the address bar (www.MySite.com/AccountCreated)
How can I prevent access to a [AllowAnonymous] page once a user has logged in.
EDIT
Is it normal to prevent logged on users from accessing certain anonymous pages, such as login?
You may always check if user is already logged in. If he did, he will be redirected to another page:
public ActionResult AccountCreated(some params)
{
if (Request.IsAuthenticated)
{
return RedirectToAction("Index")
}
else
{
// login logic here
}
}
You may also check it directly in View:
#if (Request.IsAuthenticated)
{
<span> You are already logged in</span>
}
else
{
//Login form here
}
well for your first question you can use the [Authorize] action filter.
For your other question, I guess you will have to write your own implementation. You will need to create a new IAuthorizationFilter and use it instead of [AllowAnonymous]
Just some ideas(didn't try them actually).
For question 1 - if AccountCreated is an action that means that the registration form actually POSTs to that URL - i.e. It is accessible from outside. I suggest you apply an HttpPost attribute to it so it will process only POST requests - there is not much you can do here
For point two you can do something like this in your action method
if (HttpContext.User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Home");
}
else return View();
Just a thought, Signout user when a user goes to Login or Register page .
Hope you have not provided any link on private pages for a user to go to login or Register
page or check if user is authenticated redirect it to Home page, frankly you can not stop a user to write a URL and pressing the enter button, or just give a notification that You'll be logged out if you proceed to whatever page.
Related
When I first started developing my MVC app (which requires authorization, to selectively render a SPA, which in turn does it's own authorization via ADAL.js) I simply decorated the HomeController's Index method with [Authorize] like thus,
[Authorize]
public ActionResult Index(bool ok = false)
{
return View();
}
and everything works fine.
However, sometimes I want users to share URLs with each other. My SPA uses hash-based routing to render various states. As long as both users are logged in already, they can share links with each other and everything magically works ok.
But, if the user clicking a link is yet-to-login, we lose the state communicated in the URL.. everything after the hash.. e.g. if user A sends unauthenticated user B this URL: http://mycorp.com/#/orders/85/edit the HomeController's going to notice user B is unauthenticated, and the middleware is going to kick user B over to our Microsoft login page.. and when he redirects back, the critical #/orders/85/edit portion of the URL in is gone.
My solution was to remove the [Authorize] attribute from the Index action, and instead do something like this:
public ActionResult Index(bool ok=false)
{
if (!User.Identity.IsAuthenticated)
{
if (!ok)
return View("SetCookie_Then_RedirectWith_OK_InQueryString");
if (ok)
//how do I do this part?
return new RedirectResult(Microsoft.Owin.Security.OpenIdConnect.Url);
}
else
//now we're authenticated,
//let JS resume by looking for cookie set previously
return View("Index");
}
Notice the how do I do this part comment above. That's my question.
+++++++++ FWIW ++++++++++
My app features a Startup.Auth.cs file with the familiar middleware declared...
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
...blah...
}
);
I presume I can do something fancy here in Startup.Auth.cs w/Notification Handlers but, I was hoping to directly call the middleware into action from the Index method.
I'm in the process of creating an Intranet site using Windows Authentication.
Maybe I'm not going about this the best way, but I'm trying to load partial views via calling a controller method that has an Authorize action filter wrapped around it, so that only authorized individuals are able to see that portion of the page. Say, for instance, I wanted to load administrator tools onto the page but only if the logged-in individual is an administrator.
So on the index.cshtml page I might have something like:
#Html.Action("LoadAdminTools","ControllerName")
The Controller would contain the code:
[Authorize(Roles="Admins")]
public ActionResult LoadAdminTools()
{
return PartialView("_AdminToolsPartialView");
}
And then the partial view containing the admin controls (or whatever) would render to the page - only if the logged-in user was part of the Admins role.
The 'problem' I'm having is that if the person logged-in is not authorized to load the partial view, the browser pops up the login dialog asking for the user's credentials. Closing the dialog without inputting any credentials causes the expected results - the partial view doesn't load while the rest of the page does. Cool, but annoying. Input the incorrect credentials and you get a 401 error - also as expected.
If it helps: In IIS, Anonymous Authentication is disabled, Windows Authentication is enabled.
"Automatic logon with current user name and password" is selected in Internet Options under "Security Settings - Local Intranet Zone."
My question is this: Is there a way to use the [Authorize] action filter to load a partial view (or to do anything, really) without the browser asking the user to log in? Just have it take the current logged-in credentials, check if they comply with the action filter, if they do, load the partial view, if not, then don't. If there isn't, is there simply a better way of going about what I want to accomplish here?
UPDATE
Beautiful. I read the solution to the question you posted, Mystere Man, created a new class inside the Controller's folder called IntranetAuthorizeAttribute.cs, threw in the code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class IntranetAuthorizeAttribute : System.Web.Mvc.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAuthenticated)
{
filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult(403);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
Replaced the Authorize filter with my new IntranetAuthorize filter:
[IntranetAuthorize(Roles="Admins")]
public ActionResult LoadAdminTools()
{
return PartialView("_AdminToolsPartialView");
}
And now it loads the page just fine with no browser login dialog - with the partial view when it's an authorized user, and without the partial view when it is not an authorized user =)
Thank you!
Unfortunately, ASP.NET (and thus MVC) conflates Authorization and Authentication in may scenarios.
Check this question for a solution.
Why does AuthorizeAttribute redirect to the login page for authentication and authorization failures?
This is my application's current workflow for user registration.
register form ( GET /register )
submit the form ( POST /register )
after success, redirect route to "/registerSuccess"
in (GET /registerSuccess ) view, I want to show the message like "username has been registered successfully".
Controller Actions:
public ActionResult Register()
{
return View();
}
[HttpPost]
public ActionResult Register(string name, string password, string confirmPassword)
{
TempData["registered_user"] = name;
return RedirectToRoute("RegisterSuccess");
}
public ActionResult RegisterSuccess()
{
return View();
}
RegisterSuccess View
<h2><%: TempData["registered_user"] %> has been registered successfully.</h2>
It is working fine except the username is empty when user refresh the page (GET /registerSuccess).
Is it normal behavior in other applications? or Should I do something about it? :) (It is my pet project and requirements come from me :P )
update: Actually, there is one more step which is registered user required admin's approval. So, I can't let user log in after successful register. I'm currently using cookie-based FormsAuthentication to track logged in user.
I prefer to use PRG pattern rather than showing flash message in the submit form.
that is normal behaviour.. when the user 'refreshes' the page is requested directly not through your Register function so registered_user key is gone by then. When you do the initial registration you should use a session marker (say a cookie) to track a successfully logged in user.
Using cookies tutorial is a good place to start
If you were to use HTTP authentication (say Basic Auth) then the browser submits the username and password (in clear text for basic auth) with every request that sends out a 401 Unauthorized (see HTTP/1.0 Protocol)
Hmm...
Normally you should think of a SSO solution, that holds your User's authentication (token).
then you would just need to display the current authenticated user (as a completed registration normally logs in the user)
other solution would be, to add the user in the url, so that the success page has all parameters.
/registerSuccess/<username>
(and maybe check, that the logged in user is just registered, and is the same)
for your own fun project, i would do the second one. ist just plain simple.
I'm currently working on the user registration in my project. After the registration is done I wish to show some confirmation to the user. I decided to create another view. That's fine.
Now, if after the registration I just return the view like:
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
return View ("ConfirmationView");
}
}
Everything is working as desired. No changed url in the title bar. But... If I click the refresh button, the browser will submit the data from the form again which I do not want.
Then I decided to create a separate action, but that means it will produce a new url in the address bar. I do not want the user to click refresh now because this view will not be able to sensibly display the confirmation information again. Is there any way to make an action not accessible directly? Or at least any way to determine whether it was called directly or by redirection? In the latter case I would just take the user away from that page to maybe the home page.
Any way to accomplish this?
So I found the solution myself.
One can use TempData to detect the repeated or external action calls.
public class MyController : Controller
{
[AcceptVerbs (HttpVerbs.Post), ValidateAntiForgeryToken]
public ActionResult Registration (FormCollection form)
{
/* Some logic goes here */
TempData["RedirectCall"] = true;
return RedirectToAction ("Confirmation");
}
[AcceptVerbs (HttpVerbs.Get)]
public ActionResult Confirmation ()
{
if (TempData["RedirectCall"] == null)
return RedirectToAction ("StartPage", "Home");
return View ();
}
}
Nice and simple. :)
One way to solve your problem is to attach a guid or similar type of "random" data to a user session, and check for a valid session when the page is requested. If there is none, you redirect to a page saying that this url is not available at the moment, and that the user will soon be redirected (and then redirect to home after 5 seconds or so using js).
Roughly it would work like this:
When the user is registered, a session cookie is created with for example a GUID. The GUID is also stored in a database table, in which you have one column for the UserID primary key and one for the GUID. You also create an authentication cookie, thus logging the user on to your site.
When all datacalls etc are done, the user has been successfully registered and so on, you redirect to the confirmation page.
When the confirmation page is loaded, the user is automatically logged on (because you created the authentication cookie in step 1). You can then check for a row in the UserID-GUID table corresponding to the logged on user.
a) If there is such a row, you delete the row, and display the confirmation page with all the information.
b) If there is no such row, you display the error message and redirect. As you deleted the row when you showed the message the first time, the user will not be able to access the confirmation page again.
Note: If you use this approach (or some other that makes the confirmation page available only once) you should make sure that it is clearly stated on the confirmation page that the user won't be able to access that page again.
if(Request.UrlReferrer == null)
{
return RedirectToAction("Index");
}
In C# with MVC, i want to write a common utility or class in which if a particular conditoin fails need to redirect to login page.
For ex.: When the user logged in to the website, userid will be added to session. To Access the "ManageUsers" page, the user should be logged in as admin, else i need to redirect to Login page. i need to check this condition in some of the other similar pages also. i dont want to check either the user is admin or normal user while login. i need to check this in common class.
Any suggesstions?
Actually I think this is not particularly good behavior for an application. I think you ought to disable (or hide) any actions that a user is not able to perform. In the case where the user hand-enters a URL, or uses a bookmark from when they had the privilege, show an error message rather than redirecting to the login page.
Imagine you're a user who is logged into your application. You click on a user interface element and it looks like you've been logged out. You have no way of knowing that you weren't supposed to use it. Disabling/hiding the element prevents this scenario from occurring for most users. Redirecting to an error gives valuable feedback to the user as to why the action they took did not result in what they expected.
I use a custom attribute derived from AuthorizeAttribute to achieve this effect. If a user isn't logged in, it redirects to the login page. If they are logged in, but not sufficiently privileged, it displays a suitable error view.
This already exist in ASP.NET MVC with the Authorize Attribute:
[Authorize(Roles="Administrators")]
public AcitonResult ManageUsers() {
return View();
}
Or
[Authorize(Users="Admin,SomeUser")]
public AcitonResult ManageUsers() {
return View();
}
More infos:
http://www.asp.net/learn/mvc/tutorial-17-vb.aspx
[Authorize(Roles = "Admin")]
public ActionResult ManageUsersController()
{
...
}
In your web.config check:
...
<forms loginUrl="~/your_login_page" defaultUrl="~/">
...
Also you should setup both MembershipProvider and RoleProvider in your web.config