I have successfully implemented group based authorization in an MVC application by using the [Authorize(Roles = "Admin")] tags in my controller.
However, the default behaviour when a user requests a page they are not authorized to view is to redirect them to the login page. This far from intuitive, and causes too much confusion amongst users who will repeatedly try to login.
Instead I would like to display a custom screen, or at the very least have a message display on the login screen stating that they are already logged in, but with insufficient privileges.
A User.Identity.IsAuthenticated tag exists, which can be used in the base logic, but there doesn't appear to be a similar IsAuthorised tag.
How can this behaviour be implemented?
I believe you have already partly solved your problem. This because because when authorization fails, the user will be redirected to login page. Verify if the user is authenticated before displaying the login view. If they are authenticated re-direct them to appropriate page instead.The code snippet below will not display login view if the user is authenticated and the cookie has not expired. They will be redirected to "DashboardOrSomeOtherView.cshtml" instead
[HttpGet]
public ActionResult Login(string returnUrl)
{
// Before showing login view check if user is authenticated.
// If they are redirect to suitable page,
// and print appropriate message
if (ControllerContext.HttpContext.User.Identity.IsAuthenticated)
{
// You can even use TempData as additionally, to hold data here and check in
//the view you redirect to if it is not null and is true ,
// then they are authenticated and show some message,
// e.g. You have been redirected here because you do not
// have authorization to view previous page.
TempData["UserAlreadyAuthicated"] = true;
return RedirectToAction("DashboardOrSomeOtherView");
}
// If they are not authenticated , show them login view
return View();
}
In the DashboardOrSomeOtherView
<div>
<h1>DashboardOrSomeOtherView</h1>
#{
if(TempData["UserAlreadyAuthicated"] != null && TempData["UserAlreadyAuthicated"].ToString().ToLower() == "true")
<div>You have been redirected here because of inadequate authorization</div>
}
</div>
Related
I have an application that has two authentications i.e user and admin.
I want to redirect the user to a specific page if they are already login. Like if the user is already logged in then the URL http://example.com/log should take the user to URL http://example.com/dashboard.
Similarly, if an admin is already logged in then, URL http://example.com/admin/login
should redirect them to URL http://example.com/admin/dashboard.
The problem is I can change the handle method to redirect to only one page in RedirectIfAuthenticated page. Any idea on how to check if request is from admin or user in that page.
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect('/dashboard');
}
return $next($request);
}
if you create 2 guards for authentication like user and admin then simply check by below
if (!Auth::guard('user')->user()) {
return redirect('/dashboard');
}
if (!Auth::guard('admin')->user()) {
return redirect('/admin/dashboard');
}
I have an ASP.NET Core web application and I am decorating a few controller action methods with Authorize attribute.
So, when I am not logged in, it doesn't do any redirect and only shows me a blank page for that controller action. I have gone through a couple of tutorials and they talk about Cookie authentication.
So, I made changes in my Startup.cs and added the following:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookie",
LoginPath = new PathString("/Account/Login/"),
AccessDeniedPath = new PathString("/Account/Forbidden/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
I also made a change in Authorize attribute to include ActiveAuthenticationScheme as:
[Authorize(ActiveAuthenticationSchemes = "Cookie")]
Now when I tried to go to that controller action, I get the login page. I am able to login successfully but I am again redirected to Login page instead of showing the controller action method View.
I can tell that I successfully logged in as I can see my email and a 'logoff' button on top of page (Layout with partial view). It seems like I am authenticated but Not Authorized. It that is true that I should have seen the forbidden page but I am seeing only the login page.
Am I missing something here? Why I am being redirected to Login page even after logging in?
.
I have a asp.net mvc web api app with ember and simplemembershipprovider. I am using the ember template and with it, ember app is created upon user successfully logged in in the home controller.
public ActionResult Index(string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return View("App");
}
ViewBag.ReturnUrl = returnUrl;
return View();
}
Sometimes user would click a link in an email with an id when visiting the site, if the url includes an id, upon successful login, I want to redirect user to a detail page base on the provided id in the url. An example would be http://siteURL.com/#/product/1412 . I am having a hard time figuring out how to do this. Since this is a client side ember route, MVC does not differentiate between this route and http://siteURL.com so it just ignores the redirect request. Here is what I have tried.
assign the url in the login controller - nothing happens after json data is returned, stays in the login page and never hit the HomeController even though user is not authenticated.
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
returnUrl = "http://siteURL.com/#/product/1412";
return Json(new { success = true, redirect = returnUrl });
use response redirect. Same as #1
Response.Redirect(returnUrl);
Assigned url in home controller, same as above.
if (User.Identity.IsAuthenticated)
{
returnUrl = "http://siteURL.com/#/product/1412";
return View("App");
}
ViewBag.ReturnUrl = returnUrl;
return View();
Most browsers don't even send the # up to the server, so you won't have it to redirect. Here's a few options
Don't use the hash, not every browser supports it, http://emberjs.com/guides/routing/specifying-the-location-api/
Give them a fake address that redirects, http://siteURL.com/Redirect/product/1412
inject that url into some js on the page that redirects on load
Here is my use cases.
I have a login page which is /public/login.xhtml. All my other pages are required to log-in before reaching them. They are in /pages/ directory.
I want that :
If my user access to http://host/myapp/pages/* it redirects him first to the login page, and then, to the URL he has firstly entered.
If my user access to http://host/myapp/, it redirects him first to the login page, and then, to /pages/home.xhtml.
If my user access to http://host/myapp/public/login.xhtml, it redirects him first to the login page, and then, to /pages/home.xhtml.
If my user access to http://host/myapp/public/login.xhtml and is already logged in, it redirects to /pages/home.xhtml.
What is working currently?
With Seam 3 (v3.1.0.Final) and the Security + Faces module, my use case n°1 is automagically working with :
#ViewConfig
public interface PagesConfig {
static enum Pages {
#ViewPattern("/pages/*")
#LoginView("/public/login.xhtml")
#LoggedIn
LOGGED_IN_PAGES,
}
}
My problem is that I don't understand how Seam's working to do that redirection to the "capture view".
With Seam 2, it was easy to understand, in components.xml we had
<event type="org.jboss.seam.security.notLoggedIn">
<action execute="#{redirect.captureCurrentView}" />
</event>
<event type="org.jboss.seam.security.loginSuccessful">
<action execute="#{redirect.returnToCapturedView}" />
</event>
So we captured the events notLoggedIn and loginSuccessful to handle that with a redirect component.
In Seam 3, I didn't found that configuration : nothing seems to #Observes LoggedInEvent, and there is no Redirect class...
The point n°2 is achieved with that /index.htm file :
<html><head>
<meta http-equiv="Refresh" content="0; URL=pages/home.xhtml">
</head></html>
But for my point n°3, I've tried solutions which don't fully work.
First I tried that in login.xhtml :
<f:metadata>
<s:viewAction action="#{loginAction.redirectToHome}" if="#{identity.loggedIn}" immediate="true" />
</f:metadata>
And with or without onPostback="true", after I login, I'm still in the login page with that error message (twice) : "Unable to find matching navigation case with from-view-id «/public/login.xhtml» for action «#{identity.login}» with outcome «success».". It's only if I now re-access to http://host/myapp/public/login.xhtml that my viewAction redirects me to the home.
I also tried that navigation-rule in faces-config.xml :
<navigation-rule>
<from-view-id>/public/login.xhtml</from-view-id>
<navigation-case>
<if>#{identity.loggedIn}</if>
<to-view-id>/pages/home.xhtml</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
But then, my use case n°1 was disabled : every time I logged-in, I was redirected to the home.
Finally, for my point n°4, the s:viewAction does the job.
So does somebody knows the best practices in order to correctly handle those 4 use cases (which I think are common use cases), especially the point n°3?
Use case No. - 1 SeamFaces stores the originally requested viewId in the user Session, then re-routes to that view after the successful login. It does this by intercepting the navigation from the Seam Security login button, and fires a PostLoginEvent with the data stored in the SessionMap.
Use case No. 2 - nice solution with the redirect! You could also do this with a #UrlMapping in your ViewConfig.
Use case No. 3 - Your viewAction solution should work, but I believe you are coming across SEAMFACES-179. There are a couple of solutions you can use:
1) In your login method, you can manipulate the seesion map stored by the Seam Faces, as demonstrated in this gist -- (this solution courtesy of Cody Lerum)
2) Use PrettyFaces to intercept the request for the login view, and rediret you if you are not logged in.
Finally here is what I did.
<f:metadata>
<s:viewAction action="#{loginAction.redirectToHome}" immediate="true" />
</f:metadata>
So I removed the if="#{identity.loggedIn}" in order to call my redirectToHome method which redirects to
the /pages/home.xhtml.
If the user is already authenticated, then he is redirected to the home page.
If he's not, then it is redirected to the home page, which redirects him to the login page thanks to my #ViewConfig
Here is the loginAction :
public void redirectToHome() throws IOException {
externalContext.redirect(externalContext.encodeActionURL(externalContext.getRequestContextPath()+"/pages/home.xhtml"));
}
The problem I faced then was when I logged out.
Here is my logout action :
<h:commandLink action="/public/login" actionListener="#{securityAction.logout()}" value="Disconnect" immediate="true" />
And the securityAction.logout() method :
public void logout() {
identity.logout();
if (!conversation.isTransient()) {
conversation.end();
}
}
The problem is that I was redirected to the login page (thanks to the #ViewConfig I think), but no PreLoginEvent were thrown, so the Seam LoginListener.observePreLoginEvent wasn't called, and so my previous URL wasn't put in session. So when I logged in (immediatly after logout), I was stuck on the login page, but was logged in.
Thanks to Brian Leathem and he's previous answer, here is what I did : in my authenticate method of my BaseAuthenticator, I called that method after authentication :
private void overrideRedirectToLogin() {
final String PRE_LOGIN_URL = LoginListener.class.getName() + "_PRE_LOGIN_URL";
final ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
final Map<String, Object> sessionMap = externalContext.getSessionMap();
String redirectURL = (String) sessionMap.get(PRE_LOGIN_URL);
if (redirectURL == null) {
final HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
redirectURL = request.getRequestURL().toString();
}
sessionMap.put(PRE_LOGIN_URL, redirectURL.replace("/public/login.xhtml", "/pages/home.xhtml"));
}
With that solution, my previous URL wasn't set in session, but at least, my user is redirected to the home page.
I have a problem creating authentication part for my application.
Below is the simplified version of my controllers.
The idea is that the MY_controller checks if session with user data exists.
If it doesn’t, then redirects to the index page where you have to log in.
MY_controller.php
class MY_Controller extends Controller {
function __construct()
{
parent::__construct();
$this->load->helper('url');
$this->load->library('session');
if($this->session->userdata('user') == FALSE) {
redirect('index');
} else {
redirect('search');
}
}
}
order.php - main controller
class Orders extends MY_Controller {
function __construct()
{
parent::__construct();
$this->load->helper('url');
$this->load->library('session');
}
function index()
{
// Here would be the code that validates information input by user.
// If validation is successful, it creates user session.
$this->load->view('header.html', $data); // load header
$this->load->view('index_view', $data); // load body
$this->load->view('footer.html', $data); // load footer
}
function search()
{
//different page
}
what is happening is that the browser is telling me that “The page isn’t redirecting properly. Firefox has detected that the server is redirecting the request for this address in a way that will never complete.”
It seems like the redirect() is being looped. I looked at a few other examples of user auth and they were build using similar technique.
When a user is already logged in, it appears you want to redirect them to /search/. The redirect occurs, and the constructor is called again, which recognizes that the user is already logged in, so it redirects them to /search/... you get the idea.
I would start by separating your login logic into it's own controller that doesn't extend from MY_Controller.
Also, note that when not logged in your controller redirects to 'index'. If the Index controller is also based on My_Controller, then it will redirect back to itself (until the user logs in and then Dolph Mathews' answer comes true).
You need to provide a 'safe zone' with no checking/redirecting that provides users with a login form (and note that your login controller/method has to have open access too!)
I tend to pop a gateway method into My_Controller, which is called only by private controllers/methods as required (in the constructor of completely private controllers). I'm sure there must be a better way though, perhaps like a gateway function in your My_Controller (as yours is done) but that filters for the URI path (e.g. allows index; index/login; index/logout etc)