Skip to content

Instantly share code, notes, and snippets.

@kwisatz
Last active February 10, 2018 13:25
Show Gist options
  • Save kwisatz/cfe8d8ed89764ea899fb to your computer and use it in GitHub Desktop.
Save kwisatz/cfe8d8ed89764ea899fb to your computer and use it in GitHub Desktop.
Silex ApiKeyAuthenticationServiceProvider
<?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)
{
}
}
<?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);
}
}
<?php
/**
* This class is a UserProvider for the Symfony Security component,
* implementing its UserProviderInterface
* @author David Raison <david@tentwentyfour.lu>
*/
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;
}
}
<?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)
{
}
}
@dextervip
Copy link

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!

@saschaklatt
Copy link

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