<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Bruce Thomas</title>
    <description>The latest articles on Forem by Bruce Thomas (@fliptopbox).</description>
    <link>https://forem.com/fliptopbox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F832666%2Fd321deb2-165d-43d2-acc5-eaa756bf2669.jpeg</url>
      <title>Forem: Bruce Thomas</title>
      <link>https://forem.com/fliptopbox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fliptopbox"/>
    <language>en</language>
    <item>
      <title>Netlify Identity protects Ably apps from hacks</title>
      <dc:creator>Bruce Thomas</dc:creator>
      <pubDate>Wed, 25 May 2022 10:18:20 +0000</pubDate>
      <link>https://forem.com/fliptopbox/netlify-identity-protects-ably-apps-from-hacks-54e4</link>
      <guid>https://forem.com/fliptopbox/netlify-identity-protects-ably-apps-from-hacks-54e4</guid>
      <description>&lt;p&gt;A few weeks ago I saw this message in our internal support channel on Slack, and it made my gears grind. So I stopped to take a look, since the last thing we want is a customer taken advantage of.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The customer has responded to 703762824&lt;br&gt;
Ticket Name: xxxxxxxxxxxxx - API Key Security breach&lt;br&gt;
Priority: Ticket&lt;br&gt;
Description: Ticket raised as a result of a security report as indicated by ... to contact client to let them know and take action as their API Key was exposed in the Wayback Machine, and is still live.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are people out there on the internet cheeky enough to freeload off your account, and use up your monthly quotas. What's more: you may not know that it is happening.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to ensure your Ably app gets hacked
&lt;/h1&gt;

&lt;p&gt;Log in to Ably, copy your app's API key and paste it into your code, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ably&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;
&lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aBCdeFg.ABcDEfG:abc123def456....789xyz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then commit this line of code to a git repository, deploy it to production and the wayback machine will freeze it in amber. It is that simple. There it will wait until an eager little Hobbit finds it in the dark. Your precioussssss API key will be in the wrong hands.&lt;/p&gt;

&lt;p&gt;Luckily, at Ably, we monitor authentication key leaks and contact the client.&lt;/p&gt;

&lt;p&gt;Basic authentication is like a cat flap -- any old cat can come into your house. Token authentication is like one of those with a chip reader: only the cool cats that match the chip's programmed VIP guest list can pass the flap test and get in.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using Netlify functions to protect your app
&lt;/h1&gt;

&lt;p&gt;This article shows you how to set up token authentication with very little effort. We will make an endpoint using a &lt;a href="https://docs.netlify.com/functions/overview/" rel="noopener noreferrer"&gt;Netlify function&lt;/a&gt; that lets you do this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.netlify/functions/ably-jwt?id={user-id}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ably&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That looks great, no API anywhere, but what\'s stopping someone on the internet from taking the authentication URL and using it elsewhere? Well nothing is stopping them. Token authentication will hide the API key, but that alone is not enough.&lt;/p&gt;

&lt;p&gt;We also need a way to issue the token to valid users only. This is where Netfliy comes into the picture, we can use their &lt;a href="https://docs.netlify.com/visitor-access/identity/" rel="noopener noreferrer"&gt;Identity services&lt;/a&gt; in conjunction with a serverless function, and that is included in the free tier plan (when this article was written).&lt;/p&gt;

&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;p&gt;If you just want to get into the guts of setting up your own Netlify app without all the explanations, then head over to the &lt;a href="https://github.com/ably-labs/netlify-identity-auth" rel="noopener noreferrer"&gt;ably-labs Github repo&lt;/a&gt;, where the README is a condensed version of this article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reminder&lt;/strong&gt;: you will need accounts on these platforms:&lt;br&gt;
&lt;a href="https://ably.com/sign-up" rel="noopener noreferrer"&gt;Ably&lt;/a&gt;, &lt;a href="https://github.com" rel="noopener noreferrer"&gt;Github&lt;/a&gt; and &lt;a href="https://app.netlify.com/signup?ably-realtime" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are only interested in going through the implementation steps, then jump down to &lt;strong&gt;Okay, okay, enough ... let's ship it!&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  What is an "Ably" JWT token?
&lt;/h1&gt;

&lt;p&gt;JSON Web Token (JWT) is an open standard (&lt;a href="https://tools.ietf.org/html/rfc7519" rel="noopener noreferrer"&gt;RFC 7519&lt;/a&gt;) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Taken from &lt;a href="https://jwt.io/introduction/" rel="noopener noreferrer"&gt;https://jwt.io/introduction/&lt;/a&gt; )&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Typically an encoded JWT token looks like this:&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Try it here &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;https://jwt.io/&lt;/a&gt; - crafted by the folks at &lt;a href="https://auth0.com/" rel="noopener noreferrer"&gt;auth0&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Token authentication is secure for two reasons: Ably JWT tokens are digitally signed and they expire regularly. Additionally you can monitor who is using your app whenever the token refreshes. When the JWT expires, the user is routed via your validation server and you choose whether or not to reissue their token.&lt;/p&gt;

&lt;p&gt;An Ably JWT is not strictly an Ably construct, rather it is a JWT which has been constructed to be compatible with Ably. If you want to know in detail how it works, our &lt;a href="https://ably.com/documentation/best-practice-guide" rel="noopener noreferrer"&gt;best practice guide&lt;/a&gt; will cover everything.&lt;/p&gt;
&lt;h1&gt;
  
  
  Netlify serverless functions &amp;amp; identity workflow
&lt;/h1&gt;

&lt;p&gt;In our example app, a new user needs to register and confirm their email address to activate themselves before they can log in. At login, we validate them with Netlify Identity and check that they have not been flagged as &lt;strong&gt;Banned&lt;/strong&gt;. Bad actors are not issued with a JWT token. Valid users are issued a token, to authenticate with Ably, and the auth URL carries their unique ID within it.&lt;/p&gt;

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

&lt;p&gt;(1) User logs-in (2) Check user identity - valid users get JWT token and continue to the app, and invalid users are rejected (3) use the token, wait for it to expire then repeat.&lt;/p&gt;

&lt;p&gt;The Netlify Identity allows us to administer users by editing metadata associated with their account. Flagging a bad actor is a matter of assigning them a role via the Netlify dashboard.&lt;/p&gt;
&lt;h1&gt;
  
  
  The JWT token as a serverless function
&lt;/h1&gt;

&lt;p&gt;The key part of our JWT endpoint is this code snippet. Independent of what platform you are using, this part would be the same. This is the anatomy of the Ably JWT token, which differs from a standard JWT because we require additional key/values in the data payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateAblyJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttlSeconds&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keySecret&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\.\:]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JWT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// type&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HS256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// algorithm sha256&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;keyName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// appId + keyId&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// initiated at (seconds)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ttlSeconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// expire after&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;typ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kid&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;iat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-ably-capability&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-ably-clientId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64Header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encryptObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;base64Claims&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encryptObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base64Header&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base64Claims&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;b64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;keySecret&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;generateAblyJWT&lt;/code&gt; function is almost identical to the one you will find when you read through the Ably documentation on &lt;a href="https://ably.com/documentation/core-features/authentication#ably-jwt" rel="noopener noreferrer"&gt;authentication methods&lt;/a&gt;. We chose Netlify as the platform for this article but the same idea can be applied to any platform that exposes a serverless endpoint. For example you could do the same thing with a &lt;a href="https://developers.cloudflare.com/pages/platform/functions" rel="noopener noreferrer"&gt;&lt;strong&gt;Cloudflare&lt;/strong&gt; function&lt;/a&gt;, a &lt;a href="https://runkit.com/home#endpoint" rel="noopener noreferrer"&gt;&lt;strong&gt;Runkit&lt;/strong&gt; endpoint&lt;/a&gt;, or a &lt;a href="https://www.heroku.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Heroku&lt;/strong&gt; dyno&lt;/a&gt; ...etc. Essentially, you can use any service that "wakes-up", runs code and goes dormant after the response payload has been delivered.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using Netlify Identity to validate our users
&lt;/h1&gt;

&lt;p&gt;This next function is responsible for two things. First it is the endpoint itself, so our authentication URL will execute this function. Secondly, it connects with &lt;a href="https://docs.netlify.com/visitor-access/identity/" rel="noopener noreferrer"&gt;Netlify Identity&lt;/a&gt; to validate that the user, calling the endpoint, is trustworthy.&lt;/p&gt;

&lt;p&gt;I'd like to draw your attention to the line of code highlighted below. That is the Netlify endpoint which we will use to get the user with that id. The value of &lt;strong&gt;id&lt;/strong&gt; is coming from the querystring, which we use to "bake" into our Ably JWT, as the &lt;code&gt;clientId&lt;/code&gt; property. This means that both Ably and Netlify will have the same &lt;strong&gt;id&lt;/strong&gt; in their respective audit logs.&lt;/p&gt;

&lt;p&gt;The great thing about Netlify Identity is that once you activate it, the second argument in the endpoint handler has a populated user context Object, and that is how we connect the frontend client with the underlying backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateAblyJWT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./generate-ably-jwt.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;queryStringParameters&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queryStringParameters&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clientContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;queryStringParameters&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;clientContext&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientContext&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/admin/users/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Authorization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Authorization&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/*

    We Use client context and querystring ID value
    to check the user exists by retrieving their
    User Identity object.

    Then we inspect that accounts metadata for role flags,
    which are added via the Identity dashboard,
    if it contains "Banned" we do not reissue the token

  */&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Authorization&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Success! User identity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;banned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^banned/i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;roles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_metadata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;banned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

      &lt;span class="c1"&gt;// delegate the error message to the catch clause.&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`User with id [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] has been banned`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ABLY_APIKEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ABLY_CAPABILITY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ttlSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ABLY_TTLSECONDS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateAblyJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/jwt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to get user!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Internal Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response payload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exported handler function is a modified version of &lt;a href="https://docs.netlify.com/functions/build-with-javascript/" rel="noopener noreferrer"&gt;Netlify's "hello world" tutorial&lt;/a&gt;. The only major changes are: it's integrated with Identity and the return (aka response) has the header &lt;code&gt;Content-Type: application/jwt&lt;/code&gt;, the example body was originally a (JSON object), now it is a String.&lt;/p&gt;

&lt;p&gt;Apart from these two main functions there is additional boilerplate code that loads an encryption library, &lt;a href="https://www.npmjs.com/package/cryptojs" rel="noopener noreferrer"&gt;CryptoJS&lt;/a&gt;. Oh, and be sure that &lt;strong&gt;expiry time&lt;/strong&gt; is cast as Number(). If anything is incorrect the Ably realtime network will provide a diagnostic error message.&lt;/p&gt;

&lt;h1&gt;
  
  
  A summary of the front-end setup
&lt;/h1&gt;

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

&lt;p&gt;The modal pop-up, that does registration, log-in and password reminder actions, is governed by a single &lt;strong&gt;div&lt;/strong&gt; and a JavaScript from Netlify: &lt;a href="https://identity.netlify.com/v1/netlify-identity-widget.js" rel="noopener noreferrer"&gt;Identity widget&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://ably.com/"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"images/motif-red.svg?netlify-identity-jwt"&lt;/span&gt;
        &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Ably logo"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;
      Ably JWT Authentication &lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      using Netlify Identity and &lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      serverless functions
    &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-netlify-identity-menu&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;

    &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"go(this)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;authenticate&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;strong&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the &lt;strong&gt;AUTHENTICATE&lt;/strong&gt; button executes this function ...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/*
    get the user id from localstorage.
    This is only created once the user logs in
  */&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gotrue.user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;showMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Can't access user ID, please log in first.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/*
     Add the User's identity ID to the authURL
     to ensure the Ably token refresh has that credential
     we are binding the authURL and the user
  */&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.netlify/functions/ably-jwt?id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ably&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Realtime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;authUrl&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ably&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again the URL is highlighted. This value plus the user's ID is used to connect the client to Ably realtime. The moment that happens a countdown timer begins, according to the TTL value set in our environment variables. When it expires, the URL (now stored in Ably realtime) will be used to request an updated token.&lt;/p&gt;

&lt;h1&gt;
  
  
  Okay, okay, enough ... let's ship it!
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;p&gt;The first thing we need to do is prepare the respective platforms. Here we &lt;a href="https://ably.com/login" rel="noopener noreferrer"&gt;login to our Ably account&lt;/a&gt; and create a new app, for which we will need the API key later. Then we fork the repo from Ably Labs to your own repository. We need to do this so that Netlify can import our project in the next step.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Deploy and setup the Netlify services
&lt;/h2&gt;

&lt;p&gt;Now we can import the source code from Github to Netlify, and deploy the app. When the app is up and running we will encounter some errors which we need to correct. As we fix these you will learn a little more about how Netlify works.&lt;/p&gt;

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

&lt;p&gt;To moderate who is allowed to use our app we need a user registration process. Netlify provides a very good widget to create users, log them on and even send password reminders. In addition, you can use SSO log-in services like Google, Github etc. For this project I went with basic email registration. Before you can start registration you need to activate the Netlify Identity services in your dashboard.&lt;/p&gt;

&lt;p&gt;A user is only considered valid once they have confirmed their email address. This integrity check can be bypassed if you want. Once a user has completed the confirmation you will see that identity appear in the dashboard, and you will be able to edit their metadata.&lt;/p&gt;

&lt;p&gt;So we registered a user and logged in with their credentials, and still we are seeing an error when we try to connect to Ably realtime. This is because we have not added the API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the environment variables
&lt;/h2&gt;

&lt;p&gt;We finish up the process by adding our environment variables, re-deploying the app and test the validity of the user account. At this stage you should finally see the authenticate button turn green, which indicates that this user was issued a JWT token, and was successfully connected to the Ably realtime network.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Key&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ABLY_APIKEY&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;The API key of your Ably App&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ABLY_CAPABILITY&lt;/td&gt;
&lt;td&gt;String or Null&lt;/td&gt;
&lt;td&gt;JSON string of permissions e.g. &lt;code&gt;{"channel-name":["subscribe"]}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ABLY_TTLSECONDS&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Refresh rate in seconds e.g. 3600 (60sec)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Capability&lt;/strong&gt; is related to the Ably APP permissions.&lt;/p&gt;

&lt;p&gt;You might want to limit operations to &lt;em&gt;subscribe&lt;/em&gt; only. To refine permissions please refer to our documentation: &lt;a href="https://ably.com/documentation/core-features/authentication#capabilities-explained" rel="noopener noreferrer"&gt;capabilities explained&lt;/a&gt;. In this example &lt;code&gt;{"channel-name": ["subscribe", "publish"]}&lt;/code&gt; means the status channel has &lt;em&gt;publish&lt;/em&gt; and &lt;em&gt;subscribe&lt;/em&gt; permission.&lt;/p&gt;

&lt;p&gt;This demo application doesn't use a channel, it's purpose is to test authentication, however the Ably JWT needs to have that value assigned when the JWT is generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  User moderation
&lt;/h2&gt;

&lt;p&gt;In this last section we will pretend that our main user is no longer welcome, edit their metadata in the Identity section and add "Banned" to the role property. From that point, our app will refuse to re-issue the JWT to that particular user.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Now you know a little more about how Ably works internally and that our team actively checks for API key leaks. External flags do pop up and we will reach out if something looks odd, or potentially dangerous. We take security seriously and can't recommend token authentication enough.&lt;/p&gt;

&lt;p&gt;In this article, we covered how to set up an authentication server using Netlify's free tier offering, how to integrate with Identity services, and we illustrated how straightforward an authentication endpoint is. The security and moderation control really is worth the effort.&lt;/p&gt;

&lt;p&gt;With Netlify, the entire developer experience was smooth, from onboarding, to linking Github, importing a project and deploying to production. My only slow down was moving the dashboard settings (the UI values) into a netlify.toml to ensure anyone who uses this repo gets an equally easy experience.&lt;/p&gt;

&lt;p&gt;The TOML bootloads my defaults, which identifies where the functions are and where the website files serve from. There was one other hiccup; figuring how to do application/jwt content type for the response header. Originally I used plain text (that is what a JWT string is), but in this case the header must identify the type as a "JWT".&lt;/p&gt;

&lt;p&gt;And in closing, a word about the development of this demo. The majority of the work was done using the Netlify CLI, which is a superb piece of dev tooling. It simulates the serverless environment perfectly, but using Identify requires that you complete your development online. I got struck with a bug regarding the context object supplied to the endpoint handler, which was always missing user context. This is highlighted on the &lt;a href="https://docs.netlify.com/visitor-access/identity/" rel="noopener noreferrer"&gt;Netlify documentation for Identity&lt;/a&gt; and they clearly state Identity requires HTTPS, but I missed that and had egg on my face for the greater part of a day. (blush) Read the documentation!&lt;/p&gt;

&lt;h1&gt;
  
  
  Cleanup and teardown
&lt;/h1&gt;

&lt;p&gt;When you are done with the demo please be sure to remove it securely, either by key revocation or by deleting the app.?&lt;/p&gt;

&lt;p&gt;In this last GIF we go over three things: revoking an API key (in the event that you have been compromised) this preserves the app but destroys the key. Delete the app entirely which permanently destroys everything associated with that app, and finally how to delete the Netlify instance.&lt;/p&gt;

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




&lt;h1&gt;
  
  
  Ably in a nutshell
&lt;/h1&gt;

&lt;p&gt;Ably provides APIs to implement &lt;a href="https://ably.com/topic/pub-sub" rel="noopener noreferrer"&gt;pub/sub&lt;/a&gt; messaging for realtime features in your apps. You also get a globally-distributed scalable infrastructure out-of-the-box, along with a suite of services.&lt;/p&gt;

&lt;p&gt;These include features like &lt;a href="https://ably.com/documentation/core-features/presence" rel="noopener noreferrer"&gt;presence&lt;/a&gt; 窶? which shows the online/offline status of various participants, &lt;a href="https://knowledge.ably.com/connection-state-recovery" rel="noopener noreferrer"&gt;automatic re-connection and resumption of messages&lt;/a&gt; in case of intermittent network issues, &lt;a href="https://ably.com/four-pillars-of-dependability#integrity" rel="noopener noreferrer"&gt;message ordering and guaranteed delivery&lt;/a&gt;, as well as easy ways to &lt;a href="https://ably.com/integrations" rel="noopener noreferrer"&gt;integrate with third-party APIs&lt;/a&gt;.Ably enables pub/sub messaging, primarily over &lt;a href="https://ably.com/topic/websockets" rel="noopener noreferrer"&gt;WebSockets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The concept of &lt;a href="https://ably.com/documentation/core-features/channels" rel="noopener noreferrer"&gt;channels&lt;/a&gt; allows you to categorize the data and decide which components have access to which channels. You can also specify &lt;a href="https://ably.com/documentation/core-features/authentication#capabilities-explained" rel="noopener noreferrer"&gt;capabilities&lt;/a&gt; for various participants on these channels like publish-only, subscribe-only, message history, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ably.com/" rel="noopener noreferrer"&gt;Learn more about the Ably platform&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Tools to limit your exposure
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.gitguardian.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;gitguardian&lt;/strong&gt;&lt;/a&gt; keeps secrets out of your source code. It scans your source code to detect API keys, passwords, certificates, encryption keys and other sensitive data in realtime&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.blog/changelog/2020-12-15-github-actions-environments-environment-protection-rules-and-environment-secrets-beta/" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt;&lt;/a&gt; - is great for environment protection rules and environment secrets (beta)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.github.com/en/code-security/secret-scanning/secret-scanning-partners" rel="noopener noreferrer"&gt;GitHub Secret Scanning Partners&lt;/a&gt;&lt;/strong&gt; - lists supported secrets and the partners that GitHub works with to prevent fraudulent use of secrets that were committed accidentally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://git-scm.com/docs/gitignore" rel="noopener noreferrer"&gt;Global .gitignore&lt;/a&gt;&lt;/strong&gt; - project .gitignore files are well known, the lesser known more important &lt;code&gt;.gitignore&lt;/code&gt; file can be set up on your machine and applied to every git repository you track. &lt;code&gt;git config --global core.excludesfile ~/.gitignore_global&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;JSON web token&lt;/strong&gt;&lt;/a&gt; (JWT) and the testing utility.&lt;/p&gt;

</description>
      <category>ably</category>
      <category>netlify</category>
      <category>jwt</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
