Logged in Pages vs Non-Logged in Pages

Hi, I’m fairly new to SLIM and enjoying it but having trouble finding current examples using V3.*

How do i handle a user’s logged in state and route accordingly?

Should I be using $_SESSION[] to store a hash and then load user attributes and then route accordingly?

How would I allow non-logged-in users to visit route: mysite.com/about, mysite.com/login, but prevent non-logged-in access to mysite.com/admin ?

I would really appreciate if someone could provide some examples to get me started - thanks!

Typically you would do this with Middleware. Any routes requiring an authenticated user would have some sort of an authentication middleware. Such a route might look like this.

$app->get('/admin', function ($request, $response) {
    return $response->getBody()->write('Logged In');
})->add('App\Middleware\Authentication');

The Authentication Middleware might then look something like this:

<?php

namespace App\Middleware;

class Authentication
{
    /**
     * Authentication middleware invokable class
     *
     * @param  \Psr\Http\Message\ServerRequestInterface $request  PSR7 request
     * @param  \Psr\Http\Message\ResponseInterface      $response PSR7 response
     * @param  callable                                 $next     Next middleware
     *
     * @return \Psr\Http\Message\ResponseInterface
    **/
    public function __invoke(Request $request, Response $response, $next)
    {
        if (!$user->loggedIn()) {
            return $response->withRedirect($this->router->pathFor('login'), 403);
        }
        $response = $next($request, $response);
        return $response;
    }
}

okay cool thanks. I’ll be honest - I have no idea what __invoke() is there for - I’m guessing it’s called automatically via $app somehow. I won’t get to try this out until tomorrow but just thinking ahead with curiousity:

$app->get('/admin', function ($request, $response) {
    return $response->getBody()->write('Logged In');
})->add('App\Middleware\Authentication');

If I have many pages that are “gated” for logged in users only is there a more concise way to write this?

__invoke is one of PHP’s Magic methods. See http://lornajane.net/posts/2012/phps-magic-__invoke-method-and-the-callable-typehint for more information. Basically it runs that method automatically.

As to gating multiple routes, put them into a Route Group and then you only need to add the Middleware to the group.

I’m not sure how the works in the above example:

return $response->withRedirect($this->router->pathFor('login'), 403);

$this is referring to the class and has no knowledge of $app instantiated in index.php (my controller) How can I work with $app inside the class? And on another note how can I pass php variables to the page so that I can inform the user of things such as “bad password” etc?

In my original setup that probably didn’t follow SLIM 3 best practices I was using something to the tune of:

return $this->view->render($response, "login.html", ['msg'=>'Wrong password and/or user name.']);

How do I access $app from inside the class and what’s the best way to pass vars and show the page?

You can assign the controller class method in the routes rather than creating a function like this:

$c = $app->getContainer();

$c['db'] = function() {
    return new DB;
};

$c['view'] = function() {
    return new Slim\Views\Twig('/templates');
};

$c['Controller'] = function($c) {
    $db = $c->get('db');
    $view = $c->get('view');
    return new LoginController($db,$view);
};

// login
$app->get('/login','Controller:showLogin');
$app->post('/login','Controller:submitLogin');

// admin area
$user = $_SESSION['USER'];
$authentication = new Authentication($user);

$app->get('/admin','AdminController:home')->add($authentication);

Login Controller

class LoginController {

    public function __construct($db,$view) {
	$this->db = $db;
	$this->view = $view;
    }

    public function showLogin($request,$response) {
	$data = array(
	    'loginError' => $request->getAttribute('loginError')
	);
	return $this->view->render($response,'login.html',$data);
    }

    public function submitLogin($request,$response) {
	$postData = $request->getParsedBody();
	$user = $this->db->login($postData['username'],$postData['password']);

        // invalid login
	if (empty($user)) {
	    // add the message to the request for the showLogin function to assign
	    $_SESSION['USER'] = null;
            $request = $request->withAttribute('loginError','Invalid Login');

	    // return the login view
	    return $this->showLogin($request,$response);
	}

	// valid login
	$_SESSION['USER'] = $user;
	return $response->withHeader('Location','/admin');
    }
}

Auth Middleware

class Authentication {

    function __construct($user) {
	$this->user = $user;
    }

    function __invoke($request,$response,$next) {
	if (empty($this->user)) {
	    return $response->withStatus(403)->withHeader('Location','/login');
	}

	return $next($request,$response);
    }
}
1 Like

@robrothedev, cool thanks for the sample code. After looking how you structured things I was able to restructure my app and get things going!

I’m still wrapping my head around this framework but I think i was using the “invoking” method where you are using “container resolution” - if I read the docs correctly? (I’d like to see the same solution - that is calling $app from inside a class - with the invoking way just for comparison)

I’m curious about this line:

$request->getAttribute('loginError')

This SLIM docs are weak in this department… there seems to be no formal docs to just look up something.

@bionary Glad to help. Yeah, the framework along with PHP magic methods take a little bit to understand how it works under the hood but I’ve been working with SLIM for several months and really enjoy it.

As far as the $request->getAttribute goes, it coincides with $request->withAttribute and can be explained here: https://www.slimframework.com/docs/concepts/value-objects.html

Example:

$request->withAttribute('name', 'Rob');
echo 'Welcome, ' . $request->getAttribute('name');

Basically it sets a key/value pair to the request object.

This:

class Controller {

    function viewLogin($request,$response) {
        // loginError doesn't exit until the form is posted with an invalid login
        $data['loginError'] = $request->getAttribute('loginError');

        return $this->view->render($response,'login.html',$data);
    }

    function submitLogin($request,$response) {
        if ($invalidLogin) {
	    // set a loginError value to the request
	    $request = $request->withAttribute('loginError','That login is invalid.');
	    return $this->viewLogin($request,$response);
	}
    }
}

is an alternative to this:

class Controller {
	
    function login($request,$response) {
        $data = array();
        if ($request->isPost()) {
	    if ($invlidLogin) {
	        $data['loginError'] = 'That login is invalid.';
            }
	}

	return $this->view->render($response,'login.html',$data);
    }
}

Rather than using one controller method to handle the login, I broke mine up into two methods as they’re handled differently based on the request type. I use the withAttribute to assign the loginError to the request object for my viewLogin method to consume. Does that make sense?

Yes it does, thanks for explaining. It reminds me of flashvars from when I used to do some AS3 programming.

Unfortunately I’m still not convinced that that is easier to read/maintain - it seems like yet, one more layer of abstraction that will leave me scratching my head 2 years from now when I’m working in the code.

Can I easily dump all the ->getAttribute() key/values to see what the heck is being stored? With your second example that is super easy… fb($data) or print_r($data) is all I need and I can see everything instantly.

I’m a fan of thin methods, methods based on request and comments so the first option is my preference. I add comments regarding where response attributes are coming from.

Not sure about dumping the getAttribute. But yes, the second example is much less implicit.

Hey there,

how do I access ->router inside the class?
It doesn’t seem to be part of the container.

return $response->withRedirect($this->router->pathFor('login'), 403);

And is there anyway to pass data like one could using the ->render() method?

$this->router will need to be defined in the class. Typically you would pass the router object to the constructor of the class, then set it as a property. For example in my Authentication class above it would have a constructor like this.

use Slim\Router;

// ... namespace, class, etc

protected $router;

public function __construct(Router $router)
{
    $this->router = $router;
}

//__ invoke function

Then you just need to pass the Router to this function from your container.

As for passing data, since you are doing a redirect you would want to put any additional data into a session (perhaps using a flash messages package) and then you can construct that login view and give it whatever data from the flash/session.

The reason I’m doing a redirect is only because I tried using ->render() and it simply cobbled everything together. For instance I was looking to:

check if user session was valid
and if no render the login page

It rendered the page (which should have been unavailable - due to not being signed in) and it added the login page afterwards. So I’m not sure if ->render() is the right way to do it.

I will say though that I’m calling the Authorize class via ->add(‘Auth’)

I’m not sure what is best practice here?

(on another note, I just read the docs on flash messages, wow that looks really useful - thanks!)

$this->router->pathFor('login') is looking for a route with the name (not uri) of login. The route would therefore look something like this

$app->get('/login', ... )->setName('login');

So for this particular request, a response is returned to the browser that tells it to request the login page instead. At that point a new request is made and your app starts again, this time going through your login route. That route will have its own closure or controller and will render its own responses.

So back to the first entry when the user isn’t logged in, nothing really gets rendered. It is only in the next request that you will render output (the login page) back to the user.

so is this the proper way to do it? using router() rather than ->render()
?

Yes, if you want to redirect, you would typically redirect to a different url, and you can get that url from the router since it knows about your routes. You could also do something like this…

return $response->withRedirect('http://example.com/login', 403);

… but by using the router if you change your login route then the link here will change too. Using $this->router->pathFor() just gives you the convenience of being able to lookup the URL you need to redirect to from the routes you’ve created.

seems all very structured once you get the hang of it :slight_smile:

Yes, there is quite a bit to learn and it all seems very abstract at first. But once you get the hang of it things start making sense. Then suddenly you start having those eureka moments when you realize how much the framework is doing for you and how easy things are to change.

I’m starting to see this, but I’ve still got miles to go! I wish they had a formal documentation… you know like php.net or codeignighter has.

I’ve had an impossibly hard time finding info about v3 on my own. If it wasn’t for this forum I would have given up. so thanks again!

1 Like

When I first looked at Slim I felt the same way. Everything seemed too abstract. Too little documentation. And frankly, I gave up. The mistake I made was thinking that since it was “Slim” it would be easier to learn. But the opposite turned out to be true. If Slim were a product it would be a “custom build” whereas something like Laravel or Code Igniter is “off the shelf”.

I learned Laravel instead. (And a bit of Cake PHP too.) There is way more documentation and tutorials available for Laravel. I spent several months building up a new project in Laravel.

Once I felt like I knew my way around a bit I came back and had another look at Slim. Suddenly things made sense!

And that’s just the thing about Slim… It is… well… Slim. You’re responsible for much of the configuration, customization, and pulling in additional packages as you need them. You’re essentially building up your own custom framework.

So in a backwards way, Laravel (and especially Laracasts) ultimately taught me Slim. Now I can look at the menu of packages for things like view templates, validation, ORMs, etc and pick the one most to my liking rather than what comes boxed with Laravel.

So stick with it… and it doesn’t hurt to spin up a few other frameworks as well. Once you start to put together what they have in common everything starts to make more sense.

2 Likes