Skip to content

Instantly share code, notes, and snippets.

@jonsa
Created September 28, 2018 09:40
Show Gist options
  • Save jonsa/6c4b6c76bdfaa4b2ce341f684475aec2 to your computer and use it in GitHub Desktop.
Save jonsa/6c4b6c76bdfaa4b2ce341f684475aec2 to your computer and use it in GitHub Desktop.
Alternative Zend Expressive Session Authentication Workflow
<?php
// in a config/autoload/*.global.php file:
declare(strict_types=1);
use App\Authentication\LoginAdapter;
use Zend\Expressive\Authentication\AuthenticationInterface;
use Zend\Expressive\Authentication\UserRepositoryInterface;
use Zend\Expressive\Authentication\UserRepository\PdoDatabase;
return [
'dependencies' => [
'aliases' => [
AuthenticationInterface::class => LoginAdapter::class,
UserRepositoryInterface::class => PdoDatabase::class,
],
],
'authentication' => [
'redirect' => '/login',
],
];
<?php
//
// Authentication on all routes
//
// in config/pipeline.php:
declare(strict_types=1);
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\Authentication\AuthenticationMiddleware;
use Zend\Expressive\MiddlewareFactory;
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
// ...
$app->pipe(SessionMiddleware::class);
$app->pipe(AuthenticationMiddleware::class);
// ...
};
// in config/routes.php:
declare(strict_types=1);
use App\Handler\HomePageHandler;
use App\Handler\PingHandler;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\MiddlewareFactory;
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
$app->get('/', HomePageHandler::class, 'home');
$app->get('/api/ping', PingHandler::class, 'ping');
};
//
// Authentication on specific routes
//
// in config/pipeline.php:
declare(strict_types=1);
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\MiddlewareFactory;
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
// ...
$app->pipe(SessionMiddleware::class);
// No AuthenticationMiddleware
// ...
};
// in config/routes.php:
declare(strict_types=1);
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\Authentication\AuthenticationMiddleware;
use Zend\Expressive\MiddlewareFactory;
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
// Tell the router that we accept the login route
$loginPath = $container->get('config')['authentication']['redirect'];
$app->route($loginPath, AuthenticationMiddleware::class, ['GET', 'POST']);
$app->get('/', App\Handler\HomePageHandler::class, 'home');
$app->get('/api/ping', [AuthenticationMiddleware::class, App\Handler\PingHandler::class], 'ping');
};
<!-- in src/templates/app/login.phtml: -->
<?php $this->layout('layout::default', ['title' => 'Login']) ?>
<div class="container">
<div class="row">
<div class="col-sm">
<form action="" method="post">
<?php if (isset($error)): ?>
<div class="alert alert-danger" role="alert">
<?= $this->e($error) ?>
</div>
<?php endif ?>
<div class="form-group">
<label for="username">Username</label>
<input
name="username"
value="<?= $username ?>"
<?= $username ? '' : 'autofocus' ?>
id="username"
type="text"
class="form-control"
placeholder="Enter username"
>
</div>
<div class="form-group">
<label for="password">Password</label>
<input
name="password"
<?= $username ? 'autofocus' : '' ?>
id="password"
type="password"
class="form-control"
placeholder="Password"
>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
<?php
// in src/App/Authentication/LoginAdapter.php:
declare(strict_types=1);
namespace App\Authentication;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Authentication\AuthenticationInterface;
use Zend\Expressive\Authentication\UserInterface;
use Zend\Expressive\Session\SessionMiddleware;
class LoginAdapter implements AuthenticationInterface
{
private const REDIRECT_KEY = LoginAdapter::class . ':redirect';
/**
* @var RequestHandlerInterface
*/
private $handler;
/**
* @var AuthenticationInterface
*/
private $auth;
/**
* @var string
*/
private $path;
/**
* @var callable
*/
private $responseFactory;
/**
* @param RequestHandlerInterface $handler
* @param AuthenticationInterface $auth
* @param string $path
* @param callable $responseFactory
*/
public function __construct(
RequestHandlerInterface $handler,
AuthenticationInterface $auth,
string $path,
callable $responseFactory
) {
$this->handler = $handler;
$this->auth = $auth;
$this->path = $path;
$this->responseFactory = $responseFactory;
}
/**
* Authenticate the PSR-7 request and return a valid user
* or null if not authenticated
* @param ServerRequestInterface $request
* @return null|UserInterface
*/
public function authenticate(ServerRequestInterface $request) : ?UserInterface
{
$user = $this->auth->authenticate($request);
$uri = $request->getUri();
// The user is unauthorized as long as they are on the login route.
return $this->path === $uri->getPath() ? null : $user;
}
/**
* Generate the unauthorized response
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function unauthorizedResponse(ServerRequestInterface $request) : ResponseInterface
{
$session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
$uri = $request->getUri();
// Redirect to login page
if ($this->path !== $uri->getPath()) {
$session->set(self::REDIRECT_KEY, (string)$uri);
return ($this->responseFactory)()
->withHeader('Location', $this->path)
->withStatus(302);
}
// Tell the user to login.
if (!$session->has(UserInterface::class)) {
return $this->handler->handle($request);
}
// Redirect back to the url initially requested.
$redirectTo = $session->get(self::REDIRECT_KEY);
$session->unset(self::REDIRECT_KEY);
return ($this->responseFactory)()
->withHeader('Location', $redirectTo ?? '/')
->withStatus(302);
}
}
<?php
// in src/App/Authentication/LoginAdapterFactory.php:
declare(strict_types=1);
namespace App\Authentication;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Zend\Expressive\Authentication\Session\PhpSession;
class LoginAdapterFactory
{
public function __invoke(ContainerInterface $container) : LoginAdapter
{
$handler = $container->get(LoginHandler::class);
$auth = $container->get(PhpSession::class);
$loginPath = $container->get('config')['authentication']['redirect'];
$responseFactory = $container->get(ResponseInterface::class);
return new LoginAdapter($handler, $auth, $loginPath, $responseFactory);
}
}
<?php
// in src/App/Authentication/LoginHandler.php:
declare(strict_types=1);
namespace App\Authentication;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Expressive\Template\TemplateRendererInterface;
class LoginHandler implements RequestHandlerInterface
{
/**
* @var TemplateRendererInterface
*/
private $renderer;
/**
* @param TemplateRendererInterface $renderer
*/
public function __construct(TemplateRendererInterface $renderer)
{
$this->renderer = $renderer;
}
/**
* {@inheritDoc}
*/
public function handle(ServerRequestInterface $request) : ResponseInterface
{
$post = (array)$request->getParsedBody() ?? [];
$data = [
'username' => $post['username'] ?? '',
'password' => $post['password'] ?? '',
];
if ($request->getMethod() === 'POST') {
$data['error'] = 'Invalid username or password';
}
return new HtmlResponse($this->renderer->render('app::login', $data));
}
}
<?php
// in src/App/Authentication/LoginHandlerFactory.php:
declare(strict_types=1);
namespace App\Authentication;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
class LoginHandlerFactory
{
public function __invoke(ContainerInterface $container) : LoginHandler
{
$template = $container->get(TemplateRendererInterface::class);
return new LoginHandler($template);
}
}
<?php
// in config/autoload/zend-expressive-tooling-factories.global.php:
declare(strict_types=1);
use App\Authentication\LoginAdapter::class;
use App\Authentication\LoginAdapterFactory::class;
use App\Authentication\LoginHandler::class;
use App\Authentication\LoginHandlerFactory::class;
return [
'dependencies' => [
'factories' => [
LoginAdapter::class => LoginAdapterFactory::class,
LoginHandler::class => LoginHandlerFactory::class,
],
],
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment