avatarVladimir Kovalchuk

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

3354

Abstract

js-variable">params</span>; <span class="hljs-variable language_">this</span>->jwtManager = <span class="hljs-variable">jwtManager</span>; }</pre></div><div id="f4f8"><pre>public<span class="hljs-keyword"> static</span> function getSubscribedEvents():<span class="hljs-built_in"> array </span>{ <span class="hljs-built_in"> return </span>[ Events::JWT_AUTHENTICATED =&gt; 'onAuthenticatedAccess', KernelEvents::RESPONSE =&gt; ['onAuthenticatedResponse', EventPriorities::PRE_RESPOND] ]; }</pre></div><div id="eeb1"><pre>public function onAuthenticatedResponse(ResponseEvent event) { <span class="hljs-function"><span class="hljs-title">response</span> = event-></span>getResponse(); <span class="hljs-function"><span class="hljs-title">response</span>-&gt;</span><span class="hljs-function"><span class="hljs-title">headers</span>-&gt;</span>set(<span class="hljs-string">'Access-Control-Allow-Credentials'</span>, <span class="hljs-string">'true'</span>); <span class="hljs-function"><span class="hljs-title">response</span>-></span><span class="hljs-function"><span class="hljs-title">headers</span>-></span>set(<span class="hljs-string">'Access-Control-Allow-Headers'</span>, implode(<span class="hljs-string">', '</span>, [<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'Origin'</span>])); <span class="hljs-comment">// you can add more custom headers (if you send some) here</span></pre></div><div id="b02f"><pre><span class="hljs-keyword">if</span> (!<span class="hljs-keyword">empty</span>(<span class="hljs-variable language_">this</span>-&gt;payload) &amp;&amp; !<span class="hljs-keyword">empty</span>(<span class="hljs-variable language_">this</span>->user)) { <span class="hljs-variable">expireTime</span> = <span class="hljs-variable language_">this</span>->payload[<span class="hljs-string">'exp'</span>] - <span class="hljs-title function_ invoke__">time</span>(); <span class="hljs-keyword">if</span> (<span class="hljs-variable">expireTime</span> &lt; <span class="hljs-built_in">static</span>::<span class="hljs-variable constant_">TIME_LEFT</span>) { <span class="hljs-comment">// Refresh token by creating a new one</span> <span class="hljs-variable">jwt</span> = <span class="hljs-variable language_">this</span>-&gt;jwtManager-&gt;<span class="hljs-title function_ invoke__">create</span>(<span class="hljs-variable">this</span>->user); <span class="hljs-comment">// Set cookie</span> <span class="hljs-variable language_">this</span>-&gt;<span class="hljs-title function_ invoke__">setJWTCookie</span>(<span class="hljs-variable">response</span>, <span class="hljs-variable">jwt</span>); } } }</pre></div><div id="9d63"><pre>public function onAuthenticatedAccess(JWTAuthenticatedEvent event) { <span class="hljs-function"><span class="hljs-title">this</span>-&gt;</span><span class="hljs-function"><span class="hljs-title">payload</span> = event-></span>getPayload(); <span class="hljs-function"><span class="hljs-title">this</span>-&gt;</span><span class="hljs-function"><span class="hljs-title">user</span> = event-></span><span class="hljs-fun

Options

ction"><span class="hljs-title">getToken</span>()-></span>getUser(); }</pre></div><div id="2989"><pre><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setJWTCookie</span>(<span class="hljs-params">Response <span class="hljs-variable">response</span>, <span class="hljs-variable">token</span></span>) </span>{ <span class="hljs-variable">ttlSeconds</span> = <span class="hljs-variable language_">this</span>->params-><span class="hljs-title function_ invoke__">get</span>(<span class="hljs-string">'token_ttl'</span>); <span class="hljs-variable">response</span>-&gt;headers-&gt;<span class="hljs-title function_ invoke__">setCookie</span>( <span class="hljs-title class_">Cookie</span>::<span class="hljs-title function_ invoke__">create</span>( <span class="hljs-string">"BEARER"</span>, <span class="hljs-variable">token</span>, <span class="hljs-keyword">new</span> <span class="hljs-title class_">\DateTime</span>(<span class="hljs-string">"+<span class="hljs-subst">{$ttlSeconds}</span> second"</span>), <span class="hljs-string">"/"</span>, <span class="hljs-literal">null</span>, <span class="hljs-literal">true</span>, <span class="hljs-literal">true</span>, <span class="hljs-literal">false</span>, <span class="hljs-string">"lax"</span> ) ); } }</pre></div><p id="c87e">Some explanations here: to be fully in sync with our configuration and not to hardcode it twice — you have to move the lifetime of you token into a separate parameter and use it to configure lexik bundle and our httpOnly cookies.</p><p id="5246">You can use parameters.yml located in /config/packages/parameters.yaml in your project as follows:</p><div id="0e3a"><pre><span class="hljs-attr">parameters:</span> <span class="hljs-comment"># some more params can live here</span> <span class="hljs-attr">token_ttl:</span> <span class="hljs-number">3600</span></pre></div><p id="8b9b">And then in lexik config file (/config/packages/lexik_jwt_authentication.yaml) use it like this:</p><div id="b683"><pre><span class="hljs-attribute">lexik_jwt_authentication</span><span class="hljs-punctuation">:</span> <span class="hljs-attribute">token_ttl</span><span class="hljs-punctuation">:</span> <span class="hljs-string"> '%token_ttl%'</span></pre></div><p id="3e6f">What have we done using the subscriber we created?</p><ol><li>When user got authenticated (Events::JWT_AUTHENTICATED) we run onAuthenticatedAccess method where the payload (token data) and the user is saved for the future.</li><li>Using previously saved token data and user — we are checking if it’s time to refresh the credentials by comparing the time left to expiration with our own custom constant: TIME_LEFT.</li><li>Finally (if it’s time to) we create a new jwt token and instruct the FE to save it for future requests by setting Set-Cookie header in setJWTCookie subscriber’s method.</li></ol><p id="14a2">The step #3 will be done over and over again until the user stops using app — then the auth token will just expire and the user will be forced to authenticate again.</p></article></body>

Part 3. Refreshing the token

After we’ve done Part 1 and Part 2 parts — we can set the httpOnly cookie with an auth token and read it back authenticating the user. But what will happened when the token become outdated?

The next thing we need to think about — is to constantly refreshing the token while the user is using the app.

To do so — we can create a subscriber which should listen to 2 events: the first one is fired when the user is got authenticated and the second one is response event.

We need a file like this to be created in you subscribers dir:

<?php
namespace App\EventSubscriber;
use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
class AuthSubscriber implements EventSubscriberInterface {
const TIME_LEFT = 5 * 60;
/** @var ParameterBagInterface */
    private $params;
/** @var JWTTokenManagerInterface */
    private $jwtManager;
/**
     * @required
     * @param ParameterBagInterface $params
     */
    public function setUp(
        ParameterBagInterface $params, 
        JWTTokenManagerInterface $jwtManager
    ) {
        $this->params = $params;
        $this->jwtManager = $jwtManager;
    }
public static function getSubscribedEvents(): array {
        return [
            Events::JWT_AUTHENTICATED => 'onAuthenticatedAccess',
            KernelEvents::RESPONSE => ['onAuthenticatedResponse', EventPriorities::PRE_RESPOND]
        ];
    }
public function onAuthenticatedResponse(ResponseEvent $event) {
        $response = $event->getResponse();
        $response->headers->set('Access-Control-Allow-Credentials', 'true');
        $response->headers->set('Access-Control-Allow-Headers', implode(', ', ['Content-Type', 'Origin']));
// you can add more custom headers (if you send some) here
if (!empty($this->payload) && !empty($this->user)) {
            $expireTime = $this->payload['exp'] - time();
            if ($expireTime < static::TIME_LEFT) {
                // Refresh token by creating a new one
                $jwt = $this->jwtManager->create($this->user);
                // Set cookie
                $this->setJWTCookie($response, $jwt);
            }
        }
    }
public function onAuthenticatedAccess(JWTAuthenticatedEvent $event) {
        $this->payload = $event->getPayload();
        $this->user = $event->getToken()->getUser();
    }
private function setJWTCookie(Response $response, $token) {
        $ttlSeconds = $this->params->get('token_ttl');
        $response->headers->setCookie(
            Cookie::create(
                "BEARER",
                $token,
                new \DateTime("+{$ttlSeconds} second"),
                "/",
                null,
                true,
                true,
                false,
                "lax"
            )
        );
    }
}

Some explanations here: to be fully in sync with our configuration and not to hardcode it twice — you have to move the lifetime of you token into a separate parameter and use it to configure lexik bundle and our httpOnly cookies.

You can use parameters.yml located in /config/packages/parameters.yaml in your project as follows:

parameters:
    # some more params can live here
    token_ttl: 3600

And then in lexik config file (/config/packages/lexik_jwt_authentication.yaml) use it like this:

lexik_jwt_authentication:
    token_ttl:   '%token_ttl%'

What have we done using the subscriber we created?

  1. When user got authenticated (Events::JWT_AUTHENTICATED) we run onAuthenticatedAccess method where the payload (token data) and the user is saved for the future.
  2. Using previously saved token data and user — we are checking if it’s time to refresh the credentials by comparing the time left to expiration with our own custom constant: TIME_LEFT.
  3. Finally (if it’s time to) we create a new jwt token and instruct the FE to save it for future requests by setting Set-Cookie header in setJWTCookie subscriber’s method.

The step #3 will be done over and over again until the user stops using app — then the auth token will just expire and the user will be forced to authenticate again.

Security
Httponly
Cookies
Symfony
Authentication
Recommended from ReadMedium