mnavarrocarter/auth-bundle

This package is abandoned and no longer maintained. No replacement package was suggested.

1.0.0 2018-07-13 14:58 UTC

This package is auto-updated.

Last update: 2019-08-18 18:29:24 UTC


README

Introduction

This bundle provides JWT Authentication for your Symfony Project.

Installation

Simply run composer require mnavarrocarter/auth-bundle

Before that, make sure you have Symfony's security component (the bundle) in your project, using: composer require security (Flex only)

Configuration

In order for this Bundle to work properly with your application, you need to define a series of rules in your security.yaml. The most important ones are the the ones defined in the two firewalls, auth and main.

The auth firewall is where the user authenticates with username and email and as a result, a token is returned to him. The token is created in the mnc_auth.login_handler.default service. You can replace and create your own. Just use the original one as an example.

In the main firewall, the authentication method is the token itself. This is processed by the mnc_auth.guard_authenticator.default service. If you want to customize the behaviour of this class (success and failure responses, or SymfonyToken creation) the best way is to extend MNC\Bundle\AuthBundle\Security\Guard\JwtGuardAuthenticator and override the methods that interest you. Then define it as a service and place it in your authenticator.

security:
    firewalls:
        
        # ... other firewalls
        
        auth:
            pattern:  ^/auth
            stateless: false
            anonymous: true
            form_login:
                check_path: /auth/token
                # This habdler creates the token and returns it in a response
                # You can also create your own.
                # See: 
                success_handler: mnc_auth.login_handler.default
                # The failure handler just returns an error response
                failure_handler: mnc_auth.login_handler.default
                # This only changes the default params
                username_parameter: email
                password_parameter: password
        
        main:
            pattern: ^/
            anonymous: false
            # Important if you want to do token session invalidation
            stateless: false
            guard:
                # This authenticator does all the work when a request carries a token.
                authenticators:
                    - mnc_auth.guard_authenticator.default

Also, you need to add this route in your routes.yaml

auth_token:
    path: /auth/token

How it works

Using the awesome lcobucci/jwt library, this bundle creates a JWT encoded with Hmac Sha 512 using your kernel secret as your secret key. The claims are:

  • sub (subject): the value from UserInterface::getUsername()
  • exp (expiration): the token creation time + 3600 seconds
  • jti (json token id): an autogenerated id for the token

However, you can implement custom behaviour beyond these defaults. Keep reading to know how.

The TokenService

Token creation and verification is centralized in the TokenService. This services has two required dependencies and a third optional one.

The first dependency is a class implementing TokenBuilderInterface. It contains the instructions for token creation and validation. You can create your own implementation that uses different signing algorithms and that sets different claims and validation rules. Then, you can make the TokenService use your implementation by modifying the config.

The second dependency is a class implementing the TokenIdentifierGenerator interface, that generates ids for your JWT's. The default one uses PHP's uniqid function, but you can implement your own using other method. Then, you can make the TokenService use your implementation by modifying the config.

The third dependency is TokenStoreInterface. This bundle does not provide any implementation of that interface. That's up to you. By default, the TokenService does not use if it's not defined. For more info on how this can be of some use for you, refer to using the TokenStoreInterface below.

If you want to sign a token using Rsa, you have to implement your own token builder following TokenBuilderInterface contract.

Modifiying the default TokenBuilder

If the hashing algorithm (Hmac Sha 512) of the default token builder suits you, but you want to change the secret or the token lifetime, you can do so by modifying the config in the node mnc_auth.default_builder. Refer to the default config reference for more info.

Using the TokenStoreInterface

As it is, this bundles works and serves the basic purposes of authentication. However, there are more complex scenarios where you may need some extra functionality. For example, ¿what about being able to detect when a machine uses a token requested in another machine? That means that someone has either shared their token or stole it. ¿What if you want to revoke a token? JWT are non-persisted, that means, they are not stored in the db. So you don't have a way to "know" a token.

This is TokenStoreInterface to the rescue. The interface defines a minimal contract for token id persistence. You can associate that to a cookie for example, and to a user. And easily give the change to the user to revoke tokens in other sessions. The possibilities are basically up to you.

This bundle does not have a default TokenStore service, because that's an implementation detail. We don't know what persistence engine you are using. However, you can look at this example that implements the TokenStoreInterface in a DoctrineRepository, and invalidates tokens that do not belong to the same session id.

<?php

namespace App\Repository;

use App\Entity\Token;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use MNC\Bundle\AuthBundle\Token\Store\TokenStoreInterface;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;

/**
 * @method Token|null find($id, $lockMode = null, $lockVersion = null)
 * @method Token|null findOneBy(array $criteria, array $orderBy = null)
 * @method Token[]    findAll()
 * @method Token[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class TokenRepository extends ServiceEntityRepository implements TokenStoreInterface
{
    /**
     * @var RequestStack
     */
    private $requestStack;

    public function __construct(RegistryInterface $registry, RequestStack $requestStack)
    {
        parent::__construct($registry, Token::class);
        $this->requestStack = $requestStack;
    }

    public function store($tokenId): void
    {
        if ($this->getSession()->has('toid')) {
            $toid = $this->getSession()->get('toid');
        } else {
            $toid  = md5($this->getSession()->getId());
            $this->getSession()->set('toid', $toid);
        }

        $token = $this->findOneBy(['sessionId' => $toid]);

        if (null === $token) {
            $token = new Token($tokenId, $toid);
        } else {
            $token->updateTokenId($tokenId);
        }

        $manager = $this->getEntityManager();
        $manager->persist($token);
        $manager->flush();
    }

    public function revoke($tokenId): void
    {
        $token = $this->find($tokenId);
        if (null !== $token) {
            $token->revoke();
            $manager = $this->getEntityManager();
            $manager->persist($token);
            $manager->flush();
        }
    }

    public function isTokenValid($tokenId): bool
    {
        $token = $this->find($tokenId);
        if (null !== $token) {
            return $token->getSessionId() === $this->getSession()->get('toid');
        }
        return false;
    }

    public function isTokenRevoked($tokenId): bool
    {
        $token = $this->find($tokenId);
        if (null !== $token) {
            return $token->isRevoked();
        }
        return true;
    }

    public function getRequest(): Request
    {
        return $this->requestStack->getMasterRequest();
    }

    public function getSession(): SessionInterface
    {
        return $this->getRequest()->getSession();
    }
}

Default Config Reference

mnc_auth:
    token_service:
        # You can define a custom token builder changing this value
        token_builder: mnc_auth.token_builder.default
        # You can define a custom token id generator changing this value
        token_id_generator: mnc_auth.identifier_generator.default
        # You can define your implementation of token store here.
        token_store: ~
    default_builder:
        # This allows you to modify the secret for the default token builder
        secret: '%kernel.secret%'
        # This allows you to modify the expiration time for the default token builder.
        ttl: 3600
    # Token extractors configuration.
    token_extractors:
        cookie:
            key_name: token
        query_param:
            key_name: token
        header:
            header_name: Authorization
            prefix: Bearer