<?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>Forem: Fabien</title>
    <description>The latest articles on Forem by Fabien (@fabienbrn).</description>
    <link>https://forem.com/fabienbrn</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%2F1143257%2F1733bc7d-c45a-45fa-a574-87db2c3ae435.jpg</url>
      <title>Forem: Fabien</title>
      <link>https://forem.com/fabienbrn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fabienbrn"/>
    <language>en</language>
    <item>
      <title>Modern OAuth2 Discord Authentification with Symfony</title>
      <dc:creator>Fabien</dc:creator>
      <pubDate>Tue, 29 Aug 2023 16:38:01 +0000</pubDate>
      <link>https://forem.com/fabienbrn/modern-oauth2-discord-authentification-with-symfony-3h5m</link>
      <guid>https://forem.com/fabienbrn/modern-oauth2-discord-authentification-with-symfony-3h5m</guid>
      <description>&lt;p&gt;Are you making an app that's related to gaming ? Are you requiring users to be authenticated ? But most importantly, are you a gamer (although that's not a requirement) ? &lt;/p&gt;

&lt;p&gt;This tutorial has been created for you !&lt;/p&gt;




&lt;h2&gt;
  
  
  The OAuth2 protocol
&lt;/h2&gt;

&lt;p&gt;If you are already familiar with the OAuth2 protocol, you can already skip to the next part.&lt;/p&gt;

&lt;p&gt;I'm not going to make any revolutionary speech about what is the OAuth2 protocol and how great it is, so at first, I'm going to quote the official &lt;a href="https://oauth.net/2/" rel="noopener noreferrer"&gt;oauth2.net&lt;/a&gt; website for this one :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In simple words, it means the users, as clients, only have to click buttons to connect into your app using external services such as Google, Facebook, Discord, etc.&lt;/p&gt;

&lt;p&gt;Other than simplifying authentication, it also makes registration way easier, since there's none !&lt;/p&gt;

&lt;p&gt;With this protocol, the main resource, the key, is the &lt;strong&gt;access token&lt;/strong&gt;. The goal here is to obtain this access token using your secret API key, and use it in order to retrieve resources from the provider (such as user data).&lt;/p&gt;

&lt;p&gt;Here I've unleashed my skills and made a drawing on Paint so you get the idea :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlgz1vrie6379x6aoxb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlgz1vrie6379x6aoxb9.png" alt="OAuth2 drawing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that authentication and resource servers can be the same.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Registering your app
&lt;/h2&gt;

&lt;p&gt;The very first step but not the least, going onto &lt;a href="https://discord.com/developers/docs/intro" rel="noopener noreferrer"&gt;Discord's developer portal&lt;/a&gt; in order to register your app. Once you've done that, you will obtain your API keys that will make you able to send requests to the Discord API.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;em&gt;Before you continue, make sure you have a Discord account which is still available.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Find the button
&lt;/h3&gt;

&lt;p&gt;At first, click the "New application" button. You can find it on the top right hand corner of the portal, next to your profile :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1lidx0f4ac2js4nops4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1lidx0f4ac2js4nops4n.png" alt="Create application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter the name
&lt;/h3&gt;

&lt;p&gt;Once you've done that, a popup will appear so you can give a name to the app. For the purpose of the tutorial, the app will be called "OAuthTutorial" :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewvs4gkt4vlsrbrv0f36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewvs4gkt4vlsrbrv0f36.png" alt="Application name popup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously, don't forget to read the terms of service 😉&lt;/p&gt;

&lt;p&gt;You now have access to the dashboard of the app. &lt;/p&gt;

&lt;h2&gt;
  
  
  Create the Symfony application
&lt;/h2&gt;

&lt;p&gt;In this part, we are going to run throughout basic Symfony project setup. If you are already familiar with Symfony, you can already go to the next part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set the project up
&lt;/h3&gt;

&lt;p&gt;Open a terminal, and the following command :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

symfony new &lt;span class="nt"&gt;--webapp&lt;/span&gt; DiscordOauthTutorial


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you havn't installed &lt;a href="https://github.com/symfony-cli/symfony-cli" rel="noopener noreferrer"&gt;Symfony CLI&lt;/a&gt; yet, although I highly recommend you to do so, run the following boring command :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

composer create-project symfony/skeleton:&lt;span class="s2"&gt;"6.3.*"&lt;/span&gt; DiscordOauthTutorial
&lt;span class="nb"&gt;cd &lt;/span&gt;DiscordOauthTutorial
composer require webapp


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Run the webserver
&lt;/h3&gt;

&lt;p&gt;Once you've opened the project in your favorite IDE &lt;em&gt;which must obviously be PHP Storm I mean what else&lt;/em&gt;, run the following command :&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

symfony server:start


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You now have launched the local web server, which makes the app available at &lt;a href="https://127.0.0.1:8000/" rel="noopener noreferrer"&gt;https://127.0.0.1:8000/&lt;/a&gt; by default. If you click that link, you will be greeted by the Symfony welcome page 🥳.&lt;/p&gt;

&lt;p&gt;If you encounter problems or wish to learn more about local web servers, &lt;a href="https://symfony.com/doc/current/setup/symfony_server.html" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database things
&lt;/h2&gt;

&lt;p&gt;This part is for people who would like to authenticate the user using Symfony's authentication system. This requires creating a database with a &lt;code&gt;User&lt;/code&gt; entity. If you don't want to do so (&lt;a href="https://github.com/knpuniversity/oauth2-client-bundle#step-3-use-the-client-service" rel="noopener noreferrer"&gt;which is possible&lt;/a&gt;), skip to the next part.&lt;/p&gt;

&lt;p&gt;At first, let's modify the database information in our &lt;code&gt;.env&lt;/code&gt; file to match our database's settings :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&amp;amp;charset=utf8mb4"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Let's assume we are working on MariaDB.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then run :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;

&lt;span class="kd"&gt;php&lt;/span&gt; &lt;span class="kd"&gt;bin&lt;/span&gt;&lt;span class="na"&gt;/console &lt;/span&gt;&lt;span class="kd"&gt;doctrine&lt;/span&gt;&lt;span class="nl"&gt;:database:create&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Create the entity
&lt;/h3&gt;

&lt;p&gt;Now that our database is alive, run the following command :&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;

&lt;span class="kd"&gt;php&lt;/span&gt; &lt;span class="kd"&gt;bin&lt;/span&gt;&lt;span class="na"&gt;/console &lt;/span&gt;&lt;span class="kd"&gt;make&lt;/span&gt;&lt;span class="nl"&gt;:entity&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For the purpose of the tutorial, we are going to call the entity &lt;code&gt;User&lt;/code&gt; (how normy).&lt;/p&gt;

&lt;p&gt;The main goal here is to add a (e.g.) &lt;code&gt;discordId&lt;/code&gt; property, which will be a &lt;code&gt;string&lt;/code&gt;. This will hold the user's Discord ID so you will be able to retrieve or create it while authenticating.&lt;/p&gt;

&lt;p&gt;Our entity should look like this :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="na"&gt;#[ORM\Entity(repositoryClass: UserRepository::class)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Id]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\GeneratedValue]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Column]&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="na"&gt;#[ORM\Column(length: 255)]&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;$discordId&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&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Migrating to the database
&lt;/h3&gt;

&lt;p&gt;Let's now use the magic of Doctrine to push the entity into our database. In order to do so, Doctrine uses &lt;a href="https://www.doctrine-project.org/projects/doctrine-migrations/en/3.6/reference/introduction.html" rel="noopener noreferrer"&gt;migrations&lt;/a&gt;. These are lines of SQL that describe what's changed in our database since last migration.&lt;/p&gt;

&lt;p&gt;Obviously we don't have to generate migrations by ourselves, just run :&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

php bin/console make:migration


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will generate a file into the &lt;code&gt;migrations&lt;/code&gt; folder of our project. The migration name is "Version" followed by the datetime.&lt;/p&gt;

&lt;p&gt;Check it, push it.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

php bin/console doctrine:migrations:migrate


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Let's do it
&lt;/h2&gt;

&lt;p&gt;Fortunately, this planet has produced great people that make great things. I don't know if the people from KNP University are great, but from what I know, is that they make great tools to help us implementing complex things. So let's use one of these tools, the &lt;a href="https://github.com/knpuniversity/oauth2-client-bundle" rel="noopener noreferrer"&gt;KNP OAuth2 Client bundle&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Install the bundle
&lt;/h3&gt;

&lt;p&gt;To install the bundle, run :&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

composer require knpuniversity/oauth2-client-bundle


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After Composer has downloaded the bundle, it will ask you if you wish to execute the recipes for this bundle, which you should. The recipes will generate the configuration file for the bundle, which can be found at &lt;code&gt;config/packages/knpu_oauth2_client.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If, for some reasons, that does not happen, create the file and paste this in it :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;knpu_oauth2_client&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;clients&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Install the OAuth Discord client
&lt;/h3&gt;

&lt;p&gt;Since the KNP package provides a flexible tool for OAuth authentication, it requires you to install clients for it to work. Another great person on Earth has made it ! So, in order to install it, run :&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

composer require wohali/oauth2-discord-new


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/wohali/oauth2-discord-new" rel="noopener noreferrer"&gt;More information about the client here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the client
&lt;/h3&gt;

&lt;p&gt;Since we're authenticating our users using OAuth2, we need API keys. Spot on, you've just created a Discord app ! Let's gather what we need !&lt;/p&gt;

&lt;p&gt;At first, go to the dashboard of our app, and get the application ID :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9auexczrlfwptjliulb3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9auexczrlfwptjliulb3.png" alt="Discord developer dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to our &lt;code&gt;.env.local&lt;/code&gt; file, and paste the application ID into a brand new variable that is going to be called &lt;code&gt;DISCORD_APP_ID&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

DISCORD_APP_ID=app_id


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For the secret key, it's always more complicated...&lt;/p&gt;

&lt;p&gt;Click on the "OAuth2" section of the sidebar on the left side of the screen, and click the "General" link :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprnvbk06wqfjto0nhudf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprnvbk06wqfjto0nhudf.png" alt="Discord developer portal OAuth2 section"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On this page, you should find this :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F715oxoiztuuimlaculru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F715oxoiztuuimlaculru.png" alt="Secret key button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click this button, get the secret key, hold it tightly, and paste it into another variable that will be called &lt;code&gt;DISCORD_SECRET_KEY&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

DISCORD_SECRET_KEY=app_secret


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note that the app's secret key is important and private, and must not leak. If you have problems configuring the project, do not send screenshots on which people can see it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that our environment variables are set, go back to the YAML configuration file, and fill it like so :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;knpu_oauth2_client&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;clients&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;discord&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord&lt;/span&gt;
            &lt;span class="na"&gt;client_id&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(DISCORD_APP_ID)%'&lt;/span&gt;
            &lt;span class="na"&gt;client_secret&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(DISCORD_SECRET_KEY)%'&lt;/span&gt;
            &lt;span class="na"&gt;redirect_route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auth_discord_login&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, we have a &lt;code&gt;redirect_route&lt;/code&gt; parameter that sets a route name that does not exist yet. This route is where the magic will happen for the user to authenticate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the login route
&lt;/h3&gt;

&lt;p&gt;Let's create a &lt;a href="https://symfony.com/doc/current/controller.html" rel="noopener noreferrer"&gt;Controller&lt;/a&gt; named &lt;code&gt;DiscordController&lt;/code&gt;. I suggest you not to use the &lt;a href="https://symfony.com/doc/current/controller.html#generating-controllers" rel="noopener noreferrer"&gt;maker command&lt;/a&gt; since it also generates a template, which we don't want for this one.&lt;/p&gt;

&lt;p&gt;Once the Controller is created, create the route like so :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&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;KnpU\OAuth2ClientBundle\Client\ClientRegistry&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\Routing\Annotation\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[AsController]&lt;/span&gt;
&lt;span class="na"&gt;#[Route("/auth/discord", name: "auth_discord_")]&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiscordController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Route("/login", name: "login")]&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;login&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="kt"&gt;ClientRegistry&lt;/span&gt; &lt;span class="nv"&gt;$clientRegistry&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;Here the method is empty, since the login process will be handled by the &lt;strong&gt;custom authenticator&lt;/strong&gt;. However, you can &lt;a href="https://github.com/knpuniversity/oauth2-client-bundle#step-3-use-the-client-service" rel="noopener noreferrer"&gt;add logic in order to check authentication&lt;/a&gt; in it if you wish to.&lt;/p&gt;

&lt;p&gt;For the purpose of making it clean, we are separating the Discord authentication into a &lt;code&gt;/auth/discord/&lt;/code&gt; prefix.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;/auth&lt;/code&gt; part to make clear that we are in an authentication process.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;/discord&lt;/code&gt; part to separate the Discord auth from any authentication you could add.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that our app knows the way, Discord must also. So let's go back to the developer portal, onto the OAuth2 &amp;gt; General page.&lt;/p&gt;

&lt;p&gt;In this page, you will be able to set the &lt;strong&gt;redirect URL&lt;/strong&gt;. As a reminder, in the OAuth2 process, it's the URL that receives the code generated after the user said "Yes" for authentication on the vendor's page (the user must say "Yes" for you to access its data).&lt;/p&gt;

&lt;p&gt;So let's add this URL :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwyhotn392h2tgn7cj2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftwyhotn392h2tgn7cj2w.png" alt="Redirect URL configuration on Discord developer portal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the authenticator
&lt;/h3&gt;

&lt;p&gt;In order to manage authentication, Symfony uses &lt;a href="https://symfony.com/doc/current/security/custom_authenticator.html" rel="noopener noreferrer"&gt;Authenticators&lt;/a&gt;. These basically are services that defines how to retrieve the User and what to do once the job is done.&lt;/p&gt;

&lt;p&gt;Let's now create the big boy :&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\Security&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="nc"&gt;App\Repository\UserRepository&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;KnpU\OAuth2ClientBundle\Client\ClientRegistry&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;KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator&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\RedirectResponse&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\RouterInterface&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\Authentication\Token\TokenInterface&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\Exception\AuthenticationException&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\Http\Authenticator\Passport\Badge\UserBadge&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\Http\Authenticator\Passport\SelfValidatingPassport&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\Http\EntryPoint\AuthenticationEntryPointInterface&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;Wohali\OAuth2\Client\Provider\DiscordResourceOwner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiscordAuthenticator&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;OAuth2Authenticator&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;AuthenticationEntryPointInterface&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;ClientRegistry&lt;/span&gt; &lt;span class="nv"&gt;$clientRegistry&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;$em&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;RouterInterface&lt;/span&gt; &lt;span class="nv"&gt;$router&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;UserRepository&lt;/span&gt; &lt;span class="nv"&gt;$userRepository&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;start&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="kt"&gt;AuthenticationException&lt;/span&gt; &lt;span class="nv"&gt;$authException&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="kt"&gt;RedirectResponse&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;RedirectResponse&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;router&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"auth_discord_start"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_TEMPORARY_REDIRECT&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;supports&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="kt"&gt;?bool&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;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;attributes&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="s2"&gt;"_route"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"auth_discord_login"&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;authenticate&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="kt"&gt;SelfValidatingPassport&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$client&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;clientRegistry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"discord"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$accessToken&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;fetchAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$client&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;SelfValidatingPassport&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;UserBadge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$accessToken&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="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="cd"&gt;/** @var DiscordResourceOwner $discordUser */&lt;/span&gt;
                &lt;span class="nv"&gt;$discordUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fetchUserFromToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$accessToken&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;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;userRepository&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="s2"&gt;"discordId"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$discordUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&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="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;===&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;$user&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;User&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;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setDiscordId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$discordUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getId&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;em&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;$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;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;em&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;$user&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;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;onAuthenticationSuccess&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="kt"&gt;TokenInterface&lt;/span&gt; &lt;span class="nv"&gt;$token&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;$firewallName&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// redirect to user to your post authentication page (e.g. dashboard, home)&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;onAuthenticationFailure&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="kt"&gt;AuthenticationException&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;?Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// do something&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;Let's make a quick run through what's going on in here :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;start&lt;/code&gt; method defines the &lt;strong&gt;entrypoint&lt;/strong&gt; of the authentication. The goal for us here is to redirect the user to Discord's portal (in other words, start the OAuth2 process). As you can see, it references a route we haven't created yet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;supports&lt;/code&gt; method defined whether the current Request must be supported by our authenticator. This should happen only if the current route is the login route we've created before.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;authenticate&lt;/code&gt; method is the heart of the authenticator. It must return a &lt;a href="https://symfony.com/doc/current/security/custom_authenticator.html#security-passport" rel="noopener noreferrer"&gt;&lt;code&gt;Passport&lt;/code&gt;&lt;/a&gt; that will contain the user to authenticate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;onAuthenticationSuccess&lt;/code&gt; defines our post-authentication process. We mostly want to redirect users to their homepage here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;onAuthenticationFailure&lt;/code&gt; allows you to interact with authentication failures if you need to.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that our authenticator is all set up, let's make Symfony aware that it is in charge of authentication. Let's now open the &lt;code&gt;config/packages/security.yaml&lt;/code&gt; file, and make it look like this :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;security&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;App\Entity\User'&lt;/span&gt;
    &lt;span class="na"&gt;firewalls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^/(_(profiler|wdt)|css|images|js)/&lt;/span&gt;
            &lt;span class="na"&gt;security&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;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;lazy&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users&lt;/span&gt;
            &lt;span class="na"&gt;custom_authenticators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;App\Security\DiscordAuthenticator&lt;/span&gt;
            &lt;span class="na"&gt;logout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_logout&lt;/span&gt;
                &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_index&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Symfony is now aware that you are authenticating users using the &lt;code&gt;User&lt;/code&gt; entity through the &lt;code&gt;App\Security\DiscordAuthenticator&lt;/code&gt; authenticator.&lt;/p&gt;

&lt;p&gt;BUT&lt;/p&gt;

&lt;p&gt;We are not done yet 😄&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the User entity authentication-friendly
&lt;/h3&gt;

&lt;p&gt;Following Symfony's way to build authentication, we now have to make our &lt;code&gt;User&lt;/code&gt; entity implement the &lt;code&gt;UserInterface&lt;/code&gt;. It should now look like this :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="na"&gt;#[ORM\Entity(repositoryClass: UserRepository::class)]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;UserInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Id]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\GeneratedValue]&lt;/span&gt;
    &lt;span class="na"&gt;#[ORM\Column]&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="na"&gt;#[ORM\Column(length: 255)]&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;$discordId&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&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;getRoles&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO: Implement getRoles() method.&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;eraseCredentials&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO: Implement eraseCredentials() method.&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;getUserIdentifier&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="c1"&gt;// TODO: Implement getUserIdentifier() method.&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;&lt;em&gt;This tutorial does not aim to dive into Symfony authentication. If you need more information about the User, &lt;a href="https://symfony.com/doc/current/security.html#the-user" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the entrypoint
&lt;/h3&gt;

&lt;p&gt;Going back to the authenticator, don't forget the entrypoint ! In order to set it up, let's go back to our &lt;code&gt;DiscordController&lt;/code&gt;, and add the &lt;code&gt;auth_discord_start&lt;/code&gt; route like so :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;

&lt;span class="na"&gt;#[Route("/start", name: "start")]&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;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ClientRegistry&lt;/span&gt; &lt;span class="nv"&gt;$clientRegistry&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;RedirectResponse&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;$clientRegistry&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"discord"&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;redirect&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"identify"&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;In this method, we are getting the client called &lt;code&gt;discord&lt;/code&gt; from the registry. The name &lt;code&gt;discord&lt;/code&gt; does not come out of nowhere. Indeed, this is the one we have set earlier in the YAML configuration of the KNP bundle. Don't forget to change the name if you did in the YAML.&lt;/p&gt;

&lt;p&gt;After getting the right client, we call the &lt;code&gt;redirect&lt;/code&gt; method in order to redirect the user to the Discord authentication portal. Note that we set the &lt;a href="https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes" rel="noopener noreferrer"&gt;list of scopes&lt;/a&gt; to &lt;code&gt;[identify]&lt;/code&gt;. It will allow the client to make requests to the &lt;code&gt;/users/@me&lt;/code&gt; URL and gather users' information.&lt;/p&gt;

&lt;p&gt;Now that this is all set up, you can now add a button to the UI that redirects to this URL. Once the user is on this URL, it gets redirected to the Discord authentication portal :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxchnxcls6nbwvuert4ic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxchnxcls6nbwvuert4ic.png" alt="Discord OAuth2 portal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Well yes this is French that's not the point.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After the user clicks the "Autoriser" button, it should be redirected to the &lt;code&gt;/auth/discord/login&lt;/code&gt; URL, with a little bonus in the form of a &lt;code&gt;code&lt;/code&gt; query parameter. This code will be used in order to retrieve the access token, which is done automatically here.&lt;/p&gt;

&lt;p&gt;While testing on our dev environement, after authenticating, we should be able to witness the authentication success using the Symfony Web debug toolbar :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3f063xv6a8xzdvye7g9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3f063xv6a8xzdvye7g9v.png" alt="Symfony web debug toolbar showing successfull authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If that's the case, &lt;strong&gt;good job, our full OAuth2 authentication process is done and successful !&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclude with logging out
&lt;/h2&gt;

&lt;p&gt;Hey ! Hold on a second ! Haven't we forgot something ? No worries, disconnecting users with Symfony is very easy. Indeed, you have already started configuring the way you want to log the user out !&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;config/packages/security.yaml&lt;/code&gt; file, we added that :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;logout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_logout&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_index&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This part defines that our logout route name is &lt;code&gt;app_logout&lt;/code&gt;, and that the user will be redirected to &lt;code&gt;app_index&lt;/code&gt; once logged out.&lt;/p&gt;

&lt;p&gt;All we need to do now is to declare the logout route :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&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;Symfony\Component\HttpKernel\Attribute\AsController&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\Annotation\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[AsController]&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecurityController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Route('/logout', name: 'app_logout')]&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;logout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Logging out'&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;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for those who followed this tutorial all along. I hope that helped you, and that everything is clear. I could have gone deeper at some points, but you know, the tutorial must stay readable !&lt;/p&gt;

&lt;p&gt;More importantly, I hope you got the point of OAuth2 ! In this tutorial, we are using Discord. Thankfully, they have a clean OAuth2 system that can help you get introduced to the protocol. Once you have understood the protocol, it is only a matter of implementation !&lt;/p&gt;

&lt;p&gt;If you need to, go and check out the &lt;a href="https://github.com/FabienLge/symfony-discord-oauth" rel="noopener noreferrer"&gt;Github repository&lt;/a&gt; for this tutorial.&lt;/p&gt;

&lt;p&gt;Thanks a lot again for reading my very first article. If you need more explaination on anything, let's meet in the comment section !&lt;/p&gt;

&lt;p&gt;Happy coding, and don't forget that PHP is not dead !&lt;/p&gt;

</description>
      <category>symfony</category>
      <category>php</category>
      <category>oauth2</category>
      <category>discord</category>
    </item>
  </channel>
</rss>
