Skip to content

Instantly share code, notes, and snippets.

@tabbi89
Forked from kbond/AppKernel.php
Created October 1, 2015 18:13
Show Gist options
  • Select an option

  • Save tabbi89/a7fa08b238cd02e3f2d3 to your computer and use it in GitHub Desktop.

Select an option

Save tabbi89/a7fa08b238cd02e3f2d3 to your computer and use it in GitHub Desktop.

Revisions

  1. @kbond kbond revised this gist Oct 1, 2015. No changes.
  2. @kbond kbond revised this gist Oct 1, 2015. No changes.
  3. @kbond kbond revised this gist Oct 1, 2015. 2 changed files with 8 additions and 7 deletions.
    5 changes: 2 additions & 3 deletions GuardAuthenticator.php
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,8 @@
    abstract class GuardAuthenticator extends AbstractGuardAuthenticator
    {
    /**
    * {@inheritdoc}
    * NOTE: I chose to throw an HTTP Exception here to let the response be rendered elsewhere -
    * separation of concerns and all... You could always return a JsonResponse here.
    */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
    @@ -26,8 +27,6 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
    $message = $exception->getMessageKey();
    }

    // NOTE: I chose to throw an HTTP Exception here to let the response be rendered elsewhere -
    // separation of concerns and all... You could always return a JsonResponse here.
    throw new HttpException(401, $message);
    }

    10 changes: 6 additions & 4 deletions UserController.php
    Original file line number Diff line number Diff line change
    @@ -8,12 +8,14 @@

    class UserController extends Controller
    {
    /**
    * NOTE: I don't return a response in the UsernamePasswordGuardAuthenticator
    * because I wanted a controller to do the rendering. You could always
    * return a JsonResponse in UsernamePasswordGuardAuthenticator::onAuthenticationSuccess().
    * If you do that, this class/method is no longer required.
    */
    public function loginAction()
    {
    // NOTE: I don't return a response in the UsernamePasswordGuardAuthenticator
    // because I wanted a controller to do the rendering. You could always
    // return a JsonResponse in UsernamePasswordGuardAuthenticator::onAuthenticationSuccess()

    $token = $this->get('jwt_coder')->encode([
    'username' => $this->getUser()->getUsername()
    ]);
  4. @kbond kbond revised this gist Oct 1, 2015. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions UserController.php
    Original file line number Diff line number Diff line change
    @@ -10,9 +10,9 @@ class UserController extends Controller
    {
    public function loginAction()
    {
    // NOTE: I don't return a response in the UsernamePasswordGuardAuthenticator because I wanted a controller
    // to do the rendering. You could always return a JsonResponse in
    // UsernamePasswordGuardAuthenticator::onAuthenticationSuccess()
    // NOTE: I don't return a response in the UsernamePasswordGuardAuthenticator
    // because I wanted a controller to do the rendering. You could always
    // return a JsonResponse in UsernamePasswordGuardAuthenticator::onAuthenticationSuccess()

    $token = $this->get('jwt_coder')->encode([
    'username' => $this->getUser()->getUsername()
  5. @kbond kbond revised this gist Oct 1, 2015. 2 changed files with 6 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions GuardAuthenticator.php
    Original file line number Diff line number Diff line change
    @@ -26,6 +26,8 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio
    $message = $exception->getMessageKey();
    }

    // NOTE: I chose to throw an HTTP Exception here to let the response be rendered elsewhere -
    // separation of concerns and all... You could always return a JsonResponse here.
    throw new HttpException(401, $message);
    }

    4 changes: 4 additions & 0 deletions UserController.php
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,10 @@ class UserController extends Controller
    {
    public function loginAction()
    {
    // NOTE: I don't return a response in the UsernamePasswordGuardAuthenticator because I wanted a controller
    // to do the rendering. You could always return a JsonResponse in
    // UsernamePasswordGuardAuthenticator::onAuthenticationSuccess()

    $token = $this->get('jwt_coder')->encode([
    'username' => $this->getUser()->getUsername()
    ]);
  6. @kbond kbond created this gist Oct 1, 2015.
    18 changes: 18 additions & 0 deletions AppKernel.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    <?php
    // app/AppKernel.php

    use Symfony\Component\Config\Loader\LoaderInterface;
    use Symfony\Component\HttpKernel\Kernel;

    class AppKernel extends Kernel
    {
    public function registerBundles()
    {
    return = [
    new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
    new Symfony\Bundle\SecurityBundle\SecurityBundle(),
    new KnpU\GuardBundle\KnpUGuardBundle(),
    new AppBundle\AppBundle(),
    ];
    }
    }
    55 changes: 55 additions & 0 deletions GuardAuthenticator.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    <?php
    // src/AppBundle/Security/GuardAuthenticator.php

    namespace AppBundle\Security;

    use KnpU\Guard\AbstractGuardAuthenticator;
    use KnpU\Guard\Exception\CustomAuthenticationException;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpKernel\Exception\HttpException;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    use Symfony\Component\Security\Core\Exception\AuthenticationException;

    /**
    * @author Kevin Bond <kevinbond@gmail.com>
    */
    abstract class GuardAuthenticator extends AbstractGuardAuthenticator
    {
    /**
    * {@inheritdoc}
    */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
    $message = 'Invalid Credentials';

    if ($exception instanceof CustomAuthenticationException) {
    $message = $exception->getMessageKey();
    }

    throw new HttpException(401, $message);
    }

    /**
    * {@inheritdoc}
    */
    public function start(Request $request, AuthenticationException $authException = null)
    {
    // noop
    }

    /**
    * {@inheritdoc}
    */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
    // noop
    }

    /**
    * {@inheritdoc}
    */
    public function supportsRememberMe()
    {
    return false;
    }
    }
    8 changes: 8 additions & 0 deletions InvalidJWTException.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    <?php
    // src/AppBundle/Exception/InvalidJWTException.php

    namespace AppBundle\Exception;

    class InvalidJWTException extends \UnexpectedValueException
    {
    }
    80 changes: 80 additions & 0 deletions JWTCoder.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,80 @@
    <?php
    // src/AppBundle/Service/JWTCoder.php

    namespace AppBundle\Service;

    use Namshi\JOSE\JWS;
    use AppBundle\Exception\InvalidJWTException;

    /**
    * @author Kevin Bond <kevinbond@gmail.com>
    */
    class JWTCoder
    {
    const ALG = 'HS256';

    private $key;

    public function __construct($key)
    {
    $this->key = $key;
    }

    /**
    * @param array $payload
    * @param int $ttl
    *
    * @return string
    */
    public function encode(array $payload, $ttl = 86400)
    {
    $payload['iat'] = time();
    $payload['exp'] = time() + $ttl;

    $jws = new JWS([
    'typ' => 'JWS',
    'alg' => self::ALG,
    ]);

    $jws->setPayload($payload);
    $jws->sign($this->key);

    return $jws->getTokenString();
    }

    /**
    * @param string $token
    *
    * @return array
    *
    * @throws InvalidJWTException
    */
    public function decode($token)
    {
    $jws = JWS::load($token);

    if (!$jws->verify($this->key, self::ALG)) {
    throw new InvalidJWTException('Invalid JWT');
    }

    if ($this->isExpired($payload = $jws->getPayload())) {
    throw new InvalidJWTException('Expired JWT');
    }

    return $payload;
    }

    /**
    * @param array $payload
    *
    * @return bool
    */
    private function isExpired($payload)
    {
    if (isset($payload['exp']) && is_numeric($payload['exp'])) {
    return (time() - $payload['exp']) > 0;
    }

    return false;
    }
    }
    70 changes: 70 additions & 0 deletions JWTGuardAuthenticator.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,70 @@
    <?php
    // src/AppBundle/Security/JWTGuardAuthenticator.php

    namespace AppBundle\Security;

    use KnpU\Guard\Exception\CustomAuthenticationException;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
    use AppBundle\Exception\InvalidJWTException;
    use AppBundle\Service\JWTCoder;

    /**
    * @author Kevin Bond <kevinbond@gmail.com>
    */
    final class JWTGuardAuthenticator extends GuardAuthenticator
    {
    private $jwtCoder;

    public function __construct(JWTCoder $jwtCoder)
    {
    $this->jwtCoder = $jwtCoder;
    }

    /**
    * {@inheritdoc}
    */
    public function getCredentials(Request $request)
    {
    if (!$request->headers->has('Authorization')) {
    throw CustomAuthenticationException::createWithSafeMessage('Missing Authorization Header');
    }

    $headerParts = explode(' ', $request->headers->get('Authorization'));

    if (!(count($headerParts) === 2 && $headerParts[0] === 'Bearer')) {
    throw CustomAuthenticationException::createWithSafeMessage('Malformed Authorization Header');
    }

    return $headerParts[1];
    }

    /**
    * {@inheritdoc}
    */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
    try {
    $payload = $this->jwtCoder->decode($credentials);
    } catch (InvalidJWTException $e) {
    throw CustomAuthenticationException::createWithSafeMessage($e->getMessage());
    } catch (\Exception $e) {
    throw CustomAuthenticationException::createWithSafeMessage('Malformed JWT');
    }

    if (!isset($payload['username'])) {
    throw CustomAuthenticationException::createWithSafeMessage('Invalid JWT');
    }

    return $userProvider->loadUserByUsername($payload['username']);
    }

    /**
    * {@inheritdoc}
    */
    public function checkCredentials($credentials, UserInterface $user)
    {
    // noop
    }
    }
    19 changes: 19 additions & 0 deletions UserController.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    <?php
    // src/AppBundle/Controller/UserController.php

    namespace AppBundle\Controller;

    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\HttpFoundation\JsonResponse;

    class UserController extends Controller
    {
    public function loginAction()
    {
    $token = $this->get('jwt_coder')->encode([
    'username' => $this->getUser()->getUsername()
    ]);

    return new JsonResponse(['token' => $token]);
    }
    }
    59 changes: 59 additions & 0 deletions UsernamePasswordGuardAuthenticator.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,59 @@
    <?php
    // src/AppBundle/Security/UsernamePasswordGuardAuthenticator.php

    namespace AppBundle\Security;

    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
    use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
    use Symfony\Component\Security\Core\Exception\BadCredentialsException;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;

    /**
    * @author Kevin Bond <kevinbond@gmail.com>
    */
    final class UsernamePasswordGuardAuthenticator extends GuardAuthenticator
    {
    private $passwordEncoder;

    public function __construct(UserPasswordEncoderInterface $passwordEncoder)
    {
    $this->passwordEncoder = $passwordEncoder;
    }

    /**
    * {@inheritdoc}
    */
    public function getCredentials(Request $request)
    {
    if (!$request->isMethod('POST')) {
    throw new MethodNotAllowedHttpException(['POST']);
    }

    return [
    'username' => $request->request->get('username'),
    'password' => $request->request->get('password'),
    ];
    }

    /**
    * {@inheritdoc}
    */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
    return $userProvider->loadUserByUsername($credentials['username']);
    }

    /**
    * {@inheritdoc}
    */
    public function checkCredentials($credentials, UserInterface $user)
    {
    $plainPassword = $credentials['password'];

    if (!$this->passwordEncoder->isPasswordValid($user, $plainPassword)) {
    throw new BadCredentialsException();
    }
    }
    }
    7 changes: 7 additions & 0 deletions composer.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    {
    "require": {
    "symfony/symfony": "~2.7.4",
    "namshi/jose": "~6.0",
    "knpuniversity/guard-bundle": "~0.3"
    }
    }
    16 changes: 16 additions & 0 deletions config.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    # ...

    services:
    username_password_guard_authenticator:
    class: AppBundle\Security\UsernamePasswordGuardAuthenticator
    arguments: [@security.password_encoder]
    public: false

    jwt_guard_authenticator:
    class: AppBundle\Security\JWTGuardAuthenticator
    arguments: [@jwt_coder]
    public: false

    jwt_coder:
    class: AppBundle\Service\JWTCoder
    arguments: [%secret%]
    3 changes: 3 additions & 0 deletions routing.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    login:
    path: /login
    defaults: { _controller: AppBundle:User:login }
    24 changes: 24 additions & 0 deletions security.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,24 @@
    security:
    encoders:
    Symfony\Component\Security\Core\User\UserInterface: plaintext # change for real app

    providers:
    in_memory: # change for real app
    memory:
    users:
    user: { password: userpass }

    firewalls:
    login:
    pattern: ^/login$
    stateless: true
    knpu_guard:
    authenticators:
    - username_password_guard_authenticator

    api:
    pattern: ^/api
    stateless: true
    knpu_guard:
    authenticators:
    - jwt_guard_authenticator