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)
Related
The requirement is that a logged in user MUST accept privacy statement before accessing other areas of the application. I can write a Middleware or an actionfilter but not sure what's better suited.
Currently the flow will be something like below (Assuming it's a actionfilter).
Authenticate user and load claims from db including whether privacy statement is accepted and redirect to application dashboard.
Below things happen inside the actionfilter
Is user authenticated?
Has the user accepted the privacy statement if any available? (read claims for "PrivacyAccepted" = true)
If no privacy accepted claim available, redirect user to a page showing a message with buttons to accept/reject
If accepted, save it in database, update current user claims with a value like "PrivacyAccepted" = true (using IClaimsTransformation)?
If rejected, show a message and no matter what user does he'll always get the privacy statement page since action filter will redirect here until he accepts it.
From a design/best practice/performance standpoint what is the best thing to do here? Use a middleware or an ActionFilter?
Also point 5 using IClaimsTransformation should be used to update the current claims in logged in user if he accepts the privacy statement. But I haven't found any resources saying whether I can call IClaimsTransformation.TransformAsync() from my code. Everywhere it seems to be working as a middleware rather than I calling it manually.
Maybe you can define a Policy and go for Policy-Based Authorization to achieve your goal.
First when the user accepted the privacy terms add the user a new claim. (like "IsPrivacyAccepted", true)
await _userManager.AddClaimsAsync(user, new List<Claim>
{
new Claim("IsPrivacyAccepted", true),
//other claims
});
Then define a policy with the required claim.
services.AddAuthorization(x =>
{
x.AddPolicy("PrivacyAccepted", policy =>
{
policy.RequireClaim("IsPrivacyAccepted", true); //claim based authorization
});
});
Use the policy wherever you want to restrict users to access your actions.
[Authorize(Policy = "PrivacyAccepted")]
public ActionResult Index() { //... }
If you do like this, you don't need to create an action filter or middleware.
But as I understand you also want to redirect the user to the privacy policy page if he/she is not accepted yet (does not have IsPrivacyAccepted claim). If you want to do this you can write a basic middleware as below
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode == 401)
{
context.Request.Path = "/PrivacyPolicyPage";
await next();
}
});
If you don't want to define the [Authorize(Policy = "PrivacyAccepted")] for each of your controller, maybe you can create a base controller and inherit all of your controller from it.
[Authorize(Policy = "PrivacyAccepted")]
public class MyBaseController : Controller
{
}
and inherit all your controllers from this class instead of Controller class
public class MyController : MyBaseController
{
}
Consider that I have .NET Controller with Policy-based authorization:
public class ImportantController: Controller {
[HttpGet]
[Authorize(Policy = "CanAccessVIPArea")]
public IActionResult ShowInformation() {
...
return OK(VipData);
}
[HttpPost]
[Authorize(Policy = "CanChangeVIPData")]
public IActionResult SaveInformation([FromBody] VipData) {
...
return CreatedAtAction(...);
}
}
Obviously, the real example is much more complex; I apologize if my simplification leads to too much make-believe in it. Also, real application is SPA with Angular front end; but I don't think it makes any difference for the purposes of this question.
When the user calls ShowInformation() I show a lot of data. On that page I have Save button that calls SaveInformation(). Authorization middleware checks for the right policy and it all works fine.
The problem is that by the time the user presses Save, she entered a lot of data, only to find out that she doesn't have the permissions to save. Obviously, leading to bad experience. I want to check for permissions on SaveInformation in the middleware that gets invoked when the user calls ShowInformation. I would prefer not to check for the hardcoded policy because it is on the server and it can change (we have pretty sophisticated permission management system that manipulates permissions at runtime). Invocation of SaveInformation is in the same Angular service as ShowInformation, and it is very easy to check...
I would like to invoke something like /api/SaveInformation?dryrun that will short-circuit the pipeline after authorization middleware with success or failure.
You can inject an IAuthorizationService and ask to evaluate a policy by name:
public class ImportantController: Controller
{
private readonly IAuthorizationService authorization;
public ImportantController(IAuthorizationService authorization)
{
this.authorization = authorization;
}
public async Task<IActionResult> ShowInformation()
{
// ...
var result = await authorizationService.AuthorizeAsync(User, "IsLucky");
return OK(VipData);
}
}
My pratice is to include all permission claims in the id token, when the user first login to the system, the id token will return to the client side. The client side then render the page according to the permission claims.
I am developing a laravel web application of business directory.
here is my scenario.
http://localhost/genie-works/devojp/customer // user searching with keyword of business and
Showing results in http://localhost/genie-works/devojp/customer/search-result?keyword=apple&searchcity=1 this page.
here listing too many business data with post an enquiry feature.
when clicking on the post an enquiry button page goes to http://localhost/genie-works/devojp/customer/post-enquiry/{bisinjessid}/
the post enquiry page checking a middle-ware as authentication.
when user not logged in the middleware redirect to login page http://localhost/genie-works/devojp/customer and showing the login form
after entering login details its needs to redirect to http://localhost/genie-works/devojp/customer/post-enquiry/{bisinjessid}/ this page.
but i tried the function Redirect::back its redirecting to customers page (http://localhost/genie-works/devojp/customer)
How can i solve this issue by redirecting to my last page....
Thanks
Middleware..
public function handle($request, Closure $next)
{
if (!Auth::check()) {
return redirect()->intended(route('cust_index'))->with('activelogin','Succesfully LoggedOut !!!');
}
return $next($request);
}
Controller..
public function custdologin(){
$userdata=array(
'username'=>Input::get('email'), // getting data from form
'password'=>Input::get('password') // getting data from form
);
if(Auth::attempt($userdata)){
switch (Auth::user()->user_type) {
case '2':
return Redirect::to(route('myaccount'));
break;
case '3':
return back();
break;
default:
Auth::logout();
return Redirect::to(route('business_login'))->with('message','Check Your Entries!!');
break;
}
}
else
return Redirect::to(route('business_login'))->with('message','Check Your Entries!!');
}
In your middleware where you are using redirect, use the following:
return redirect()->intended('put a default url'); // i.e: '/dashboard'
This will redirect the user to the intended url (s)he wanted to go without being logged in. Check more here (in Manual Authentication code snippet)
I am experimenting with Grails 3 Interceptors. Given the following interceptor:
class AuthInterceptor {
AuthInterceptor() {
matchAll().includes(controller:"account")
}
// Intercept anything under /account.
boolean before() {
User user = SimpleSecurityUtils.getCurrentUser()
if(user != SimpleSecurityUtils.anonymous) {
// Only get here if the user is currently authenticated. Redirect them to where they want to go.
true
} else {
redirect(controller: auth, action: signin)
true ??
}
}
boolean after() { true }
void afterView() {
// no-op
}
}
matchAll().includes(...) doesn't actually exist on the Matcher object. So how do I actually say "only intercept requests to the AccountController"?
If you follow the auth logic, if the user is currently anonymous (not logged in), I want to redirect them to the AuthController#signin action, which will present them with a login screen. It doesn't appear that the redirect(...) closure is available to interceptors...so how do I perform this redirect safely? Furthermore, how do I "save off" the URL we are currently intercepting so that, after successful login, the user can yet again be redirected to the originally-requested URL?
I say safely above because I've had issues with Grails CannotRedirectExceptions being thrown if too many redirects keep getting tossed around, and those errors are usually assuaged by returning after performing a redirect per this previous answer.
For #1, match( controller: "account" ) should do the trick. Don't know the answer to #2.
I'm writing some REST api for my cake 3.0 application, and I need to set $this->Auth->unauthorizedRedirect to false, as the manual says that this would prevent my application to redirect to login url for unauthorized requests.
http://api.cakephp.org/3.0/class-Cake.Auth.BasicAuthenticate.html
The problem is that I'm trying to set it in my Users controller, and it doesn't work:
class UsersController extends AppController {
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
$this->Auth->allow(['logout']);
// Change the authentication mode when using REST api
if(! $this->RequestHandler->accepts('html')) {
$this->Auth->unauthorizedRedirect = false;
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
}
}
}
This scripts works fine as detecting if a user is actually registered, but fails when I try to use wrong authentication data, showing the login form instead of throwing an error. What am I doing wrong?
Authentication and authorization are two different things
You are mixing up authentication and authorization, that's two different things. Logging in a user is authentication, testing whether a logged in user is allowed to access a specific action is authorization.
So the unauthorized redirect configuration applies to logged in users when accessing actions.
Handling unauthenticated requests
What you are looking for, ie throw an exception on unauthenticated requests, is done by the basic authentication adapter by default, so I assume that you actually aren't using this adapter!?
So if you are using a different adapter, this behavior is best implemented in either your controller where you are trying to identify the user
$user = $this->Auth->identify();
if (!$user) {
throw new ForbiddenException('Stop! Hammer time!');
} else {
$this->Auth->setUser($user);
}
or, in case you want the exception to be thrown for every controller, in a custom authentication adapters unauthorized() method, which is being invoked on unauthenticated requests before executing possible redirects. Quote from the docs:
Cookbook > Authentication > Handling Unauthenticated Requests
When an unauthenticated user tries to access a protected page first the unauthenticated() method of the last authenticator in the chain is called. The authenticate object can handle sending response or redirection by returning a response object, to indicate no further action is necessary. Due to this, the order in which you specify the authentication provider in authenticate config matters.
If authenticator returns null, AuthComponent redirects user to login action. [...]
Here's a simple example that extends the form authentication handler:
src/Auth/MyCustomAuthenticate.php
namespace App\Auth;
use Cake\Auth\FormAuthenticate;
use Cake\Network\Exception\ForbiddenException;
use Cake\Network\Request;
use Cake\Network\Response;
class MyCustomAuthenticate extends FormAuthenticate
{
public function unauthenticated(Request $request, Response $response)
{
if(!$request->accepts('text/html')) {
throw new ForbiddenException('Ah ah ah! You didn\'t say the magic word!');
}
}
}
Controller
$this->loadComponent('Auth', [
'authenticate' => [
'MyCustom'
]
]);
See also
Cookbook > Authentication > Creating Custom Authentication Objects
Cookbook > Authentication > Using Custom Authentication Objects