<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Gal Malachi's Blog]]></title><description><![CDATA[I’m a passionate software engineer, experienced in building end-to-end applications - architecture, design, and implementation]]></description><link>https://blog.galmalachi.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 18:45:23 GMT</lastBuildDate><atom:link href="https://blog.galmalachi.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Single Sign-On, JWT Authentication, and React]]></title><description><![CDATA[In this series of posts, we create a secured end-to-end JWT-based authentication mechanism using NodeJS, Express, PassportJS, and React.
In this series I cover:

Part 1:  Background and Backend using NodeJS
Part 2:  React & JWT Authentication
Part 3:...]]></description><link>https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-react</link><guid isPermaLink="true">https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-react</guid><category><![CDATA[JWT]]></category><category><![CDATA[React]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Gal Malachi]]></dc:creator><pubDate>Tue, 22 Dec 2020 17:15:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1608569987172/y0xnC8vyb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts, we create a secured end-to-end JWT-based authentication mechanism using NodeJS, Express, PassportJS, and React.</p>
<p>In this series I cover:</p>
<ul>
<li>Part 1:  <a target="_blank" href="https://blog.galmalachi.com/react-nodejs-and-jwt-authentication-the-right-way-part-1-ckelrnovw014f99s15nzh8bg3">Background and Backend using NodeJS</a></li>
<li>Part 2:  <a target="_blank" href="https://blog.galmalachi.com/react-and-jwt-authentication-the-right-way-ckfnzadyi01d00ks18f7aa7ca">React &amp; JWT Authentication</a></li>
<li>Part 3:  <a target="_blank" href="https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-nodejs">Single Sign-On, JWT, and NodeJS</a> </li>
<li>Part 4: Single Sign-On, JWT, and React (This post)</li>
</ul>
<p>In <a target="_blank" href="https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-nodejs">part 3</a>, we enhanced our backend's authentication logic with SSO support. In this last part, we finish our journey by adding client-side support in SSO, using React. </p>
<p>If you haven't read the previous parts, I strongly encourage you to do so before proceeding with this part, as it contains an important background that will help you grasp the concepts we use here.</p>
<h2 id="our-strategy">Our strategy</h2>
<p>As you probably already know, when using SSO, some of the flow takes place on the provider's side. Obviously, we have little to no control there. Although we can't do much about it, we still have full control over our client-side. Therefore, we must do whatever we can to provide our users with a good experience on our end. Ideally, the users shouldn't feel like they've left our application in the process. All of that might sound a little bit tricky, but worry not! We have some tricks up our sleeve 😉</p>
<p>To clarify things up, let's start by describing the steps we follow in this part:</p>
<ol>
<li>The user visits our login or signup page, and chooses to authenticate via SSO</li>
<li>We let our backend know which authentication strategy the user chose by calling the <code>/auth/${providerName}</code> endpoint</li>
<li>When the backend redirects the user to the provider's login page, we open a new popup-like window, for completing the authentication process on the provider's side</li>
<li>Upon successful login, the backend redirects the user back to the UI, with a refresh token cookie. We close the popup and let our app know that we have a new refresh token</li>
<li>We call the <code>/refresh-token</code> endpoint to get a new access token and redirects the user to the homepage of our application</li>
</ol>
<h2 id="lets-code">Let's code</h2>
<h3 id="the-login-component">The login component</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1608570148258/WFhx-A4o5.png" alt="image.png" /></p>
<p>We start by creating a tiny component that renders a button for each provider we support. To make things simple, I hold the <code>supportedSocialLoginTypes</code> in an array on the client-side, but you can (and should!) move that to the backend and expose it by the API, so when adding a new SSO provider, the UI will react to it automatically.</p>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { Box, createStyles, makeStyles, Theme } <span class="hljs-keyword">from</span> <span class="hljs-string">'@material-ui/core'</span>;
<span class="hljs-keyword">import</span> Typography <span class="hljs-keyword">from</span> <span class="hljs-string">'@material-ui/core/Typography'</span>;
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">'@material-ui/core/Button'</span>;
<span class="hljs-keyword">import</span> LinkedInIcon <span class="hljs-keyword">from</span> <span class="hljs-string">'@material-ui/icons/LinkedIn'</span>;
<span class="hljs-keyword">import</span> GitHubIcon <span class="hljs-keyword">from</span> <span class="hljs-string">'@material-ui/icons/GitHub'</span>;
<span class="hljs-keyword">import</span> { openCenteredPopup } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../services/nativePopup'</span>;

<span class="hljs-keyword">const</span> useStyles = makeStyles(<span class="hljs-function">(<span class="hljs-params">theme: Theme</span>) =&gt;</span>
  createStyles({
    <span class="hljs-attr">loginWithText</span>: {
      <span class="hljs-attr">marginBottom</span>: theme.spacing(<span class="hljs-number">4</span>),
    },
    <span class="hljs-attr">socialLoginButton</span>: {
      <span class="hljs-attr">textTransform</span>: <span class="hljs-string">'none'</span>,
      <span class="hljs-attr">marginTop</span>: <span class="hljs-number">10</span>,
    },
  }),
);

<span class="hljs-keyword">const</span> supportedSocialLoginTypes = [
  { <span class="hljs-attr">name</span>: <span class="hljs-string">'LinkedIn'</span>, <span class="hljs-attr">icon</span>: LinkedInIcon },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">'Github'</span>, <span class="hljs-attr">icon</span>: GitHubIcon },
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">LoginWithSSO</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { loginWithText, socialLoginButton } = useStyles();

  <span class="hljs-keyword">const</span> handleSocialLoginSubmit = <span class="hljs-keyword">async</span> (provider: string) =&gt; {
    openCenteredPopup(<span class="hljs-string">`<span class="hljs-subst">${SERVER_URI}</span>/api/auth/<span class="hljs-subst">${provider.toLowerCase()}</span>`</span>, <span class="hljs-string">`login with <span class="hljs-subst">${provider}</span>`</span>, <span class="hljs-number">500</span>, <span class="hljs-number">500</span>);
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Box</span> <span class="hljs-attr">mt</span>=<span class="hljs-string">{2}</span> <span class="hljs-attr">width</span>=<span class="hljs-string">{</span>'<span class="hljs-attr">100</span>%'}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Typography</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{loginWithText}</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"caption"</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"textSecondary"</span> <span class="hljs-attr">align</span>=<span class="hljs-string">"center"</span>&gt;</span>
        Or login with
      <span class="hljs-tag">&lt;/<span class="hljs-name">Typography</span>&gt;</span>
      {supportedSocialLoginTypes.map(({ name, icon: Icon }) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
          <span class="hljs-attr">key</span>=<span class="hljs-string">{name}</span>
          <span class="hljs-attr">startIcon</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Icon</span> /&gt;</span>}
          className={socialLoginButton}
          variant={'outlined'}
          color={'primary'}
          onClick={() =&gt; handleSocialLoginSubmit(name)}
          fullWidth
        &gt;
          {`Login with ${name}`}
        <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">Box</span>&gt;</span></span>
  );
}
</code></pre><h3 id="open-the-providers-login-page-in-a-new-window">Open the provider's login page in a new window</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1608408248205/nQqkFSdv0.png" alt="image.png" /></p>
<p>We need to redirect our user to the provider's website, but we want to give the user the feeling they haven't left our application. Since we can't use a regular popup (e.g a floating, absolute div) for redirection, we mimic this experience by opening a new small and centered window that gives the experience we aim for.</p>
<pre><code><span class="hljs-comment">// credit to: http://www.xtf.dk/2011/08/center-new-popup-window-even-on.html</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> openCenteredPopup = <span class="hljs-function">(<span class="hljs-params">url: <span class="hljs-built_in">string</span>, title: <span class="hljs-built_in">string</span>, w: <span class="hljs-built_in">number</span>, h: <span class="hljs-built_in">number</span></span>) =&gt;</span> {
  <span class="hljs-comment">// Fixes dual-screen position                             Most browsers      Firefox</span>
  <span class="hljs-keyword">const</span> dualScreenLeft = <span class="hljs-built_in">window</span>.screenLeft !== <span class="hljs-literal">undefined</span> ? <span class="hljs-built_in">window</span>.screenLeft : <span class="hljs-built_in">window</span>.screenX;
  <span class="hljs-keyword">const</span> dualScreenTop = <span class="hljs-built_in">window</span>.screenTop !== <span class="hljs-literal">undefined</span> ? <span class="hljs-built_in">window</span>.screenTop : <span class="hljs-built_in">window</span>.screenY;

  <span class="hljs-keyword">const</span> width = <span class="hljs-built_in">window</span>.innerWidth
    ? <span class="hljs-built_in">window</span>.innerWidth
    : <span class="hljs-built_in">document</span>.documentElement.clientWidth
    ? <span class="hljs-built_in">document</span>.documentElement.clientWidth
    : <span class="hljs-built_in">window</span>.screen.width;
  <span class="hljs-keyword">const</span> height = <span class="hljs-built_in">window</span>.innerHeight
    ? <span class="hljs-built_in">window</span>.innerHeight
    : <span class="hljs-built_in">document</span>.documentElement.clientHeight
    ? <span class="hljs-built_in">document</span>.documentElement.clientHeight
    : <span class="hljs-built_in">window</span>.screen.height;

  <span class="hljs-keyword">const</span> systemZoom = width / <span class="hljs-built_in">window</span>.screen.availWidth;
  <span class="hljs-keyword">const</span> left = (width - w) / <span class="hljs-number">2</span> / systemZoom + dualScreenLeft;
  <span class="hljs-keyword">const</span> top = (height - h) / <span class="hljs-number">2</span> / systemZoom + dualScreenTop;
  <span class="hljs-keyword">const</span> newWindow = <span class="hljs-built_in">window</span>.open(
    url,
    title,
    <span class="hljs-string">`
      scrollbars=no,
      toolbar=no,
      location=no,
      directories=no,
      status=no,
      menubar=no,
      resizable=no,
      copyhistory=no,
      width=<span class="hljs-subst">${w / systemZoom}</span>, 
      height=<span class="hljs-subst">${h / systemZoom}</span>, 
      top=<span class="hljs-subst">${top}</span>, 
      left=<span class="hljs-subst">${left}</span>
      `</span>,
  );

  newWindow?.focus();
};
</code></pre><h3 id="redirecting-back-to-our-app">Redirecting back to our app</h3>
<p>Let's start by creating a function that we will use to communicate between the two windows. The <code>authenticateCallback</code> will be called by the popup window right after the authentication process is completed, and right before it disappears. We declare it in the <code>useEffect</code> of our <code>AuthContainer</code> from  <a target="_blank" href="https://blog.galmalachi.com/react-and-jwt-authentication-the-right-way/#the-authentication-container">part 2</a>:</p>
<pre><code><span class="hljs-comment">// AuthContainer.ts</span>

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// add listener for login or logout from other tabs</span>
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'storage'</span>, <span class="hljs-keyword">async</span> (event: WindowEventMap[<span class="hljs-string">'storage'</span>]) =&gt; {
      <span class="hljs-keyword">if</span> (event.key === AuthEvents.LOGOUT &amp;&amp; isAuthenticated()) {
        <span class="hljs-keyword">await</span> clearToken(<span class="hljs-literal">false</span>);
        setUser(<span class="hljs-literal">null</span>);
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === AuthEvents.LOGIN) {
        refreshToken();
      }
    });

    <span class="hljs-comment">// add listener for cross-window communication (SSO popup)</span>
    <span class="hljs-built_in">window</span>.authenticateCallback = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
      <span class="hljs-keyword">await</span> refreshToken();
      history.push(<span class="hljs-string">'/'</span>);
    };
  }, [clearToken, history, isAuthenticated, refreshToken]);
</code></pre><p>When the user completes the authentication on the provider's UI, the provider will redirect it back to our backend. In part 3, <a target="_blank" href="https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-nodejs/#express-routes-for-sso-authentication">we configured</a> the backend to send the authenticated user (+ a refresh token) to the specific route on our frontend: <code>/authentication/redirect</code>.
Recall that at this stage, the user is still in the popup window, therefore, the redirection occurs there! </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1608472418989/6Kfj0-vI2.png" alt="image.png" /></p>
<p>Let's create a small component that will render when this route is accessed; its role is to pass the control back to the caller window, by closing the popup, and notifying that a new refresh token is available by calling the <code>authenticateCallback</code> function we attached to the  <code>window</code> object earlier. Notice that we reference the main window using <code>window.opener</code>.</p>
<p><strong>Remember</strong>: cookies are available on a domain basis, rather than a particular window. We can be sure that the new refresh token is sent with each new call we make, across all open windows that share the same domain, including our caller window.</p>
<pre><code><span class="hljs-regexp">//</span> SocialAuthCallback.tsx

<span class="hljs-keyword">import</span> React, { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> const SocialAuthCallback = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
  useEffect(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
      <span class="hljs-built_in">window</span>.opener.authenticateCallback();
      <span class="hljs-built_in">window</span>.close();
    }, <span class="hljs-number">1000</span>);
  });

  <span class="hljs-keyword">return</span> &lt;div&gt;Authenticated successfully! you are being redirected...&lt;/div&gt;;
};
</code></pre><pre><code>     <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/authentication/redirect"</span> <span class="hljs-attr">exact</span> <span class="hljs-attr">component</span>=<span class="hljs-string">{SocialAuthCallback}</span> /&gt;</span>
      // .... rest of applications routes here
</code></pre><p>Yes! we are done! At this point, the main window fetches a new access token for the user and redirects them back to the homepage. Easy right?</p>
<h3 id="closing-words">Closing words</h3>
<p>That's all for this series. We have covered a lot! If you follow the steps in the series, you should have a working JWT-based authentication in no time!
That was a long and exciting journey for me. I hope you enjoyed reading it at least as much I enjoyed writing it. Please let me know what you think by leaving a comment down below.</p>
]]></content:encoded></item><item><title><![CDATA[Single Sign-On, JWT Authentication, and NodeJS]]></title><description><![CDATA[In this series of posts, we create a secured end-to-end JWT-based authentication mechanism using NodeJS, Express, PassportJS and React.
In this series I cover:

Part 1:  Background and Backend using NodeJS
Part 2:  React & JWT Authentication
Part 3: ...]]></description><link>https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-nodejs</link><guid isPermaLink="true">https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-nodejs</guid><category><![CDATA[JWT]]></category><category><![CDATA[authentication]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Gal Malachi]]></dc:creator><pubDate>Tue, 08 Dec 2020 17:57:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1607450229897/eccqjTWwo.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts, we create a secured end-to-end JWT-based authentication mechanism using NodeJS, Express, PassportJS and React.</p>
<p>In this series I cover:</p>
<ul>
<li>Part 1:  <a target="_blank" href="https://blog.galmalachi.com/react-nodejs-and-jwt-authentication-the-right-way-part-1-ckelrnovw014f99s15nzh8bg3">Background and Backend using NodeJS</a></li>
<li>Part 2:  <a target="_blank" href="https://blog.galmalachi.com/react-and-jwt-authentication-the-right-way-ckfnzadyi01d00ks18f7aa7ca">React &amp; JWT Authentication</a></li>
<li>Part 3: Single Sign-On, JWT, and NodeJS <strong>(This post)</strong></li>
<li>Part 4:  <a target="_blank" href="https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-react">Single Sign-On, JWT, and React</a></li>
</ul>
<p>In this part, we will use what we've accomplished so far and turn it into a complete solution by adding support in multiple SSO strategies using NodeJS, Express, PassportJS.
If you haven't read the previous parts, I strongly encourage you to do so before proceeding with this part, as it contains an important background that will help you grasp the concepts we use here.</p>
<h2 id="what-is-a-single-sign-on-authentication">What is a Single Sign-On Authentication?</h2>
<p>We all use Single Sign-On authentication every day. When we subscribe to a new website for the first time, there are usually several ways to choose how to complete the registration process: </p>
<ul>
<li><strong>Username &amp; password</strong> - we provide our credentials and a new user that is associated with our email address is created for us</li>
<li><strong>”Sign up with ...”</strong> - we use an existing account we own (Google, Facebook, Twitter, Github, Apple, etc)</li>
</ul>
<p>Whenever we chose the second option, we are redirected to the selected provider, which asks our permission to share our data with the service we came from. This is called Single Sign-On (SSO). Using SSO, we can log into a website without entering a username and password.</p>
<p>I'm not going to dig into the protocol itself in this post, as there's plenty of information available online, but I do encourage you to learn it yourself. Here's a nice post explaining  <a target="_blank" href="https://abhishek-patel.medium.com/what-why-and-how-of-single-sign-on-sso-7d3bcf44cb75">SSO in more depth</a>.</p>
<h2 id="wait-a-minute">Wait a minute...</h2>
<blockquote>
<p>"But... how can it work given that we have to rely on a third party to manage our user's authentication, and the JWT is being generated by our backend?"</p>
</blockquote>
<p>Great question! This is exactly what this entire post is all about!
Our strategy will be as follows:</p>
<ol>
<li>Configure PassportJS strategies for each SSO provider we support</li>
<li>Let the user authenticate using their provider of choice</li>
<li>Upon successful login, update the user in the DB, generate a refresh token, attach it to a cookie and redirect the user back to the application</li>
<li>Use the refresh token to generate an access token for the user</li>
</ol>
<h2 id="prerequisites">Prerequisites</h2>
<p>Each provider we support requires registration using a dedicated developer account. When you complete the registration process, you will get a <code>Client ID</code> and a <code>Secret ID</code>. Make sure to save them and keep them safe, we will need them later. </p>
<p>Follow the links below to create and configure your SSO access with the two providers we use in this post:</p>
<ul>
<li><a target="_blank" href="https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps">Github</a> </li>
<li><a target="_blank" href="https://www.linkedin.com/developers">LinkedIn</a> </li>
</ul>
<p>And now, without further ado...</p>
<h2 id="lets-code">Let's Code</h2>
<h3 id="the-app-configuration">The app configuration</h3>
<p>Let's start by adding our providers to the backend's config. I'm using <a target="_blank" href="https://github.com/mozilla/node-convict/tree/master/packages/convict">convict</a> to manage the configuration, but you can replace it with whatever you are comfortable with. 
<code>convict</code> pulls the sensitive data (e.g <code>Client ID</code>, <code>Secret ID</code>) from the application's <code>.env</code> file. </p>
<pre><code><span class="hljs-string">//</span> <span class="hljs-string">src/config/config.ts</span>

<span class="hljs-string">import</span> <span class="hljs-string">convict</span> <span class="hljs-string">from</span> <span class="hljs-string">'convict'</span><span class="hljs-string">;</span>

<span class="hljs-string">export</span> <span class="hljs-string">default</span> <span class="hljs-string">convict({</span>
  <span class="hljs-attr">env:</span> {
    <span class="hljs-attr">default:</span> <span class="hljs-string">'dev'</span>,
    <span class="hljs-attr">env:</span> <span class="hljs-string">'NODE_ENV'</span>,
  }<span class="hljs-string">,</span>
  <span class="hljs-attr">http:</span> {
    <span class="hljs-attr">port:</span> {
      <span class="hljs-attr">doc:</span> <span class="hljs-string">'The port to listen on'</span>,
      <span class="hljs-attr">default:</span> <span class="hljs-number">3001</span>,
      <span class="hljs-attr">env:</span> <span class="hljs-string">'PORT'</span>,
    },
    <span class="hljs-attr">host:</span> {
      <span class="hljs-attr">default:</span> <span class="hljs-string">'http://localhost'</span>,
      <span class="hljs-attr">env:</span> <span class="hljs-string">'HOST'</span>,
    },
  }<span class="hljs-string">,</span>
  <span class="hljs-attr">authentication:</span> {
    <span class="hljs-attr">linkedin:</span> {
      <span class="hljs-attr">clientID:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'The Client ID from Google to use for authentication'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">''</span>,
        <span class="hljs-attr">env:</span> <span class="hljs-string">'LINKEDIN_CLIENT_ID'</span>,
      },
      <span class="hljs-attr">clientSecret:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'The Client Secret from Google to use for authentication'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">''</span>,
        <span class="hljs-attr">env:</span> <span class="hljs-string">'LINKEDIN_SECRET'</span>,
      },
      <span class="hljs-attr">scope:</span> {
        <span class="hljs-attr">default:</span> [<span class="hljs-string">'r_emailaddress'</span>, <span class="hljs-string">'r_liteprofile'</span>],
      },
    },
    <span class="hljs-attr">github:</span> {
      <span class="hljs-attr">clientID:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'The Client ID from Github to use for authentication'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">''</span>,
        <span class="hljs-attr">env:</span> <span class="hljs-string">'GITHUB_CLIENT_ID'</span>,
      },
      <span class="hljs-attr">clientSecret:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'The Client Secret from Github to use for authentication'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">''</span>,
        <span class="hljs-attr">env:</span> <span class="hljs-string">'GITHUB_SECRET'</span>,
      },
      <span class="hljs-attr">scope:</span> {
        <span class="hljs-attr">default:</span> [],
      },
    },
    <span class="hljs-attr">token:</span> {
      <span class="hljs-attr">secret:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'The signing key for the JWT'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">'mySuperSecretKey'</span>,
        <span class="hljs-attr">env:</span> <span class="hljs-string">'JWT_SECRET'</span>,
      },
      <span class="hljs-attr">issuer:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'The issuer for the JWT'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">''</span>,
      },
      <span class="hljs-attr">audience:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'The audience for the JWT'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">''</span>,
      },
      <span class="hljs-attr">expiresIn:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'expressed in seconds or a string describing a time span zeit/ms.'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">'24h'</span>,
        <span class="hljs-attr">env:</span> <span class="hljs-string">'JWT_EXPIRES_IN'</span>,
      },
    },
    <span class="hljs-attr">refreshToken:</span> {
      <span class="hljs-attr">expiresIn:</span> {
        <span class="hljs-attr">doc:</span> <span class="hljs-string">'expressed in seconds or a string describing a time span zeit/ms.'</span>,
        <span class="hljs-attr">default:</span> <span class="hljs-string">'24h'</span>,
        <span class="hljs-attr">env:</span> <span class="hljs-string">'REFRESH_JWT_EXPIRES_IN'</span>,
      },
    },
  }<span class="hljs-string">,</span>
<span class="hljs-string">}).validate();</span>
</code></pre><h3 id="authentication-logic-using-passportjs">Authentication logic using PassportJS</h3>
<p>As you already know, we use PassportJS to declare the authentication strategies of our application. In part 1,  <a target="_blank" href="https://blog.galmalachi.com/react-nodejs-and-jwt-authentication-the-right-way-part-1-ckelrnovw014f99s15nzh8bg3/#user-authentication-with-passportjs">we created our first passport strategy - the JWT strategy</a>.</p>
<p>PassportJS supports  <a target="_blank" href="http://www.passportjs.org/packages/">multiple authentication strategies</a> out of the box. Let's add two new strategies for Github and Linkedin using <code>passport-github2</code>and <code>passport-linkedin-oauth2</code> respectively.</p>
<pre><code><span class="hljs-comment">// src/auth/providers/github.ts</span>

<span class="hljs-keyword">import</span> { Strategy <span class="hljs-keyword">as</span> GithubStrategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-github2'</span>;
<span class="hljs-keyword">import</span> { getConfigByProviderName, processUserFromSSO } <span class="hljs-keyword">from</span> <span class="hljs-string">'../index'</span>;
<span class="hljs-keyword">import</span> passport <span class="hljs-keyword">from</span> <span class="hljs-string">'passport'</span>;
<span class="hljs-keyword">import</span> { VerifiedCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-jwt'</span>;
<span class="hljs-keyword">import</span> { Request } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;

<span class="hljs-keyword">const</span> providerName = <span class="hljs-string">'github'</span>;
<span class="hljs-keyword">const</span> passportConfig = getConfigByProviderName(providerName);

<span class="hljs-keyword">if</span> (passportConfig.clientID) {
  passport.use(
    <span class="hljs-keyword">new</span> GithubStrategy(
      { ...passportConfig, passReqToCallback: <span class="hljs-literal">true</span> },
      <span class="hljs-function">(<span class="hljs-params">req: Request, accessToken: <span class="hljs-built_in">string</span>, refreshToken: <span class="hljs-built_in">string</span>, profile: <span class="hljs-built_in">any</span>, verified: VerifiedCallback</span>) =&gt;</span> {
        processUserFromSSO(req, profile, providerName, verified);
      },
    ),
  );
}
</code></pre><pre><code><span class="hljs-comment">// src/auth/providers/linkedin.ts</span>

<span class="hljs-keyword">import</span> { Strategy <span class="hljs-keyword">as</span> LinkedInStrategy } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-linkedin-oauth2'</span>;
<span class="hljs-keyword">import</span> { getConfigByProviderName, processUserFromSSO } <span class="hljs-keyword">from</span> <span class="hljs-string">'../index'</span>;
<span class="hljs-keyword">import</span> passport <span class="hljs-keyword">from</span> <span class="hljs-string">'passport'</span>;
<span class="hljs-keyword">import</span> { VerifiedCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-jwt'</span>;
<span class="hljs-keyword">import</span> { Request } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;

<span class="hljs-keyword">const</span> providerName = <span class="hljs-string">'linkedin'</span>;
<span class="hljs-keyword">const</span> passportConfig = getConfigByProviderName(providerName);

<span class="hljs-keyword">if</span> (passportConfig.clientID) {
  passport.use(
    <span class="hljs-keyword">new</span> LinkedInStrategy(
      { ...passportConfig, passReqToCallback: <span class="hljs-literal">true</span> },
      <span class="hljs-function">(<span class="hljs-params">req: Request, accessToken: <span class="hljs-built_in">string</span>, refreshToken: <span class="hljs-built_in">string</span>, profile: <span class="hljs-built_in">any</span>, verified: VerifiedCallback</span>) =&gt;</span> {
        processUserFromSSO(req, profile, providerName, verified);
      },
    ),
  );
}
</code></pre><p>You probably noticed that we use the <code>processUserFromSSO</code> function in both providers. After a successful login, the SSO provider exposes an <code>accessToken</code>, a <code>refreshToken</code>, and a limited <code>profile</code> of the logged-in user. We are not interested in the first two, as we manage the access and refresh tokens ourselves. However, we still need the information from the profile, to identify the user in our system. Once we have that, we can pull the corresponding user object from our database and attach it to the request. If we don't have a match in our DB, it means that this is the user's first time logging in to our system, so we create one for them.</p>
<p><strong>Note</strong>: To simplify things, I chose to create a new user object in the DB for each provider, by searching for the user by the <code>origin</code> and the <code>originId</code>. Ideally, to provide a better user experience, we would create one user object to identify an individual across all providers (if the email matches).</p>
<p>Let's rearrange our code from the previous parts, and create the <code>processUserFromSSO</code> function along with more useful utils.</p>
<pre><code><span class="hljs-comment">// src/auth/index.ts</span>

<span class="hljs-keyword">import</span> { User, UserModel } <span class="hljs-keyword">from</span> <span class="hljs-string">'../models/user'</span>;
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;
<span class="hljs-keyword">import</span> { removeExtensionFromFile } <span class="hljs-keyword">from</span> <span class="hljs-string">'../middleware/utils'</span>;
<span class="hljs-keyword">import</span> { VerifiedCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-jwt'</span>;
<span class="hljs-keyword">import</span> config <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/config'</span>;
<span class="hljs-keyword">import</span> passport <span class="hljs-keyword">from</span> <span class="hljs-string">'passport'</span>;
<span class="hljs-keyword">import</span> { Request } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { join } <span class="hljs-keyword">from</span> <span class="hljs-string">'path'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> init = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> providersPath = join(__dirname, <span class="hljs-string">'providers'</span>);
  fs.readdirSync(providersPath).forEach(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> authFile = removeExtensionFromFile(file);
    <span class="hljs-keyword">import</span>(join(providersPath, authFile));
  });
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> requireAuth = passport.authenticate(<span class="hljs-string">'jwt'</span>, {
  userProperty: <span class="hljs-string">'currentUser'</span>,
  session: <span class="hljs-literal">false</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getConfigByProviderName = <span class="hljs-function">(<span class="hljs-params">providerName: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> {
    clientID: config.get(<span class="hljs-string">`authentication.<span class="hljs-subst">${providerName}</span>.clientID`</span>),
    clientSecret: config.get(<span class="hljs-string">`authentication.<span class="hljs-subst">${providerName}</span>.clientSecret`</span>),
    scope: config.get(<span class="hljs-string">`authentication.<span class="hljs-subst">${providerName}</span>.scope`</span>),
    callbackURL: getAuthCallbackUrl(providerName),
  };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> processUserFromSSO = <span class="hljs-function">(<span class="hljs-params">req: Request, profile: <span class="hljs-built_in">any</span>, origin: <span class="hljs-built_in">string</span>, done: VerifiedCallback</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> {
    emails,
    name: { givenName, familyName },
    id,
  } = profile;

  UserModel.findOneAndUpdate(
    { origin, originId: id },
    {
      email: emails?.[<span class="hljs-number">0</span>]?.value,
      firstName: givenName,
      lastName: familyName,
      origin,
      originId: id,
    },
    { upsert: <span class="hljs-literal">true</span> },
    <span class="hljs-function">(<span class="hljs-params">err, user</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (err) {
        <span class="hljs-keyword">return</span> done(err);
      }
      req.currentUser = user <span class="hljs-keyword">as</span> User;
      <span class="hljs-keyword">return</span> done(<span class="hljs-literal">null</span>, user);
    },
  );
};

<span class="hljs-keyword">const</span> getAuthCallbackUrl = <span class="hljs-function">(<span class="hljs-params">providerName: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${config.get(<span class="hljs-string">'http.host'</span>)}</span>:<span class="hljs-subst">${config.get(<span class="hljs-string">'http.port'</span>)}</span>/api/auth/<span class="hljs-subst">${providerName}</span>/callback`</span>;
};
</code></pre><h3 id="express-routes-for-sso-authentication">Express routes for SSO authentication</h3>
<p>To wrap things up, we loop through the providers we configured and add two routes for each one of them:</p>
<ul>
<li><code>/auth/${providerName}</code> - initiates the authentication process. The UI calls this API to let the backend know that the user chose to authenticate using SSO. The backend will reach the relevant provider to initiate the process and will redirect the user to the provider's login page</li>
<li><code>/auth/${providerName}/callback</code> - the provider uses this route to redirect the user back to the application once the authentication process is completed. At this point, the user is authenticated, and we need to attach the access token. Since we can't redirect the user with a token in a secured way, we attach the refresh token to a cookie that will be used by the UI to fetch the access token</li>
</ul>
<pre><code><span class="hljs-comment">// src/routes/auth.ts</span>

<span class="hljs-keyword">import</span> express, { Request, Response } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">const</span> router = express.Router();
<span class="hljs-keyword">import</span> passport <span class="hljs-keyword">from</span> <span class="hljs-string">'passport'</span>;
<span class="hljs-keyword">import</span> config <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/config'</span>;
<span class="hljs-keyword">import</span> { generateRefreshToken } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/token-util'</span>;

<span class="hljs-comment">// local-jwt login</span>
<span class="hljs-comment">// .. rest of the routes from part 1 are here</span>

<span class="hljs-comment">// social-jwt login</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> generateUserTokenAndRedirect = <span class="hljs-keyword">async</span> (req: Request, <span class="hljs-attr">res</span>: Response) =&gt; {
  <span class="hljs-keyword">const</span> successRedirect = <span class="hljs-string">`<span class="hljs-subst">${process.env.FRONTEND_URL}</span>/authentication/redirect`</span>;
  <span class="hljs-keyword">const</span> { token } = generateRefreshToken(req.currentUser?._id.toString());
  res.cookie(<span class="hljs-string">'refresh_token'</span>, token, { <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span> });
  res.redirect(successRedirect);
};

<span class="hljs-comment">// loop over the available authentication providers and add a route for them</span>
<span class="hljs-built_in">Object</span>.keys(config.get(<span class="hljs-string">'authentication'</span>) || {}).forEach(<span class="hljs-function">(<span class="hljs-params">providerName</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> failureRedirect = <span class="hljs-string">`<span class="hljs-subst">${process.env.FRONTEND_URL}</span>"`</span>;
  <span class="hljs-keyword">const</span> providerAuthMiddleware = passport.authenticate(providerName, {
    <span class="hljs-attr">session</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">userProperty</span>: <span class="hljs-string">'currentUser'</span>,
    failureRedirect,
  });
  router.get(<span class="hljs-string">`/auth/<span class="hljs-subst">${providerName}</span>`</span>, providerAuthMiddleware);
  router.get(<span class="hljs-string">`/auth/<span class="hljs-subst">${providerName}</span>/callback`</span>, providerAuthMiddleware, generateUserTokenAndRedirect);
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router;
</code></pre><p>That's all for this part. In the next part, we'll complete our journey by adding client-side support in SSO using the infrastructure we created so far. I hope you find this post helpful, let me know what you think!</p>
]]></content:encoded></item><item><title><![CDATA[React & JWT Authentication - the right way!]]></title><description><![CDATA[In this series of posts, we create a secured end-to-end JWT-based authentication mechanism using NodeJS, Express, PassportJS, and React.
In this series I cover:

Part 1:  Background and Backend using NodeJS 
Part 2: React & JWT Authentication (This p...]]></description><link>https://blog.galmalachi.com/react-and-jwt-authentication-the-right-way</link><guid isPermaLink="true">https://blog.galmalachi.com/react-and-jwt-authentication-the-right-way</guid><category><![CDATA[React]]></category><category><![CDATA[authentication]]></category><category><![CDATA[JWT]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Gal Malachi]]></dc:creator><pubDate>Tue, 29 Sep 2020 13:10:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1601303731581/wqZy-1kzK.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts, we create a secured end-to-end JWT-based authentication mechanism using NodeJS, Express, PassportJS, and React.</p>
<p>In this series I cover:</p>
<ul>
<li>Part 1:  <a target="_blank" href="https://blog.galmalachi.com/react-nodejs-and-jwt-authentication-the-right-way-part-1-ckelrnovw014f99s15nzh8bg3">Background and Backend using NodeJS</a> </li>
<li>Part 2: React &amp; JWT Authentication <strong>(This post)</strong></li>
<li>Part 3:  <a target="_blank" href="https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-nodejs">Single Sign-On, JWT, and NodeJS</a> </li>
<li>Part 4:  <a target="_blank" href="https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-react">Single Sign-On, JWT, and React</a></li>
</ul>
<p>In this part, we focus on the client-side.
If you haven't read <a target="_blank" href="https://blog.galmalachi.com/react-nodejs-and-jwt-authentication-the-right-way-part-1-ckelrnovw014f99s15nzh8bg3">Part 1</a>, I strongly encourage you to do so before proceeding with this part, as it contains an important background that will help you grasp the concepts we use here.</p>
<h2 id="what-we-want-to-achieve">What we want to achieve</h2>
<ul>
<li><strong>A secured mechanism</strong> - we follow the rules described in the first part: access token is not stored in the local storage; utilize refresh tokens instead</li>
<li><strong>User (and developer) friendly</strong> - automatic login &amp; logout, multi-tabs support, automatic token refresh</li>
<li><strong>State management</strong> - our app should know whether a user is authenticated</li>
</ul>
<p>Let's start!</p>
<h2 id="state-management-unstated-next">State Management: unstated-next</h2>
<p>I really like Context API. It's a simple, integral part of React, and it works great for my use-cases.  <a target="_blank" href="https://github.com/jamiebuilds/unstated-next">unstated-next</a> is a tiny wrapper on top, that makes the use of Context API and hooks even better. You can, of course, use whatever state management solution you like (Redux 🙄, Mobx, etc). The concept remains the same.</p>
<h2 id="http-client-axios-axios-hooks">HTTP Client: axios, axios-hooks</h2>
<p>Axios is a really popular, open-source HTTP client for node and the browser. Axios has built-in support for request interceptors, which come handy when passing authorization headers.
Since we use React hooks, we will add hooks support by integrating axios-hooks. If you don't like Axios, there are other great options out there (e.g <a target="_blank" href="https://github.com/vercel/swr">SWR</a>).</p>
<h2 id="the-usetokenexpiration-hook">The <code>useTokenExpiration</code> hook</h2>
<p>Let's start at the end. Since we use refresh tokens, we benefit from an increase in the level of the app's security by generating short-living access tokens. If access tokens expire often, we should refresh them to keep the users logged in. To achieve that, we set a timeout for each new access token we receive, and call the <code>/refresh-token</code> endpoint to receive a new one when it expires.</p>
<p>The <code>useTokenExpiration</code> gets the current token's expiration time, counts down until it expires, and lets the app know that a refresh is required, by calling the <code>onTokenRefreshRequired</code> callback. </p>
<pre><code><span class="hljs-comment">// useTokenExpiration.ts</span>

<span class="hljs-keyword">import</span> { useEffect, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useTokenExpiration</span>(<span class="hljs-params">onTokenRefreshRequired: <span class="hljs-built_in">Function</span></span>) </span>{
  <span class="hljs-keyword">const</span> clearAutomaticRefresh = useRef&lt;<span class="hljs-built_in">number</span>&gt;();
  <span class="hljs-keyword">const</span> [tokenExpiration, setTokenExpiration] = useState&lt;<span class="hljs-built_in">Date</span>&gt;();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// get a new access token with the refresh token when it expires</span>
    <span class="hljs-keyword">if</span> (tokenExpiration <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Date</span> &amp;&amp; !<span class="hljs-built_in">isNaN</span>(tokenExpiration.valueOf())) {
      <span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();
      <span class="hljs-keyword">const</span> triggerAfterMs = tokenExpiration.getTime() - now.getTime();

      clearAutomaticRefresh.current = <span class="hljs-built_in">window</span>.setTimeout(<span class="hljs-keyword">async</span> () =&gt; {
        onTokenRefreshRequired();
      }, triggerAfterMs);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">window</span>.clearTimeout(clearAutomaticRefresh.current);
    };
  }, [onTokenRefreshRequired, tokenExpiration]);

  <span class="hljs-keyword">const</span> clearAutomaticTokenRefresh = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">window</span>.clearTimeout(clearAutomaticRefresh.current);
    setTokenExpiration(<span class="hljs-literal">undefined</span>);
  };

  <span class="hljs-keyword">return</span> {
    clearAutomaticTokenRefresh,
    setTokenExpiration,
  };
}
</code></pre><h2 id="the-usetoken-hook">The <code>useToken</code> hook</h2>
<p>This is the heart of the authentication mechanism. It is responsible for the entire token management and lifecycle.</p>
<h3 id="the-token-lifecycle">The token lifecycle</h3>
<p>The <code>useToken</code> hook holds the access token in-memory (we explained why it's important, remember?). Therefore we store the access token in a <code>useRef</code> hook - more on that soon. The hook is also responsible for managing the entire token lifecycle: initialization, removal, expiration, and refresh. The token expiration is handled by using the <code>useTokenExpiration</code> hook we created.</p>
<p>A word about clearing the token - since the refresh token is saved in an <code>httpOnly</code> cookie, we can't access it or modify it from the browser using Javascript. This raises a problem: we can't really complete the action on the client-side alone as the browser will keep sending the refresh token cookie and a new access token will be received on every refresh. To solve that, we call the <code>/logout</code> endpoint, which returns a new empty cookie, and in practice, deletes the refresh token from the browser.</p>
<p>Let's create the lifecycle methods:</p>
<pre><code><span class="hljs-comment">// useToken.ts</span>

<span class="hljs-comment">// .... </span>

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useToken</span>(<span class="hljs-params">onTokenInvalid: <span class="hljs-built_in">Function</span>, onRefreshRequired: <span class="hljs-built_in">Function</span></span>) </span>{
 <span class="hljs-keyword">const</span> accessToken = useRef&lt;<span class="hljs-built_in">string</span>&gt;();
  <span class="hljs-keyword">const</span> { clearAutomaticTokenRefresh, setTokenExpiration } = useTokenExpiration(onRefreshRequired);

  <span class="hljs-keyword">const</span> setToken = useCallback(
    <span class="hljs-function">(<span class="hljs-params">{ token_expiration, access_token }: TokenResponse</span>) =&gt;</span> {
      accessToken.current = access_token;
      <span class="hljs-keyword">const</span> expirationDate = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(token_expiration);
      setTokenExpiration(expirationDate);
    },
    [setTokenExpiration],
  );

  <span class="hljs-keyword">const</span> isAuthenticated = useCallback(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> !!accessToken.current;
  }, []);

  <span class="hljs-keyword">const</span> clearToken = useCallback(
    <span class="hljs-function">(<span class="hljs-params">shouldClearCookie = <span class="hljs-literal">true</span></span>) =&gt;</span> {
      <span class="hljs-comment">// if we came from a different tab, we should not clear the cookie again</span>
      <span class="hljs-keyword">const</span> clearRefreshTokenCookie = shouldClearCookie ? axios.get(<span class="hljs-string">'logout'</span>) : <span class="hljs-built_in">Promise</span>.resolve();

      <span class="hljs-comment">// clear refresh token</span>
      <span class="hljs-keyword">return</span> clearRefreshTokenCookie.finally(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">// clear token</span>
        accessToken.current = <span class="hljs-string">''</span>;

        <span class="hljs-comment">// clear auto refresh interval</span>
        clearAutomaticTokenRefresh();
      });
    },
    [clearAutomaticTokenRefresh],
  );

  <span class="hljs-keyword">return</span> {
    clearToken,
    setToken,
    isAuthenticated,
  };
}
</code></pre><h3 id="initiating-axios-and-setting-interceptors">Initiating Axios and setting interceptors</h3>
<p>Once we have the token, we want to pass it to all of the requests automatically. For that we use interceptors. Interceptors are pieces of code that run before and after we fire a request and allow us to intervene in the process. Since we should do the initiating process just once on the application startup, we store the access token in a <code>useRef</code> hook. By doing that, the <code>useEffect</code> containing the definitions of our interceptors will not redefine them over and over again when the access token changes.</p>
<p>The first interceptor attaches the access token to the authorization header before we send it to the backend. The second interceptor handles the <code>401</code> response from the backend by clearing the token's data and notifying the app that the current user isn't logged in anymore.</p>
<pre><code><span class="hljs-comment">// useToken.ts</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> axios = Axios.create({
  baseURL: BASE_URL,
});

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useToken</span>(<span class="hljs-params">onTokenInvalid: <span class="hljs-built_in">Function</span>, onRefreshRequired: <span class="hljs-built_in">Function</span></span>) </span>{
<span class="hljs-comment">// .....</span>

    useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// add authorization token to each request</span>
    axios.interceptors.request.use(
      (config: AxiosRequestConfig): <span class="hljs-function"><span class="hljs-params">AxiosRequestConfig</span> =&gt;</span> {
        config.headers.authorization = <span class="hljs-string">`Bearer <span class="hljs-subst">${accessToken.current}</span>`</span>;
        <span class="hljs-keyword">return</span> config;
      },
    );

    <span class="hljs-comment">// if the current token is expired or invalid, logout the user</span>
    axios.interceptors.response.use(
      <span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response,
      <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (error.response.status === <span class="hljs-number">401</span> &amp;&amp; accessToken.current) {
          clearToken();

          <span class="hljs-comment">// let the app know that the current token was cleared</span>
          onTokenInvalid();
        }
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.reject(error);
      },
    );

    <span class="hljs-comment">// configure axios-hooks to use this instance of axios</span>
    configure({ axios });
  }, [clearToken, onTokenInvalid]);
</code></pre><p>And finally, the entire hook:</p>
<pre><code><span class="hljs-comment">// useToken.ts</span>

<span class="hljs-keyword">import</span> Axios, { AxiosRequestConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
<span class="hljs-keyword">import</span> { useCallback, useEffect, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { configure } <span class="hljs-keyword">from</span> <span class="hljs-string">'axios-hooks'</span>;
<span class="hljs-keyword">import</span> { BASE_URL } <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/config'</span>;
<span class="hljs-keyword">import</span> { User } <span class="hljs-keyword">from</span> <span class="hljs-string">'../Containers/AuthContainer'</span>;
<span class="hljs-keyword">import</span> { useTokenExpiration } <span class="hljs-keyword">from</span> <span class="hljs-string">'./useTokenExpiration'</span>;

<span class="hljs-keyword">interface</span> TokenResponse {
  access_token: <span class="hljs-built_in">string</span>;
  token_expiration: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> UserAndTokenResponse <span class="hljs-keyword">extends</span> TokenResponse {
  user: User;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> axios = Axios.create({
  baseURL: BASE_URL,
});

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useToken</span>(<span class="hljs-params">onTokenInvalid: <span class="hljs-built_in">Function</span>, onRefreshRequired: <span class="hljs-built_in">Function</span></span>) </span>{
  <span class="hljs-keyword">const</span> accessToken = useRef&lt;<span class="hljs-built_in">string</span>&gt;();
  <span class="hljs-keyword">const</span> { clearAutomaticTokenRefresh, setTokenExpiration } = useTokenExpiration(onRefreshRequired);

  <span class="hljs-keyword">const</span> setToken = useCallback(
    <span class="hljs-function">(<span class="hljs-params">{ token_expiration, access_token }: TokenResponse</span>) =&gt;</span> {
      accessToken.current = access_token;
      <span class="hljs-keyword">const</span> expirationDate = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(token_expiration);
      setTokenExpiration(expirationDate);
    },
    [setTokenExpiration],
  );

  <span class="hljs-keyword">const</span> isAuthenticated = useCallback(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> !!accessToken.current;
  }, []);

  <span class="hljs-keyword">const</span> clearToken = useCallback(
    <span class="hljs-function">(<span class="hljs-params">shouldClearCookie = <span class="hljs-literal">true</span></span>) =&gt;</span> {
      <span class="hljs-comment">// if we came from a different tab, we should not clear the cookie again</span>
      <span class="hljs-keyword">const</span> clearRefreshTokenCookie = shouldClearCookie ? axios.get(<span class="hljs-string">'logout'</span>) : <span class="hljs-built_in">Promise</span>.resolve();

      <span class="hljs-comment">// clear refresh token</span>
      <span class="hljs-keyword">return</span> clearRefreshTokenCookie.finally(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-comment">// clear token</span>
        accessToken.current = <span class="hljs-string">''</span>;

        <span class="hljs-comment">// clear auto refresh interval</span>
        clearAutomaticTokenRefresh();
      });
    },
    [clearAutomaticTokenRefresh],
  );

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// add authorization token to each request</span>
    axios.interceptors.request.use(
      (config: AxiosRequestConfig): <span class="hljs-function"><span class="hljs-params">AxiosRequestConfig</span> =&gt;</span> {
        config.headers.authorization = <span class="hljs-string">`Bearer <span class="hljs-subst">${accessToken.current}</span>`</span>;
        <span class="hljs-keyword">return</span> config;
      },
    );

    <span class="hljs-comment">// if the current token is expired or invalid, logout the user</span>
    axios.interceptors.response.use(
      <span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response,
      <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (error.response.status === <span class="hljs-number">401</span> &amp;&amp; accessToken.current) {
          clearToken();

          <span class="hljs-comment">// let the app know that the current token was cleared</span>
          onTokenInvalid();
        }
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.reject(error);
      },
    );

    <span class="hljs-comment">// configure axios-hooks to use this instance of axios</span>
    configure({ axios });
  }, [clearToken, onTokenInvalid]);

  <span class="hljs-keyword">return</span> {
    clearToken,
    setToken,
    isAuthenticated,
  };
}
</code></pre><h2 id="the-authentication-container">The Authentication Container</h2>
<p>The Authentication Container holds the logged-in user state, and exposes the user authentication lifecycle methods: register, login, logout, and refresh token. The container is also responsible for cross-tabs login and logout support.</p>
<h3 id="initialization">Initialization</h3>
<p>The access token is saved in memory and therefore isn't available in the initialization phase of the app, however, the refresh token that is saved in an httpOnly cookie is available.
When our app starts up, we try to fetch a new access token using the <code>/refresh-token</code> endpoint and we attempt to initialize our app with it. If the request is successful, we update the state with the received access token and the user object. In case the refresh token is not available or expired, the user will have to login again. Let's create the container, and the <code>useEffect</code> hook that is responsible for the initialization.</p>
<pre><code><span class="hljs-comment">// AuthContainer.ts</span>

<span class="hljs-comment">//...</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useAuth</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> history = useHistory();
  <span class="hljs-keyword">const</span> [user, setUser] = useState&lt;User | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> refreshToken = useCallback(refresh, []);

  <span class="hljs-keyword">const</span> onTokenInvalid = useCallback(<span class="hljs-function">() =&gt;</span> setUser(<span class="hljs-literal">null</span>), []);
  <span class="hljs-keyword">const</span> { setToken, clearToken, isAuthenticated } = useToken(onTokenInvalid, refreshToken);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// try to get new token on first render using refresh token</span>
    refreshToken();
  }, [refreshToken]);

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">refresh</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> {
      <span class="hljs-attr">data</span>: { user, ...rest },
    } = <span class="hljs-keyword">await</span> axios.get&lt;UserAndTokenResponse&gt;(<span class="hljs-string">'refresh-token'</span>);

    setUser(user);
    setToken(rest);
  }

<span class="hljs-comment">//.....</span>
</code></pre><h3 id="the-user-lifecycle-methods">The user lifecycle methods</h3>
<p>The lifecycle methods have several roles:</p>
<ul>
<li>Update the user state on different lifecycle events: login, logout, register, refresh token</li>
<li>Set a new token data when user registers or logs in</li>
<li>Clear token data when the user logs out</li>
<li>Notify the other open applications tabs on login and logout events</li>
</ul>
<pre><code><span class="hljs-comment">// AuthContainer.ts</span>

<span class="hljs-comment">//...</span>


<span class="hljs-keyword">const</span> logout = useCallback(<span class="hljs-function">() =&gt;</span> {
    clearToken().finally(<span class="hljs-function">() =&gt;</span> {
      setUser(<span class="hljs-literal">null</span>);
      history.push(<span class="hljs-string">'/'</span>);

      <span class="hljs-comment">// fire an event to logout from all tabs</span>
      <span class="hljs-built_in">window</span>.localStorage.setItem(AuthEvents.LOGOUT, <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString());
    });
  }, [history, clearToken]);

  <span class="hljs-keyword">const</span> register = useCallback(
    <span class="hljs-keyword">async</span> (userToRegister: UserBase) =&gt; {
      <span class="hljs-keyword">const</span> {
        data: { user, ...rest },
      } = <span class="hljs-keyword">await</span> axios.post&lt;UserAndTokenResponse&gt;(<span class="hljs-string">'register'</span>, userToRegister);
      setUser(user);
      setToken(rest);
    },
    [setToken],
  );

  <span class="hljs-keyword">const</span> login = useCallback(
    <span class="hljs-keyword">async</span> (email: <span class="hljs-built_in">string</span>, password: <span class="hljs-built_in">string</span>) =&gt; {
      <span class="hljs-keyword">const</span> {
        data: { user, ...rest },
      } = <span class="hljs-keyword">await</span> axios.post&lt;UserAndTokenResponse&gt;(<span class="hljs-string">'login'</span>, {
        email,
        password,
      });
      setUser(user);
      setToken(rest);

      <span class="hljs-comment">// fire an event to let all tabs know they should login</span>
      <span class="hljs-built_in">window</span>.localStorage.setItem(AuthEvents.LOGIN, <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString());
    },
    [setToken],
  );

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">refresh</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> {
      data: { user, ...rest },
    } = <span class="hljs-keyword">await</span> axios.get&lt;UserAndTokenResponse&gt;(<span class="hljs-string">'refresh-token'</span>);

    setUser(user);
    setToken(rest);
  }
<span class="hljs-comment">//...</span>
</code></pre><h3 id="multiple-tabs-support-for-login-and-logout">Multiple tabs support for login and logout</h3>
<p>If the user has multiple tabs open, when they log in or out, we should notify the other tabs that the status has changed. For that, we use <code>storage</code> events. When we set a new variable on the local storage, an event is fired across all tabs, not including the tab that initiated the change. This is exactly what we are looking for!</p>
<p>Let's first add the listener that listens to <code>storage</code> events fired by other tabs:</p>
<pre><code>  useEffect(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
    <span class="hljs-regexp">//</span> add listener <span class="hljs-keyword">for</span> login <span class="hljs-keyword">or</span> logout <span class="hljs-keyword">from</span> other tabs
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'storage'</span>, <span class="hljs-keyword">async</span> (event: WindowEventMap[<span class="hljs-string">'storage'</span>]) =&gt; {
      <span class="hljs-keyword">if</span> (event.key === AuthEvents.LOGOUT &amp;&amp; isAuthenticated()) {
        <span class="hljs-keyword">await</span> clearToken(<span class="hljs-literal">false</span>);
        setUser(<span class="hljs-literal">null</span>);
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === AuthEvents.LOGIN) {
        refreshToken();
      }
    });
  }, [clearToken, isAuthenticated, refreshToken]);
</code></pre><p>Let's combine everything together:</p>
<pre><code><span class="hljs-comment">// AuthContainer.ts</span>

<span class="hljs-keyword">import</span> { useCallback, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { createContainer } <span class="hljs-keyword">from</span> <span class="hljs-string">'unstated-next'</span>;
<span class="hljs-keyword">import</span> { useHistory } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router'</span>;
<span class="hljs-keyword">import</span> { axios, UserAndTokenResponse, useToken } <span class="hljs-keyword">from</span> <span class="hljs-string">'../Hooks/useToken'</span>;
<span class="hljs-keyword">import</span> { AuthEvents } <span class="hljs-keyword">from</span> <span class="hljs-string">'../services/AuthEvents'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> UserBase {
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
  password: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> User <span class="hljs-keyword">extends</span> UserBase {
  _id: <span class="hljs-built_in">string</span>;
  role: <span class="hljs-string">'user'</span> | <span class="hljs-string">'admin'</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">useAuth</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> history = useHistory();
  <span class="hljs-keyword">const</span> [user, setUser] = useState&lt;User | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> refreshToken = useCallback(refresh, []);

  <span class="hljs-keyword">const</span> onTokenInvalid = <span class="hljs-function">() =&gt;</span> setUser(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> { setToken, clearToken, isAuthenticated } = useToken(onTokenInvalid, refreshToken);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// try to get new token on first render using refresh token</span>
    refreshToken();
  }, [refreshToken]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// add listener for login or logout from other tabs</span>
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'storage'</span>, <span class="hljs-keyword">async</span> (event: WindowEventMap[<span class="hljs-string">'storage'</span>]) =&gt; {
      <span class="hljs-keyword">if</span> (event.key === AuthEvents.LOGOUT &amp;&amp; isAuthenticated()) {
        <span class="hljs-keyword">await</span> clearToken(<span class="hljs-literal">false</span>);
        setUser(<span class="hljs-literal">null</span>);
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.key === AuthEvents.LOGIN) {
        refreshToken();
      }
    });
  }, [clearToken, isAuthenticated, refreshToken]);

  <span class="hljs-keyword">const</span> logout = useCallback(<span class="hljs-function">() =&gt;</span> {
    clearToken().finally(<span class="hljs-function">() =&gt;</span> {
      setUser(<span class="hljs-literal">null</span>);
      history.push(<span class="hljs-string">'/'</span>);

      <span class="hljs-comment">// fire an event to logout from all tabs</span>
      <span class="hljs-built_in">window</span>.localStorage.setItem(AuthEvents.LOGOUT, <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString());
    });
  }, [history, clearToken]);

  <span class="hljs-keyword">const</span> register = useCallback(
    <span class="hljs-keyword">async</span> (userToRegister: UserBase) =&gt; {
      <span class="hljs-keyword">const</span> {
        data: { user, ...rest },
      } = <span class="hljs-keyword">await</span> axios.post&lt;UserAndTokenResponse&gt;(<span class="hljs-string">'register'</span>, userToRegister);
      setUser(user);
      setToken(rest);
    },
    [setToken],
  );

  <span class="hljs-keyword">const</span> login = useCallback(
    <span class="hljs-keyword">async</span> (email: <span class="hljs-built_in">string</span>, password: <span class="hljs-built_in">string</span>) =&gt; {
      <span class="hljs-keyword">const</span> {
        data: { user, ...rest },
      } = <span class="hljs-keyword">await</span> axios.post&lt;UserAndTokenResponse&gt;(<span class="hljs-string">'login'</span>, {
        email,
        password,
      });
      setUser(user);
      setToken(rest);

      <span class="hljs-comment">// fire an event to let all tabs know they should login</span>
      <span class="hljs-built_in">window</span>.localStorage.setItem(AuthEvents.LOGIN, <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString());
    },
    [setToken],
  );

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">refresh</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> {
      data: { user, ...rest },
    } = <span class="hljs-keyword">await</span> axios.get&lt;UserAndTokenResponse&gt;(<span class="hljs-string">'refresh-token'</span>);

    setUser(user);
    setToken(rest);
  }

  <span class="hljs-keyword">return</span> {
    user,
    setUser,
    register,
    login,
    logout,
    refreshToken,
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AuthContainer = createContainer(useAuth);
</code></pre><p>That's it! Now we just need to wrap everything with the <code>AuthContainer</code> to make the authentication state available in the entire app.</p>
<pre><code><span class="hljs-comment">// App.tsx</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> () =&gt; {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ThemeProvider</span> <span class="hljs-attr">theme</span>=<span class="hljs-string">{theme}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Router</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">AuthContainer.Provider</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">AuthContainer.Provider</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Router</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ThemeProvider</span>&gt;</span></span>
  );
};
</code></pre><p>And now we can use our new and shiny <code>AuthContainer</code> to protect the application routes</p>
<pre><code><span class="hljs-keyword">import</span> React, { ComponentType } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Redirect, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">interface</span> ProtectedRouteProps { 
  component: ComponentType&lt;<span class="hljs-built_in">any</span>&gt;;
  path: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">const</span> ProtectedRoute: FC&lt;ProtectedRouteProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ component: Component, path = <span class="hljs-string">''</span> }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { user } = AuthContainer.useContainer();

  <span class="hljs-keyword">return</span> (
    &lt;Route
      path={path}
      render={<span class="hljs-function">(<span class="hljs-params">props</span>) =&gt;</span>
        user ? (
          &lt;Component {...props} /&gt;
        ) : (
          &lt;Redirect
            to={{
              pathname: <span class="hljs-string">'/login'</span>,
              state: { <span class="hljs-keyword">from</span>: props.location },
            }}
          /&gt;
        )
      }
    /&gt;
  );
};
</code></pre><p>That's all for this part. In the next part, we'll integrate multiple social logins that work seamlessly using the infrastructure we created so far. I hope you find this post helpful, let me know what you think!</p>
]]></content:encoded></item><item><title><![CDATA[React, NodeJS and JWT Authentication - the right way!]]></title><description><![CDATA[In this series of posts, we will create a secured end-to-end JWT-based authentication mechanism using NodeJS, Express, PassportJS and React. 
There's a lot of information online about JWT-based authentication, however, I still see a lot of questions ...]]></description><link>https://blog.galmalachi.com/react-nodejs-and-jwt-authentication-the-right-way</link><guid isPermaLink="true">https://blog.galmalachi.com/react-nodejs-and-jwt-authentication-the-right-way</guid><category><![CDATA[JWT]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Gal Malachi]]></dc:creator><pubDate>Thu, 03 Sep 2020 19:21:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1599220736772/vyvVsVisb.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts, we will create a <strong>secured end-to-end JWT-based authentication mechanism</strong> using NodeJS, Express, PassportJS and React. 
There's a lot of information online about JWT-based authentication, however, I still see a lot of questions and overall confusion around this topic when it comes to actual implementation in a project. Implementing a naive user authentication using JWT is relatively easy, but creating a <strong>safe and secure</strong> mechanism requires a little bit of attention.</p>
<p>In this series I will cover:</p>
<ul>
<li>Part 1: Background and Backend using NodeJS (<strong>This post</strong>)</li>
<li>Part 2:  <a target="_blank" href="https://blog.galmalachi.com/react-and-jwt-authentication-the-right-way-ckfnzadyi01d00ks18f7aa7ca">React &amp; JWT Authentication</a> </li>
<li>Part 3:  <a target="_blank" href="https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-nodejs">Single Sign-On, JWT, and NodeJS</a> </li>
<li>Part 4:  <a target="_blank" href="https://blog.galmalachi.com/single-sign-on-jwt-authentication-and-react">Single Sign-On, JWT, and React</a> </li>
</ul>
<h2 id="what-and-why">What and Why</h2>
<p>JWT, or in its full name JSON Web Token, is an open standard defined by <a target="_blank" href="https://tools.ietf.org/html/rfc7519">RFC7519</a> for safe and compact communication between parties. The authentication (and sometimes authorization) data is transmitted in a form of a JSON object. The payload inside JWT is digitally signed using a secret or a public/private key pair and therefore can be verified and trusted.</p>
<p>The JWT standard became so popular mainly due to its simplicity. JWTs are compact, stateless (and therefore are a perfect match for microservice architecture), easy to generate, and process. JWT also works great with all programming languages and can be used across different environments and systems.
If this is your first time reading about JWT, I strongly encourage you to learn the basics before proceeding with this post. Here is a <a target="_blank" href="https://jwt.io/introduction/">great starting point</a>.</p>
<h2 id="stateless-or-stateful">Stateless or Stateful?</h2>
<p>There are two approaches for users' authentication with tokens, Stateless, or Stateful. Both approaches make use of tokens that are being sent to the client to identify the user in future interactions. However, the main difference is how and where the users' authentication data is stored after a successful login.</p>
<h3 id="stateful-authentication">Stateful Authentication</h3>
<p>The backend generates a <strong>random token that represents a session</strong> of a user in the system and stores that information in the DB or the memory. When a user accesses one of the protected resources, the session is pulled out of the storage, and the verification process occurs.</p>
<h3 id="stateless-authentication">Stateless Authentication</h3>
<p>The backend generates a <strong>token that represents a user and contains the user's data as a payload</strong>. When a user accesses one of the protected resources, the token itself is verified, the users' information is extracted from the token itself, and a decision is made.</p>
<p>There are <a target="_blank" href="https://www.openidentityplatform.org/blog/stateless-vs-stateful-authentication">pros and cons to each approach</a>. Most of the advantages of JWT we have mentioned earlier exist thanks to the stateless nature of it. However, those come with a cost that can make our system vulnerable to attacks if a token is stolen from the client.</p>
<h2 id="access-token-vs-refresh-token">Access Token vs Refresh Token</h2>
<p>To solve most of the security problems that might arise from the use of JWTs, we use refresh tokens. There are many implementations and guides that explain how to create a JWT-based authentication. However, most of them don't use refresh tokens. <strong>Refresh tokens are crucial when working with JWTs</strong>, so don't skip them!</p>
<p>In general, access tokens are used to authenticate a user, while refresh tokens are used to generate a new access token. Users can't authenticate with a refresh token, and this is an important detail, therefore, our system needs to know how to differentiate the tokens by their types.</p>
<h3 id="how-do-refresh-tokens-help-us-protecting-our-system">How do refresh tokens help us protecting our system?</h3>
<p>After a successful login, our backend sends the access token to the client. From that point, the client will pass the token with every request. Allow me to emphasize two things we should <strong>NOT DO</strong>:</p>
<ul>
<li>We <strong>DO NOT</strong> store the JWT token in the local storage as it is vulnerable to  <a target="_blank" href="https://portswigger.net/web-security/cross-site-scripting">XSS attacks</a>.</li>
<li>We <strong>DO NOT</strong> store our access token in a cookie (even if it is <code>httpOnly</code> cookie), as it is vulnerable to <a target="_blank" href="https://www.synopsys.com/glossary/what-is-csrf.html">CSRF attacks</a>. There's another way to handle CSRF attacks, by using <code>SAME SITE</code> but it depends on browser support, so I wouldn't count on it completely just yet.</li>
</ul>
<p>So what do we do? We use refresh tokens.
When a user logs in for the first time, they'll get two tokens:</p>
<ul>
<li>Access token in the payload of the response of the authentication request. </li>
<li>A refresh token in an HTTP only cookie.</li>
</ul>
<p>The access token won't be saved in the local storage of our client application, and here goes the XSS risk.
The refresh token will be stored in a cookie and can be used to retrieve a new access token from a dedicated endpoint in future visits to our app. The new access token will be passed to the client in the payload of the request, here goes the CSRF risk. It might sound a little bit confusing at the start, so it's about time to see some code examples to clear things out!</p>
<h2 id="backend-implementation">Backend implementation</h2>
<h3 id="generate-and-encrypt-the-token">Generate and encrypt the token</h3>
<p>Before we start to work our way towards secured API endpoints, we need to create some Utils to help us achieve our goals. Obviously, we want to be able to generate a JWT. Since a <strong>JWT is not encrypted by default</strong>, we also want to encrypt it to prevent sensitive information that is stored on the token to leak.</p>
<p>Let's first create our <code>encryption-util</code>, as we'll need to use it in our <code>token-util</code> as well.</p>
<pre><code><span class="hljs-keyword">import</span> config <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/config'</span>;
<span class="hljs-keyword">import</span> { createCipheriv, createDecipheriv, scryptSync } <span class="hljs-keyword">from</span> <span class="hljs-string">'crypto'</span>;
const secret = config.<span class="hljs-keyword">get</span>(<span class="hljs-string">'authentication.token.secret'</span>);
const algorithm = <span class="hljs-string">'aes-192-cbc'</span>;

const key = scryptSync(secret, <span class="hljs-string">'salt'</span>, <span class="hljs-number">24</span>);
const iv = Buffer.alloc(<span class="hljs-number">16</span>, <span class="hljs-number">0</span>); // Initialization crypto vector

export <span class="hljs-keyword">function</span> encrypt(<span class="hljs-type">text</span>: string) {
  const cipher = createCipheriv(algorithm, key, iv);
  let <span class="hljs-keyword">encrypted</span> = cipher.<span class="hljs-keyword">update</span>(<span class="hljs-type">text</span>, <span class="hljs-string">'utf8'</span>, <span class="hljs-string">'hex'</span>);
  <span class="hljs-keyword">encrypted</span> += cipher.final(<span class="hljs-string">'hex'</span>);
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">encrypted</span>;
}

export <span class="hljs-keyword">function</span> decrypt(<span class="hljs-type">text</span>: string) {
  const decipher = createDecipheriv(algorithm, key, iv);
  let decrypted = decipher.<span class="hljs-keyword">update</span>(<span class="hljs-type">text</span>, <span class="hljs-string">'hex'</span>, <span class="hljs-string">'utf8'</span>);
  decrypted += decipher.final(<span class="hljs-string">'utf8'</span>);
  <span class="hljs-keyword">return</span> decrypted;
}
</code></pre><p>Now, we use our <code>encryption-util</code> to generate an encrypted JWT. For the JWT itself, we'll utilize the <code>jsonwebtoken</code> package. Let's create it.</p>
<pre><code><span class="hljs-keyword">import</span> { decode, sign, verify } <span class="hljs-keyword">from</span> <span class="hljs-string">'jsonwebtoken'</span>;
<span class="hljs-keyword">import</span> config <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/config'</span>;
<span class="hljs-keyword">import</span> { decrypt, encrypt } <span class="hljs-keyword">from</span> <span class="hljs-string">'./encryption-util'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-built_in">enum</span> TokenType {
  ACCESS_TOKEN = <span class="hljs-string">'access_token'</span>,
  REFRESH_TOKEN = <span class="hljs-string">'refresh_token'</span>,
}

<span class="hljs-keyword">type</span> JWT = { exp: <span class="hljs-built_in">number</span>; <span class="hljs-keyword">type</span>: TokenType; sub: <span class="hljs-built_in">string</span> };

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> generateAccessToken = <span class="hljs-function">(<span class="hljs-params">userId: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> generateToken(userId, TokenType.ACCESS_TOKEN);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> generateRefreshToken = <span class="hljs-function">(<span class="hljs-params">userId: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> generateToken(userId, TokenType.REFRESH_TOKEN);
};

<span class="hljs-keyword">const</span> generateToken = <span class="hljs-function">(<span class="hljs-params">userId: <span class="hljs-built_in">string</span>, <span class="hljs-keyword">type</span>: TokenType</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> audience = config.get(<span class="hljs-string">'authentication.token.audience'</span>);
  <span class="hljs-keyword">const</span> issuer = config.get(<span class="hljs-string">'authentication.token.issuer'</span>);
  <span class="hljs-keyword">const</span> secret = config.get(<span class="hljs-string">'authentication.token.secret'</span>);
  <span class="hljs-keyword">const</span> expiresIn =
    <span class="hljs-keyword">type</span> === TokenType.ACCESS_TOKEN
      ? config.get(<span class="hljs-string">'authentication.token.expiresIn'</span>)
      : config.get(<span class="hljs-string">'authentication.refreshToken.expiresIn'</span>);

  <span class="hljs-keyword">const</span> token = sign({ <span class="hljs-keyword">type</span> }, secret, {
    expiresIn,
    audience: audience,
    issuer: issuer,
    subject: userId,
  });

  <span class="hljs-keyword">return</span> {
    token: encrypt(token),
    expiration: (decode(token) <span class="hljs-keyword">as</span> JWT).exp * <span class="hljs-number">1000</span>,
  };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getTokenType = (token: <span class="hljs-built_in">string</span>): <span class="hljs-function"><span class="hljs-params">TokenType</span> =&gt;</span> {
  <span class="hljs-keyword">return</span> (verify(token, config.get(<span class="hljs-string">'authentication.token.secret'</span>)) <span class="hljs-keyword">as</span> JWT).type;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> parseTokenAndGetUserId = (token: <span class="hljs-built_in">string</span>): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> decryptedToken = decrypt(token);
  <span class="hljs-keyword">const</span> decoded = verify(decryptedToken, config.get(<span class="hljs-string">'authentication.token.secret'</span>)) <span class="hljs-keyword">as</span> JWT;
  <span class="hljs-keyword">return</span> decoded.sub || <span class="hljs-string">''</span>;
};
</code></pre><h3 id="user-authentication-with-passportjs">User Authentication with PassportJS</h3>
<p><a target="_blank" href="http://www.passportjs.org">PassportJS</a> is modular authentication middleware for NodeJS, allowing us to use different authentication strategies. Here we create a new passport strategy using the passport-jwt package.</p>
<p>Our frontend uses the <code>authorization</code> HTTP header to provide the access token while making a new request to the backend. When a new request comes in, we need to authenticate the user by the provided token, read the user from the DB, and attach it to the request. 
The passport strategy contains 2 main parts, the <code>jwtFromRequest</code> and the <code>verifyCallback</code> function.
The <code>jwtFromRequest</code> function is responsible for getting the token from the request, and then decrypting, verifying, and checking the type of the token.
The <code>verifyCallback</code> function is responsible for getting the user from the database based on the token, and attaching it to the request for future use.</p>
<p>Here's the passport JWT strategy.</p>
<pre><code><span class="hljs-keyword">import</span> { UserModel } <span class="hljs-keyword">from</span> <span class="hljs-string">'../models/user'</span>;
<span class="hljs-keyword">import</span> { Strategy <span class="hljs-keyword">as</span> JwtStrategy, VerifiedCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">'passport-jwt'</span>;
<span class="hljs-keyword">import</span> config <span class="hljs-keyword">from</span> <span class="hljs-string">'../config/config'</span>;
<span class="hljs-keyword">import</span> passport <span class="hljs-keyword">from</span> <span class="hljs-string">'passport'</span>;
<span class="hljs-keyword">import</span> { Request } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { decrypt } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/encryption-util'</span>;
<span class="hljs-keyword">import</span> { getTokenType, TokenType } <span class="hljs-keyword">from</span> <span class="hljs-string">'../utils/token-util'</span>;

passport.use(
  <span class="hljs-keyword">new</span> JwtStrategy(
    {
      jwtFromRequest: <span class="hljs-function">(<span class="hljs-params">req: Request</span>) =&gt;</span> {
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">if</span> (!req.headers.authorization) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'token was not provided, authorization header is empty'</span>);
          }

          <span class="hljs-keyword">const</span> tokenFromHeader = req.headers.authorization.replace(<span class="hljs-string">'Bearer '</span>, <span class="hljs-string">''</span>).trim();
          <span class="hljs-keyword">const</span> decryptedToken = decrypt(tokenFromHeader);
          <span class="hljs-keyword">const</span> tokenType = getTokenType(decryptedToken);

          <span class="hljs-keyword">if</span> (tokenType !== TokenType.ACCESS_TOKEN) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'wrong token type provided'</span>);
          }

          <span class="hljs-keyword">return</span> decryptedToken;
        } <span class="hljs-keyword">catch</span> (e) {
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Token is not valid'</span>, e.message);
          <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
        }
      },
      secretOrKey: config.get(<span class="hljs-string">'authentication.token.secret'</span>),
      issuer: config.get(<span class="hljs-string">'authentication.token.issuer'</span>),
      audience: config.get(<span class="hljs-string">'authentication.token.audience'</span>),
      passReqToCallback: <span class="hljs-literal">true</span>,
    },
    <span class="hljs-function">(<span class="hljs-params">req: Request, payload: <span class="hljs-built_in">any</span>, done: VerifiedCallback</span>) =&gt;</span> {
      UserModel.findById(payload.sub, <span class="hljs-function">(<span class="hljs-params">err, user</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (err) {
          <span class="hljs-keyword">return</span> done(err, <span class="hljs-literal">false</span>);
        }
        req.currentUser = user?.toObject();
        <span class="hljs-keyword">return</span> !user ? done(<span class="hljs-literal">null</span>, <span class="hljs-literal">false</span>) : done(<span class="hljs-literal">null</span>, user);
      });
    },
  ),
);
</code></pre><p>Once we have the strategy configured, we need to have a way to restrict our API endpoints. For that, we create a middleware. As discussed before, we work in a stateless way and therefore we set the <code>session</code> value as <code>false</code>.</p>
<pre><code><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> requireAuth = passport.authenticate(<span class="hljs-string">'jwt'</span>, {
  userProperty: <span class="hljs-string">'currentUser'</span>,
  session: <span class="hljs-keyword">false</span>,
});
</code></pre><p>We're almost done with our backend. We need to create the API endpoints, protect them, as send the access_token and the refresh_token to the user. Routes that require authentication will be protected using the <code>requireAuth</code> middleware we created earlier.</p>
<pre><code><span class="hljs-keyword">import</span> express from <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">const</span> router = express.Router();
<span class="hljs-keyword">import</span> { requireAuth } from <span class="hljs-string">'../auth'</span>;
<span class="hljs-keyword">import</span> config from <span class="hljs-string">'../config/config'</span>;
<span class="hljs-keyword">import</span> { generateAccessToken, generateRefreshToken } from <span class="hljs-string">'../utils/token-util'</span>;

<span class="hljs-keyword">const</span> generateTokensAndAuthenticateUser = <span class="hljs-keyword">async</span> (res, userId) =&gt; {
  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> findUserById(userId);
  <span class="hljs-keyword">const</span> { token: access_token, expiration: token_expiration } = <span class="hljs-keyword">await</span> generateAccessToken(userId);
  <span class="hljs-keyword">const</span> { token: refreshToken } = generateRefreshToken(userId);
  res.cookie(<span class="hljs-string">'refresh_token'</span>, refreshToken, { httpOnly: <span class="hljs-keyword">true</span> });
  res.status(<span class="hljs-number">200</span>).json({ access_token, token_expiration, user });
};

router.post(<span class="hljs-string">'/register'</span>, () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { email } = req.body;
    <span class="hljs-keyword">const</span> doesEmailExist = <span class="hljs-keyword">await</span> fieldExists(<span class="hljs-string">'email'</span>, email);

    <span class="hljs-keyword">if</span> (!doesEmailExist) {
      <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> registerUser(req.body);
      generateTokensAndAuthenticateUser(res, user._id);
    }
  } <span class="hljs-keyword">catch</span> (error) {
    handleError(res, error);
  }
});

router.<span class="hljs-keyword">get</span>(<span class="hljs-string">'/refresh-token'</span>, () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> tokenEncrypted = req.cookies.refresh_token;
    <span class="hljs-keyword">const</span> userId = <span class="hljs-keyword">await</span> parseTokenAndGetUserId(tokenEncrypted);
    generateTokensAndAuthenticateUser(res, userId);
  } <span class="hljs-keyword">catch</span> (error) {
    handleError(res, error);
  }
});

router.<span class="hljs-keyword">get</span>(<span class="hljs-string">'/logout'</span>, requireAuth, () =&gt; {
  res.cookie(<span class="hljs-string">'refresh_token'</span>, <span class="hljs-string">''</span>, { httpOnly: <span class="hljs-keyword">true</span> });
  res.status(<span class="hljs-number">200</span>).end();
});

router.post(<span class="hljs-string">'/login'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { email, passport } = req.body;
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> findUser(data.email);
    <span class="hljs-keyword">await</span> checkPassword(password, user);
    generateTokensAndAuthenticateUser(res, user._id);
  } <span class="hljs-keyword">catch</span> (error) {
    handleError(res, error);
  }
});
</code></pre><p>That's all for now. The full code will be published soon!
In the next part, we'll create a React-based web app and use the backend we just created. I hope you find this post helpful, let me know what you think!</p>
]]></content:encoded></item></channel></rss>