Authentication, private pages direct urls

I have created a page called ‘Projects’ with subpages that use a single template ‘project’.

All projects have a checkbox field for ‘privacy’, where true means that it is a private project.

Currently, I have all projects children with privacy true collected into a ‘Client’ page that asks for a login if the user is not logged in. However, the projects are still visible via their direct url.

What is the best method for redirecting to a login page if a non-logged in user tries to access the private project page, and redirect to it when logged in? Do I need to create a separate template specifically for private projects? Or can I work with the template I’m currently using for both.

You could redirect to the login page if the user is not logged in and the privacy is true, sth like:

<?php
  if(!$site->user() && $page->privacy()->isTrue()) go('login') ?>
1 Like

Ha I’m a dummy, that works perfectly. Is there a way to get it to redirect to the page the user was attempting to view after they successfully login? Right now in login.php, I have logins redirecting to a single page (‘client’), rather than the redirect being contextually aware…

if($user = $site->user(get('username')) and $user->login(get('password'))) {
  // redirect to the homepage 
  // if the login was successful
  go('client');
} else {
  // make sure the alert is being 
  // displayed in the template
  $error = true;
}

EDIT:

I found this same question asked here:

http://forum.getkirby.com/t/redirecting-user-after-logging-in/492/3

Exactly, you can pass the location over from the redirecting template, sth. like this

<?php if(!$site->user() && $page->privacy()->isTrue()) go('login?location=' . urlencode(kirby()->request()->path())); ?>

In the login form, I put a hidden input field:

<input type="hidden" name="location" value="<?php if(isset($_GET['location'])) {
    echo htmlspecialchars($_GET['location']);} ?>" />

And in the login controller:

<?php
return function($site, $pages, $page) {
   // go to this url if login was successful
   if($_POST['location'] != '') {
    $redirect = $_POST['location']; 
   } else {
      $redirect = $site->url(); 
   }

   // redirect immediately if user is already logged in
   if ($site->user()) go($redirect);

   $form = uniform(
      'login-form',
      array(
         'guard' => '',
         'required' => array(
            'username' => '',
            'password' => '',
         ),
         'actions' => array(
            array(
               '_action'  => 'login',
               'redirect' => $redirect,
            )
         )
      )
   );

   return compact('form');
};

Maybe there is a more elegant way of doing it …

1 Like

@texnixe,

the second code block seems to be cut off, was that intentional? I’m trying to piece together the last bit.

@zerotaste No, that was just silly, missed some characters when copying :flushed:m changed it above

hahah, its cool, the world is full of typos.

I made the changes you posted, but now my login form doesnt seem to be submitting. I’m guessing it has to do with my template having problems with some of the changes to the form in the controller? Code below:

<?php snippet('header') ?>

<h1><?php echo $page->title()->html() ?></h1>

<?php if($error): ?>
<div class="alert"><?php echo $page->alert()->html() ?></div>
<?php endif ?>

<form method="post">

  <input type="hidden" name="location" value="<?php if(isset($_GET['location'])) {
    echo htmlspecialchars($_GET['location']);} ?>" />

  <div>
    <label for="username"><?php echo $page->username()->html() ?></label>
    <input type="text" id="username" name="username">
  </div>
  <div>
    <label for="password"><?php echo $page->password()->html() ?></label>
    <input type="password" id="password" name="password">
  </div>
  <div>      
    <input type="submit" name="login" value="<?php echo $page->button()->html() ?>">
  </div>
</form>

<?php snippet('footer') ?>

I don’t know, can you post your controller as well maybe? My controller won’t work in your context because it is using the uniform plugin.

Yeah, originally I had just replaced my controller with what you posted. I ended up combining the two to get something that works, so, I’m not sure how elegant it is. I’ll post it all together since it does seem to be working

Login Controller

<?php 

return function($site, $pages, $page) {

  // don't show the login screen to already logged in users
  if($site->user()) go('/');

    // go to this url if login was successful
   if($_POST['location'] != '') {
    $redirect = $_POST['location']; 
   } else {
      //go to the client page if login was successful but no location is found
      $redirect = $site->page('client'); 
   }

   // redirect immediately if user is already logged in
   if ($site->user()) go($redirect);

  // handle the form submission
  if(r::is('post') and get('login')) {

    // fetch the user by username and run the 
    // login method with the password
    if($user = $site->user(get('username')) and $user->login(get('password'))) {
      // redirect to the homepage 
      // if the login was successful
      go($redirect);
    } else {
      // make sure the alert is being 
      // displayed in the template
      $error = true;
    }

  } else {
    // nothing has been submitted
    // nothing has gone wrong
    $error = false;  
  }

  return array('error' => $error);

};

Login Template

<?php snippet('header') ?>

<h1><?php echo $page->title()->html() ?></h1>

<?php if($error): ?>
<div class="alert"><?php echo $page->alert()->html() ?></div>
<?php endif ?>

<form method="post">

  <input type="hidden" name="location" value="<?php if(isset($_GET['location'])) {
    echo htmlspecialchars($_GET['location']);} ?>" />

  <div>
    <label for="username"><?php echo $page->username()->html() ?></label>
    <input type="text" id="username" name="username">
  </div>
  <div>
    <label for="password"><?php echo $page->password()->html() ?></label>
    <input type="password" id="password" name="password">
  </div>
  <div>      
    <input type="submit" name="login" value="<?php echo $page->button()->html() ?>">
  </div>
</form>

<?php snippet('footer') ?>

Project Template

<?php if(!$site->user() && $page->privacy()->isTrue()) go('login?location=' . urlencode(kirby()->request()->path())); ?>

Thanks so much for you help @texnixe. I’m pretty new to php, so its hard for me to say if this is a hacky solution or not.

1 Like

Glad you got it to work, one small thing, I think this first bit

  // don't show the login screen to already logged in users
  if($site->user()) go('/');

in your controller is superfluous.

hah, yep. Removed.

I’m going to reference this thread in the other question, since the subject is actually more relevant.

Thanks again!

Two quick notes:

  • Redirecting a private project to a login page can be bad in situations where knowing the URL of a project can be an attack vector. Because of the redirection, an attacker knows for sure the URL was correct. This warning is not relevant for every project and use-case, but should be considered.
  • Storing the “page after login” could also be done using the user session with less form and query string magic involved.
1 Like

How can I get the form with Ajax and not page reload to validate the entries?

For some reason I can’t get this to work automatically. Pieces seem to work depending on which URL I enter, but the redirect code in the controller is breaking for me. My URL correctly appends ?location=portfolio%2Fprojectname to the end, but when I turn on Debug mode, I get Undefined index: location. If I delete:

// go to this url if login was successful
    if($_POST['location'] != '') {
        $redirect = $_POST['location']; 
    } else {
//go to the portfolio page if login was successful but no location is found
    $redirect = $site->page('portfolio'); 
    }

It works, but obviously there is no redirect back to the previous page. Any ideas?

if( isset($_POST['location']) != '') {
    $redirect = $_POST['location']; 
} else {
    $redirect = $site->url('portfolio'); 
}

Adding isset before the $_POST seems to have fixed the Undefined Index error. What’s strange now is that the else redirect is going to the homepage instead of the portfolio link set here. I have a feeling something is still wrong here… :sweat_smile:

Two issues with your code:

  1. The syntax of this line does not make sense: if( isset($_POST['location']) != ''). Instead, it should be:

    if(isset($_POST['location'])  && $_POST['location'] != '') {
     // ...
    }
    
  2. $site->url() does not take any other parameter than the language code in a multi language installation. You want to use $site->page('portfolio').

1 Like

As always, you’ve been more than helpful. :pray:

For anyone in the future, @texnixe’s snippet was missing a bracket. Here is the working code:

if(isset($_POST['location'])  && $_POST['location'] != '') {
// ...
} 

:beers:

I do not get this code to work @texnixe. I get a parse error. Ideas?
<?php go('login?location=' . urlencode(kirby()->request()->path()); ?>

There a parenthesis missing:

<?php go('login?location=' . urlencode(kirby()->request()->path())); ?>
1 Like

Ohh. Thank you @texnixe . :wink: