<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Browsely</title>
    <description>The latest articles on DEV Community by Browsely (@browsely_a899a03e6654ba38).</description>
    <link>https://dev.to/browsely_a899a03e6654ba38</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3448179%2Fcffc310f-b06e-4824-9004-745e8e519bd0.png</url>
      <title>DEV Community: Browsely</title>
      <link>https://dev.to/browsely_a899a03e6654ba38</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/browsely_a899a03e6654ba38"/>
    <language>en</language>
    <item>
      <title>Symfony as JWT provider</title>
      <dc:creator>Browsely</dc:creator>
      <pubDate>Sat, 30 Aug 2025 12:11:48 +0000</pubDate>
      <link>https://dev.to/browsely_a899a03e6654ba38/symfony-as-jwt-provider-28a4</link>
      <guid>https://dev.to/browsely_a899a03e6654ba38/symfony-as-jwt-provider-28a4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In the process of writing this guide Ryan Weaver passed away. &lt;br&gt;
This news was truly shocking to me and my thoughts are with his family and loved ones. I hope this guide can do some justice to his spirit. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, in my &lt;a href="https://browsely.ai/blog/pivot-auth0-to-symfony" rel="noopener noreferrer"&gt;previous article&lt;/a&gt; I talked about why I decided to switch from Auth0 to Symfony as my authentication provider. In that article I also promised to write a follow up guide on how I actually implemented  it. &lt;/p&gt;

&lt;p&gt;For some context this guide is &lt;strong&gt;not&lt;/strong&gt; about authenticating &lt;strong&gt;to&lt;/strong&gt; &lt;strong&gt;Symfony&lt;/strong&gt; with JWT rather it is about using Symfony to authenticate users to other systems. The setup used in this guide is quite simple. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user interacts through the browser extension.&lt;/li&gt;
&lt;li&gt;These interactions (e.g. LLM streaming and such) go through Node.js. &lt;/li&gt;
&lt;li&gt;Symfony is setup as a back-end API for handling data. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now Symfony also houses the user and authentication system and that flow goes as follows: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user logs into the Symfony application&lt;/li&gt;
&lt;li&gt;The extension requests a JWT and Refresh token (RT)&lt;/li&gt;
&lt;li&gt;The extension calls to Node.js with the JWT and Node.js validates it &lt;/li&gt;
&lt;li&gt;When the JWT expires the RT is exchanged for a new RT and JWT&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this guide I will only go over the Symfony side of this system. so it will require you to implement the other areas yourself, however for most of these you can follow Auth0's guides since its basically the same set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we begin
&lt;/h2&gt;

&lt;p&gt;This is a bit more of an advanced guide and its quite loose in the sense I expect you to apply it to your situation, so blindly copy and pasting this code and commands will not solve your problem. &lt;/p&gt;

&lt;p&gt;I also presume you have / know the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An up and running symfony application with users and login&lt;/li&gt;
&lt;li&gt;Some knowledge of how JWT works and what it is&lt;/li&gt;
&lt;li&gt;Enough knowledge of security to apply this in a safe way&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;To start something to mention is that we will be using  &lt;code&gt;web-token/jwt-bundle&lt;/code&gt;, all the classes in this bundle are in the &lt;code&gt;Jose\&lt;/code&gt; namespace, and i'm not sure why, I was also confused. I will however refer to this bundle as Jose since its shorter.&lt;/p&gt;

&lt;p&gt;To begin we must install the aforementioned bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require web-token/jwt-bundle web-token/jwt-signature-algorithm-rsa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Jose bundle is a framework for working with JWT and its the foundation for this setup.&lt;/p&gt;

&lt;p&gt;Now that we have installed the bundle we will begin by configuring it, and to do this we must generate the keys. The keys will live in the &lt;code&gt;config/jwt/&lt;/code&gt; folder but you can change this if you want.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: you &lt;strong&gt;should not&lt;/strong&gt; commit these key's to git&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;config/jwt
php bin/console keyset:generate:rsa &lt;span class="nt"&gt;--random_id&lt;/span&gt; &lt;span class="nt"&gt;--use&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sig &lt;span class="nt"&gt;--alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;RS256 3 4096 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; config/jwt/signature.jwkset
php bin/console key:generate:rsa 2048 &lt;span class="nt"&gt;--use&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sig &lt;span class="nt"&gt;--alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;RS256 &lt;span class="nt"&gt;--random_id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; config/jwt/signature.jwk
php bin/console keyset:rotate &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;config/jwt/signature.jwkset&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;config/jwt/signature.jwk&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; config/jwt/signature.jwkset
php bin/console keyset:convert:public &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;config/jwt/signature.jwkset&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; config/jwt/public.jwkset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note:&lt;br&gt;
If you have read the docs, they use &lt;code&gt;./jose.phar&lt;/code&gt; however the commands should just start with &lt;code&gt;php bin/console&lt;/code&gt; this goes for all &lt;code&gt;jose&lt;/code&gt; commands. So no need to mess with &lt;code&gt;./jose.phar&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I will quickly go over what each of these commands does, to give some context. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;creates the &lt;code&gt;config/jwt&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;generates a private keyset of 3 keys (none of which we'll use)&lt;/li&gt;
&lt;li&gt;generates a single private key that we will use for signing our JWT's&lt;/li&gt;
&lt;li&gt;rotates our newly generated &lt;em&gt;real&lt;/em&gt; key into the key set &lt;/li&gt;
&lt;li&gt;creates a public keyset that other systems will use to verify our signed JWT's&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now one nice thing about this JWT setup is that we can change our private key without breaking everything. so if its compromised you simply run the last 3 commands and the whole system will use a new private key, while the existing signed JWT's are still valid since their public keys remain in the public key set.&lt;/p&gt;

&lt;p&gt;To finish off the configuration of Jose we must set up the config file if you followed the steps so far you can simply copy and paste it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# /config/packages/jose.yaml&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;env(JWT_PRIV)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%kernel.project_dir%/config/jwt/signature.jwk'&lt;/span&gt;  
    &lt;span class="na"&gt;env(JWTSET_PRIV)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%kernel.project_dir%/config/jwt/signature.jwkset'&lt;/span&gt;  
    &lt;span class="na"&gt;env(JWTSET_PUB)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%kernel.project_dir%/config/jwt/public.jwkset'&lt;/span&gt;  

&lt;span class="na"&gt;jose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="na"&gt;user_sig_priv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
            &lt;span class="na"&gt;jwk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(file:JWT_PRIV)%'&lt;/span&gt;  
                &lt;span class="na"&gt;is_public&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;  

    &lt;span class="na"&gt;key_sets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="na"&gt;user_sig_priv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
            &lt;span class="na"&gt;jwkset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(file:JWTSET_PRIV)%'&lt;/span&gt;  
                &lt;span class="na"&gt;is_public&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;  
        &lt;span class="na"&gt;user_sig_pub&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
            &lt;span class="na"&gt;jwkset&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
                &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%env(file:JWTSET_PUB)%'&lt;/span&gt;  
                &lt;span class="na"&gt;is_public&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;  

    &lt;span class="na"&gt;jws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
        &lt;span class="na"&gt;builders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
            &lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
                &lt;span class="na"&gt;signature_algorithms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RS256'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;  
                &lt;span class="na"&gt;is_public&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;  

        &lt;span class="na"&gt;verifiers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
            &lt;span class="na"&gt;verifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
                &lt;span class="na"&gt;signature_algorithms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RS256'&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;  
                &lt;span class="na"&gt;is_public&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All this config basically does is allow us to use the keys we just created more easily within our Symfony project. it also sets up a builder and a verifier service which we'll use later. &lt;br&gt;
After doing this the JWT icon should appear in the debug toolbar (on any page) and it will show 1 key and 2 keysets.&lt;/p&gt;
&lt;h2&gt;
  
  
  Refresh tokens
&lt;/h2&gt;

&lt;p&gt;Now this guide includes the use of refresh tokens (RT). These are tokens stored in the database that the user can exchange for a new JWT and RT. The reason we want this is so we can keep the TTL of the refresh token very short. which has various advantages (which I won't list), and as long as the RT doesn't expire or is removed from the db the user remains logged in.&lt;/p&gt;

&lt;p&gt;To store the RT in the database we obviously need to create an entity, here is a stub of the one I use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# src/Entity/RefreshToken.php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Repository\RefreshTokenRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Doctrine\ORM\Mapping&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="no"&gt;ORM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ORM\Entity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repositoryClass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RefreshTokenRepository&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RefreshToken&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ORM\Id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ORM\GeneratedValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ORM\Column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ORM\Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ORM\Column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;\DateTime&lt;/span&gt; &lt;span class="nv"&gt;$expires&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ORM\ManyToOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inversedBy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'refreshTokens'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  
    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ORM\JoinColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;?User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;ORM\Column&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nc"&gt;\DateTime&lt;/span&gt; &lt;span class="nv"&gt;$issued&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;//GETTERS AND SETTERS NOT INCLUDED&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to you can change this entity, add whatever fields you'd like. You can change the &lt;code&gt;$user&lt;/code&gt; property to something else so that the token doesn't have to be associated with a &lt;code&gt;User&lt;/code&gt; object . this offers more options but also more complications. Also the &lt;code&gt;$issued&lt;/code&gt; property is technically not needed, but I like having it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Generating JWT's
&lt;/h2&gt;

&lt;p&gt;Now with all that setup out of the way I introduce to you the &lt;code&gt;JWTService&lt;/code&gt;. This service will do all that you need for most JWT setups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# src/Service/JWTService.php &lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Entity\RefreshToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Entity\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Jose\Component\Core\JWK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jose\Component\Core\JWKSet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;Jose\Component\Signature\JWS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jose\Component\Signature\JWSBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jose\Component\Signature\JWSVerifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jose\Component\Signature\Serializer\CompactSerializer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JWTService&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;ALG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'RS256'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;JWSBuilder&lt;/span&gt; &lt;span class="nv"&gt;$builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;JWSVerifier&lt;/span&gt; &lt;span class="nv"&gt;$verifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;CompactSerializer&lt;/span&gt; &lt;span class="nv"&gt;$serializer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;JWK&lt;/span&gt;    &lt;span class="nv"&gt;$userSigPrivKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;JWKSet&lt;/span&gt; &lt;span class="nv"&gt;$userSigPubKeySet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="kt"&gt;JWSBuilder&lt;/span&gt;              &lt;span class="nv"&gt;$builderJwsBuilder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="kt"&gt;JWSVerifier&lt;/span&gt;             &lt;span class="nv"&gt;$verifierJwsVerifier&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$builderJwsBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;verifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$verifierJwsVerifier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CompactSerializer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;generateJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;JWS&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
            &lt;span class="s1"&gt;'sub'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
            &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
                &lt;span class="c1"&gt;//This array is where you can store some signed information about the user &lt;/span&gt;
                &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
            &lt;span class="p"&gt;],&lt;/span&gt;  
            &lt;span class="s1"&gt;'iat'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
            &lt;span class="s1"&gt;'exp'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt; &lt;span class="c1"&gt;//This decides the JWT TTL,  &lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;  

        &lt;span class="nv"&gt;$jws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$claims&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;addSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userSigPrivKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  
                &lt;span class="s1"&gt;'alg'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ALG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
                &lt;span class="s1"&gt;'kid'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userSigPrivKey&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'kid'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
            &lt;span class="p"&gt;])&lt;/span&gt;  
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createRT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bin2hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;random_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  

        &lt;span class="nv"&gt;$rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RefreshToken&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setExpires&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\DateTime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;modify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+30 days'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;//This decides the RT TTL &lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setIssued&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\DateTime&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;  

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$rt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;generateJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$jws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generateJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;serializeJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;verifyJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;JWS&lt;/span&gt; &lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;verifier&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verifyWithKeySet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userSigPubKeySet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$valid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;JWSExpires&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;JWS&lt;/span&gt; &lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;\DateTime&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPayload&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;date_create_from_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'U'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'exp'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;  

    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;serializeJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;JWS&lt;/span&gt; &lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;unserializeJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$JWT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;JWS&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;unserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$JWT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only things you might want to take a look at are the &lt;code&gt;createRT&lt;/code&gt; and &lt;code&gt;generateJWS&lt;/code&gt; functions these two might need adjusting depending on your situation. Primarily the &lt;code&gt;generateJWS&lt;/code&gt; function's data array. in this array you can store any information that you want to be signed. However this information can be decoded by anyone since its just base64. So only put things in there which are not super critical i.e. passwords or api keys.&lt;/p&gt;

&lt;p&gt;In this code the TTL of both the RT and JWT are defined, change them however you like, I personaly found JWT 10 min and RT 1 month to work well for my situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Providing JWT's
&lt;/h2&gt;

&lt;p&gt;Now that we've got our &lt;code&gt;JWTService&lt;/code&gt; setup we need to create 3 endpoints&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;to share our public key set (JWKS)&lt;/li&gt;
&lt;li&gt;for the user to login and request an initial JWT and RT&lt;/li&gt;
&lt;li&gt;for exchanging an RT for a new JWT and RT&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a simple controller that does these 3 things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;  

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Entity\RefreshToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Service\JWTService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Doctrine\ORM\EntityManagerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Jose\Component\Core\JWKSet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Bundle\FrameworkBundle\Controller\AbstractController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Routing\Attribute\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Security\Core\User\UserInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JWTController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractController&lt;/span&gt;  
&lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;JWTService&lt;/span&gt;             &lt;span class="nv"&gt;$jwtService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;EntityManagerInterface&lt;/span&gt; &lt;span class="nv"&gt;$entityManager&lt;/span&gt;  
    &lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/.well-known/jwks.json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'jwt_jwks'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;jwks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;JWKSet&lt;/span&gt; &lt;span class="nv"&gt;$userSigPubKeySet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userSigPubKeySet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/jwt/request'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'jwt_request'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;UserInterface&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nv"&gt;$jws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generateJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="nv"&gt;$rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createRT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$rt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  


        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;  
            &lt;span class="s1"&gt;'token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;serializeJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
            &lt;span class="s1"&gt;'token_expires'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;JWSExpires&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'U'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
            &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$rt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
            &lt;span class="s1"&gt;'refresh_expires'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$rt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getExpires&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'U'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="p"&gt;]);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/jwt/refresh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'jwt_refresh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;  
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContent&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
            &lt;span class="nv"&gt;$token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'refresh_token'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Throwable&lt;/span&gt; &lt;span class="nv"&gt;$th&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Bad request'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;  

        &lt;span class="nv"&gt;$oldRt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RefreshToken&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findOneBy&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;  

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$oldRt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'non-existent token'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;  

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oldRt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getExpires&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\DateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'now'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Token expired'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;  

        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$oldRt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oldRt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

        &lt;span class="nv"&gt;$jws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generateJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="nv"&gt;$newRt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;createRT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$newRt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entityManager&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;  
            &lt;span class="s1"&gt;'token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;serializeJWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
            &lt;span class="s1"&gt;'token_expires'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;jwtService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;JWSExpires&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'U'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  
            &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$newRt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  
            &lt;span class="s1"&gt;'refresh_expires'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$newRt&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getExpires&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'U'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="p"&gt;]);&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, you can and should change this controller in what ever way best suits your situation. &lt;/p&gt;

&lt;p&gt;Somethings to note, the &lt;code&gt;jwt_jwks&lt;/code&gt; is fully public. This is not a (big) security risk as all this allows is validation of the signature. however it is generally advisable to also limit access to this route if possible. &lt;/p&gt;

&lt;h2&gt;
  
  
  In closing
&lt;/h2&gt;

&lt;p&gt;Here are some things you should now also do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;rotate your keys&lt;/strong&gt;, I'd recommend setting up something that rotates the private key very day or so or on deployment, this is especially recommended if you keep the &lt;code&gt;jwt_jwks&lt;/code&gt; public.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleanup RT's&lt;/strong&gt; It is also recommended to set up a cron job that deletes any expired RT's since there is no reason to keep them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up testing&lt;/strong&gt; setup proper testing to make sure your system is well protected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have any questions or find problems in this guide I would love to help clarify / fix them &lt;a href="https://x.com/TomvdPeet" rel="noopener noreferrer"&gt;@TomvdPeet&lt;/a&gt;. But keep in mind that this guide is supposed to be followed loosely and modified to your needs.&lt;/p&gt;

&lt;p&gt;This article is 100% human written &lt;/p&gt;

&lt;p&gt;Lastly this guide is provided “as is” for informational purposes. Use at your own risk. No warranties. I accept no liability for any loss or damage arising from its use.&lt;/p&gt;

&lt;p&gt;Original article: &lt;a href="https://browsely.ai/blog/symfony-as-jwt-provider" rel="noopener noreferrer"&gt;https://browsely.ai/blog/symfony-as-jwt-provider&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Pivoting from Auth0 to PHP Symfony</title>
      <dc:creator>Browsely</dc:creator>
      <pubDate>Wed, 20 Aug 2025 14:51:00 +0000</pubDate>
      <link>https://dev.to/browsely_a899a03e6654ba38/pivoting-from-auth0-to-php-symfony-39h</link>
      <guid>https://dev.to/browsely_a899a03e6654ba38/pivoting-from-auth0-to-php-symfony-39h</guid>
      <description>&lt;p&gt;These days when creating a system there are many different ways of setting up authentication. However the main options boil down to: a self made solution, a Aaas solution (authentication as a service) or a combination of the two, Aaas has become more a lot popular recently, with companies like OpenAI opting for this. And in the space of Aaas the most recognizable name is Auth0 (Partly i think because of OpenAI). &lt;/p&gt;

&lt;p&gt;The reason we needed authentication was to verify user in node.js. Node being our service for communicating between the user and the LLM. In this article we'll go over: why we went for Auth0, the problems we ran into, and why we pivoted to Symfony&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we chose for Auth0
&lt;/h2&gt;

&lt;p&gt;Starting out with Browsely we obviously wanted to get the product to the market as fast as possible, so we opted for what seemed like the easiest and quickest solution Aaas and specificly Auth0. Here is a list of perceived pros of Auth0: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Quick to implement, because we wouldn't have to build much&lt;/li&gt;
&lt;li&gt; Free to get started&lt;/li&gt;
&lt;li&gt; Social login off the bat &lt;/li&gt;
&lt;li&gt; No need to setup email and for: password reset and registration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These points seemed very appealing so we got started with implementing it and we very quickly started running into problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problems with Auth0
&lt;/h3&gt;

&lt;p&gt;When implementing Auth0 the first problem we ran into is that its existing JS package is not really meant to be used for a web extension. From some things we came across online we found that supposedly in the past they did have support but it since has been removed. This meant we had to implement quite a lot of the authentication layer our self's. And because the JS package is meant to be used plug-and-play without much customization we had to back engineer it quite a bit to get it to work in a web extension. But eventually we did get it to work and did end up working as expected. &lt;/p&gt;

&lt;p&gt;Now the next problems arose when building the subscription layer, which we decided to do with  Stripe as payment provider. This created two issues 1. We now needed to store user data in two external systems, both of which needed to be kept up to date. 2. The user would now have to log in to our portal and in to the extension separately which we thaught would feel clunky. We did make a start on trying to solve the first problem but very quickly we felt the effort required to implement this neatly was to great at this stage in the project. And it was at this point we decided to pivot. &lt;/p&gt;

&lt;p&gt;Another problem not mentioned before is the pricing of Auth0 as we started using it we found the free packed permits quite a lot, and the payed plans seemingly less by comparison. Overall it was unclear when you'd have to pay and what you'd have to pay for. This problem was less pressing but one that could blow up at a later stage. &lt;/p&gt;

&lt;p&gt;A quick summary of the problems we ended up having with Auth0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Messy to implement in to an extension&lt;/li&gt;
&lt;li&gt; Messy to set up with Stripe &lt;/li&gt;
&lt;li&gt; The user would have to login manually in two different places &lt;/li&gt;
&lt;li&gt; Vague pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The pivot to Symfony
&lt;/h2&gt;

&lt;p&gt;Something i have to mention: our website was already built in Symfony. Now i hear you ask, why Symfony? That's simple, we know Symfony, so we can build fast and know what we can expect from it, now and moving forward. So logically we would migrate the whole auth system to it.&lt;/p&gt;

&lt;p&gt;One of the things we learned form our initial Auth0 implementation that we really liked was JWT. A protocol that would make centralized authentication much easier. So our setup would be: Symfony creates and signs a JWT and send it to the user, the user would send it with their request to node, node verifies based on the public key. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want more technical information about how we setup Symfony as a JWT provider lookout for a future article where we go more in depth. Since we found there currently is not much information on how to do this with Symfony.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, this setup is essentially the same way Auth0 works the only difference being that implementing this yourself gives a lot of liberties and options, allowing us to resolve most of the problems we had with Auth0. The implementation is much cleaner and allows for the user to simply log in to our website and they will automatically be signed in to the extension. We dont have to sync user data between both Auth0 and Stripe. And the pricing is very stable and predictable this way. The only downside thus far has been the lack of social login off the bat.&lt;/p&gt;

&lt;p&gt;We are now very happy about our decision to pivot and have learnt a lot from the whole experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What have we learned. And what can you learn from this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stick to what you know.&lt;/strong&gt; Building the auth system in Symfony ended up taking about as much time as setting up Auth0. Howerver the result is alot more robust and ready for future developments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't outsource to save time.&lt;/strong&gt; This is something we had already experienced in the past but at a small scale outsourcing usually costs about as much time if not more as doing it your self. There are exceptions like payment providers for example but generally, &lt;strong&gt;if you can do it yourself its usually faster&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control is important.&lt;/strong&gt; Keeping as much of your product in house as possible especially starting out. &lt;strong&gt;Its easier to outsource something at a later point than it is to take something back.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be ready to pivot&lt;/strong&gt; If you feel like a solution is causing more problems than its solving its most often time to look for a better solution.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also i quickly want to mention that this article is not against Auth0 or Aaas in anyway, we think there are very valid use cases, especially for more traditionally structured  web-apps.&lt;/p&gt;

&lt;p&gt;Original article: &lt;a href="https://browsely.ai/blog/pivot-auth0-to-symfony" rel="noopener noreferrer"&gt;https://browsely.ai/blog/pivot-auth0-to-symfony&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
