Skip to content

Instantly share code, notes, and snippets.

@rodmcnew
Created April 22, 2016 20:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rodmcnew/e7dedfc047c04b2f5cdda5c43c856ba5 to your computer and use it in GitHub Desktop.
Save rodmcnew/e7dedfc047c04b2f5cdda5c43c856ba5 to your computer and use it in GitHub Desktop.
HTTP Basic Auth Implementing Zend\Stratigility\MiddlewareInterface
<?php
namespace App\Controller;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Zend\Stratigility\MiddlewareInterface;
/**
* Class HttpBasicAuth
*
* @package App\Controller
*/
class HttpBasicAuth implements MiddlewareInterface
{
protected $users;
protected $ignoreUrls;
/**
* Constructor
*
* @param array $users ['username' => 'password']
* @param array $ignoreUrls ['/ignored/path']
*/
public function __construct(array $users = [], array $ignoreUrls = [])
{
$this->users = $users;
$this->ignoreUrls = $ignoreUrls;
}
/**
* Process an incoming request and/or response.
*
* Accepts a server-side request and a response instance, and does
* something with them.
*
* If the response is not complete and/or further processing would not
* interfere with the work done in the middleware, or if the middleware
* wants to delegate to another process, it can use the `$next` callable
* if present.
*
* If the middleware does not return a value, execution of the current
* request is considered complete, and the response instance provided will
* be considered the response to return.
*
* Alternately, the middleware may return a response instance.
*
* Often, middleware will `return $next();`, with the assumption that a
* later middleware will return a response.
*
* @param Request $request
* @param Response $response
* @param null|callable $next
* @return null|Response
*/
public function __invoke(Request $request, Response $response, callable $next = null)
{
$basicAuthIgnoreUrls = $this->ignoreUrls;
if (!is_array($this->users)
|| count($this->users) < 1
|| !isset($_SERVER['REQUEST_URI'])
|| in_array($_SERVER['REQUEST_URI'], $basicAuthIgnoreUrls)
) {
return $next($request, $response);
}
$realm = 'Restricted area';
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
return $response->withStatus(401)
->withAddedHeader('WWW-Authenticate', 'Digest realm="' . $realm .
'",qop="auth",nonce="' . uniqid() . '",opaque="' . md5($realm) . '"');
}
// analyze the PHP_AUTH_DIGEST variable
if (!($data = $this->httpParseDigest($_SERVER['PHP_AUTH_DIGEST']))
|| !isset($this->users[$data['username']])
) {
return $response->withStatus(401)
->withAddedHeader('WWW-Authenticate', 'Basic realm="My Realm"');
}
// generate the valid response
$A1 = md5(
$data['username'] . ':' . $realm . ':' . $this->users[$data['username']]
);
$A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']);
$valid_response = md5(
$A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce']
. ':' . $data['qop'] . ':' . $A2
);
if ($data['response'] != $valid_response) {
return $response->withStatus(401)
->withAddedHeader('WWW-Authenticate', 'Basic realm="My Realm"');
}
return $next($request, $response);
}
/**
* parse the http auth header
*
* @param $txt
*
* @return array|bool
*/
protected function httpParseDigest($txt)
{
// protect against missing data
$needed_parts = array(
'nonce' => 1,
'nc' => 1,
'cnonce' => 1,
'qop' => 1,
'username' => 1,
'uri' => 1,
'response' => 1
);
$data = array();
$keys = implode('|', array_keys($needed_parts));
preg_match_all(
'@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@',
$txt,
$matches,
PREG_SET_ORDER
);
foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment