Last active
February 10, 2018 13:25
-
-
Save kwisatz/cfe8d8ed89764ea899fb to your computer and use it in GitHub Desktop.
Silex ApiKeyAuthenticationServiceProvider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* ApiKeyAuthenticator for the Symfony Security Component | |
*/ | |
namespace Ttf\Security\Provider; | |
use Silex\Application, | |
Silex\ServiceProviderInterface; | |
use Symfony\Component\Security\Core\Exception\AuthenticationException, | |
Symfony\Component\Security\Core\Authentication\Provider\SimpleAuthenticationProvider, | |
Symfony\Component\Security\Http\Firewall\SimplePreAuthenticationListener; | |
use Ttf\Mapping\User, | |
Ttf\Security\ApiKeyAuthenticator, | |
Ttf\Security\Provider\ApiKeyUserProvider; | |
class ApiKeyAuthenticationServiceProvider implements ServiceProviderInterface | |
{ | |
public function register(Application $app) | |
{ | |
$app['security.apikey.authenticator'] = $app->protect(function () use ($app) { | |
return new ApiKeyAuthenticator( | |
$app['security.user_provider.apikey'](), | |
$app['security.apikey.param'], | |
$app['logger'] | |
); | |
}); | |
$app['security.authentication_listener.factory.apikey'] = $app->protect(function ($name, $options) use ($app) { | |
$app['security.authentication_provider.'.$name.'.apikey'] = $app->share(function () use ($app, $name) { | |
return new SimpleAuthenticationProvider( | |
$app['security.apikey.authenticator'](), | |
$app['security.user_provider.apikey'](), | |
$name | |
); | |
}); | |
$app['security.authentication_listener.' . $name . '.apikey'] = $app->share(function () use ($app, $name, $options) { | |
return new SimplePreAuthenticationListener( | |
$app['security'], | |
$app['security.authentication_manager'], | |
$name, | |
$app['security.apikey.authenticator'](), | |
$app['logger'] | |
); | |
}); | |
return array( | |
'security.authentication_provider.'.$name.'.apikey', | |
'security.authentication_listener.'.$name.'.apikey', | |
null, // entrypoint | |
'pre_auth' // position of the listener in the stack | |
); | |
}); | |
return true; | |
} | |
public function boot(Application $app) | |
{ | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* This class is API key authenticator for the Symfony Security component, | |
* implementing its SimplePreAuthenticatorInterface | |
* | |
* @see http://symfony.com/doc/current/cookbook/security/api_key_authentication.html | |
*/ | |
namespace Ttf\Security; | |
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface; | |
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
use Symfony\Component\Security\Core\Exception\AuthenticationException; | |
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; | |
use Symfony\Component\Security\Core\User\UserProviderInterface; | |
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; | |
use Symfony\Component\Security\Core\Exception\BadCredentialsException; | |
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; | |
use Symfony\Component\HttpFoundation\Request, | |
Symfony\Component\HttpFoundation\Response; | |
use Psr\Log\LoggerInterface; | |
use Ttf\Security\Provider\ApiKeyUserProvider; | |
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface | |
{ | |
protected $userProvider; | |
protected $paramName; | |
public function __construct(ApiKeyUserProvider $userProvider, $paramName, LoggerInterface $logger) | |
{ | |
$this->paramName = $paramName; | |
$this->userProvider = $userProvider; | |
} | |
public function createToken(Request $request, $providerKey) | |
{ | |
if (!$request->query->has($this->paramName)) { | |
throw new BadCredentialsException('No API key found'); | |
} | |
return new PreAuthenticatedToken( | |
'anon.', | |
$request->query->get($this->paramName), | |
$providerKey | |
); | |
} | |
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey) | |
{ | |
$apiKey = $token->getCredentials(); | |
$username = $this->userProvider->getUsernameForApiKey($apiKey); | |
if (!$username) { | |
throw new AuthenticationException( | |
sprintf('API Key "%s" does not exist', $apiKey) | |
); | |
} | |
$user = $this->userProvider->loadUserByUsername($username); | |
return new PreAuthenticatedToken( | |
$user, | |
$apiKey, | |
$providerKey, | |
$user->getRoles() | |
); | |
} | |
public function supportsToken(TokenInterface $token, $providerKey) | |
{ | |
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey; | |
} | |
public function onAuthenticationFailure(Request $request, AuthenticationException $Exception) | |
{ | |
return new Response("Authentication Failed.", 403); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* This class is a UserProvider for the Symfony Security component, | |
* implementing its UserProviderInterface | |
* @author David Raison <[email protected]> | |
*/ | |
namespace Ttf\Security\Provider; | |
class ApiKeyUserProvider extends DatabaseUserProvider { | |
/** | |
* Implements getUsernameForApiKey used in the ApiKeyAuthenticator | |
* | |
* The ApiKeyAuthenticator will throw an exception if the returned value is falsy, | |
* so we don't throw any Exception here. | |
*/ | |
public function getUsernameForApiKey($apiKey) | |
{ | |
$user = $this->repository->findOneByApikey($apiKey); | |
if ($user) { | |
return $user->getUsername(); | |
} | |
return false; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* ApiKeyUserServiceProvider | |
*/ | |
namespace Ttf\Security\Provider; | |
use Silex\Application, | |
Silex\ServiceProviderInterface; | |
use Ttf\Mapping\User, | |
Ttf\Security\Provider\ApiKeyUserProvider; | |
class ApiKeyUserServiceProvider implements ServiceProviderInterface | |
{ | |
public function register(Application $app) | |
{ | |
$app['user.repository'] = $app->share(function() use ($app) { | |
return $app['orm.em']->getRepository('Ttf\Mapping\User'); | |
}); | |
$app['security.user_provider.apikey'] = $app->protect(function () use ($app) { | |
return new ApiKeyUserProvider($app['user.repository']); | |
}); | |
return true; | |
} | |
public function boot(Application $app) | |
{ | |
} | |
} |
Hi, my configuration looks like this:
$app->register(new ApiKeyUserServiceProvider());
$app->register(new ApiKeyAuthenticationProvider(), array(
'security.apikey.param' => 'access_token',
));
$app->register(new SecurityServiceProvider(), array(
'security.firewalls' => array(
'api' => array(
'apikey' => true,
'pattern' => '^/api',
'stateless' => true,
),
),
'security.access_rules' => array(
array(new RequestMatcher('^/api/subscribers' , null, 'GET' ), array('ROLE_ADMIN')),
array(new RequestMatcher('^/api/subscribers' , null, 'POST' ), array('ROLE_ADMIN', 'ROLE_USER')),
array(new RequestMatcher('^/api/subscribers/\d+', null, 'GET' ), array('ROLE_ADMIN')),
array(new RequestMatcher('^/api/subscribers/\d+', null, 'PATCH' ), array('ROLE_ADMIN')),
array(new RequestMatcher('^/api/subscribers/\d+', null, 'DELETE'), array('ROLE_ADMIN')),
),
));
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, I just got into this security issue. I am happy that someone made a workaround, great job! Could you please update with an security.firewalls config example? Regards!