Skip to content

Instantly share code, notes, and snippets.

@evillemez
Created April 29, 2014 19:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save evillemez/11409525 to your computer and use it in GitHub Desktop.
Save evillemez/11409525 to your computer and use it in GitHub Desktop.
Prototype interfaces for a simpler Authentication framework.
<?php
namespace AC\Authentication;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Authenticates an incoming request by matching it to registered Authenticators.
*/
interface FirewallInterface
{
/**
* Given a request, return an AuthContextInterface, or a Response. The firewall calls
* registered Authenticators that match the request, until one of them handles the request.
* If Authentication failed, or could not be performed, throws an exception.
*
* @param Request $request [description]
* @return AuthContextInterface|Response [description]
* @throws AuthFailedException
*/
public function authenticate(Request $request);
/**
* Register an instance of an authenticator, identifiable by a unique tag. There could
* be more than one instance of the same Authenticator class, but configured differently
* for different parts of a site.
*
* @param string $name [description]
* @param AuthenticatorInterface $handler [description]
*/
public function registerAuthenticator($name, AuthenticatorInterface $handler);
/**
* Match a request to a specific Authenticator, with extra optional parameters.
*
* @param RequestMatcherInterface $matcher A matcher for incoming Requests
* @param string $name The name of the Authenticator that should handle the matched Request
* @param array|null $options Any optional parameters that apply only to this matched Request
*/
public function matchRequest(RequestMatcherInterface $matcher, $name, array $options = []);
/**
* Get the dispatcher instance used by the Firewall. The firewall emits events that can
* be subscribed to during the authentication flow.
*
* @return EventDispatcherInterface
*/
public function getDispatcher();
}
/**
* Describes events emitted by the Firewall during the authentication process.
*/
final class FirewallEvents
{
/**
* Fires before Authenticators are called to authenticate the request.
*/
const REQUEST = 'firewall.request';
/**
* Fired when an AuthContext is either newly created, or successfully refreshed.
*/
const AUTHENTICATED = 'firewall.authenticated';
/**
* Fired when a new AuthContext has been created.
*/
const NEW_AUTH = 'firwall.newly_authenticated';
/**
* Fires when an AuthContext has been refreshed.
*/
const REFRESHED_AUTH = 'firwall.authentication_refreshed';
/**
* Fires when the Firewall returns a Response.
*/
const RESPONSE = 'firewall.response';
/**
* Fires when the Firewall catches an AuthException thrown by an Authenticator.
*/
const EXCEPTION = 'firewall.exception';
}
/**
* Implements a specific type of authentication logic for the Firewall. Authenticators
* create or refresh AuthContexts.
*/
interface AuthenticatorInterface
{
/**
* Allows the firewall to easily check for mismatches.
*
* @param AuthContextInterface $context [description]
* @return [type] [description]
*/
public function supportsAuthContext(AuthContextInterface $context);
/**
* Given a request, the handler should create a new AuthContext, or return
* a response.
*
* @param Request $req The request to authenticate.
* @param array $ops Optional array of parameters.
* @return AuthContextInterface|Response
* @throws AbstractAuthException
*/
public function createAuthContext(Request $req, $ops = []);
/**
* The the request contains a session, and there already is an AuthContext
* stored in the session, the handler must refresh it to ensure that it is
* still valid.
*
* @param Request $req [description]
* @param AuthContextInterface $context [description]
* @param array $ops [description]
* @return AuthContextInterface|Response [description]
* @throws AbstractAuthException
*/
public function refreshAuthContext(Request $req, AuthContextInterface $context, array $ops = []);
}
/**
* Base interface for AuthContext's. An AuthContext describes the manner
* in which authentication occured.
*
* Specific handlers would provide their own extensions of the AuthenticationContext that
* are relevant to them. For example, an ApiKeyAuthHandler would likely have extra
* methods for retrieving the specific ApiKey used.
*
* Applications could extend the AuthContexts as far as having their User representations
* stored in them as well - or by registering listeners to derive the User during Auth events.
*/
interface AuthContextInterface
{
/**
* Some forms of Authentication are stateless, for example API keys. If stateless, the
* Firewall will not store AuthContext in the Session.
*
* @return boolean
*/
public function isStateless();
/**
* In Authentication cases that involve multiple requests, an AuthContext could be
* created, even though Authentication may not be complete. The firewall will not
* fire any authentication events unless this method returns true.
*
* @return boolean [description]
*/
public function isAuthenticated();
/**
* If the AuthContext is not stateless and will be stored in the session, the Firewall will
* call this method to ensure that sensitive data is not stored. For example, if the AuthContext
* contains user passwords, these should be erased for security reasons.
*/
public function eraseSensitiveData();
public function getSession();
public function setSession(SessionInterface $session = null);
public function getRequest();
public function setRequest(Request $request);
}
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RequestMatcher;
use AC\Authentication\Firewall;
use AC\Authentication\FirewallEvents;
use AC\Authentication\ApiKey\ApiKeyAuthenticator;
use AC\Authentication\HttpBasic\HttpBasicAuthenticator;
$firewall = new Firewall();
$firewall->registerAuthenticator('api_key', new ApiKeyAuthenticator(/*... deps ...*/));
$firewall->matchRequest(new RequestMatcher('^/api/v1/resources'),'api_key');
$firewall->matchRequest(new RequestMatcher('^/api/v1/reslations'), 'api_key');
$firewall->registerAuthenticator('http_basic', new HttpBasicAuthenticator(/*...deps...*/));
$firewall->matchRequest(new RequestMatcher('^/clients'),'http_basic');
//one way of getting the current "user"
$firewall->getDispatcher()->addListener(FirewallEvents::AUTHENTICATED, function(FirwallEvent $e) {
$context = $e->getAuthContext();
//derive user from $context (if it's not already stored there)
//set user service in whatever framework being used
});
//handle auth errors
$firewall->getDispatcher()->addListener(FirewallEvents::EXCEPTION, function(FirewallEvent $e) {
$e->setResponse(new Response(401, 'Authentication Required.'));
});
//authenticate the request
$result = $firewall->authenticate(Request::createFromGlobals());
if ($result instanceof Response) {
$result->send();
exit();
}
//otherwise, we have an AuthContext, do your app stuff
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment