<?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: Nick Parsons</title>
    <description>The latest articles on Forem by Nick Parsons (@nickparsons).</description>
    <link>https://forem.com/nickparsons</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%2F23591%2Faff7eabf-f28f-4520-a8a4-77c13fd8a3ef.png</url>
      <title>Forem: Nick Parsons</title>
      <link>https://forem.com/nickparsons</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nickparsons"/>
    <language>en</language>
    <item>
      <title>Clerk Update — July 2024</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Fri, 12 Jul 2024 18:59:00 +0000</pubDate>
      <link>https://forem.com/clerk/clerk-update-july-2024-3mba</link>
      <guid>https://forem.com/clerk/clerk-update-july-2024-3mba</guid>
      <description>&lt;p&gt;The Clerk team has been hard at work shipping new features to help you build secure applications faster. Here’s a rundown of the highlights:&lt;/p&gt;

&lt;h2&gt;
  
  
  Clerk Elements (Beta)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://go.clerk.com/X87UqBt" rel="noopener noreferrer"&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%2Ff5gzhx1oo9056a9grmjk.jpg" alt="Clerk Elements"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clerk Elements is currently in beta and introduces an entirely new set of unstyled UI primitives that make it easy to build completely custom authentication and user management UIs on top of Clerk's APIs and business logic.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customize with CSS Frameworks&lt;/strong&gt;: Because everything is unstyled by default, Clerk Elements gives you complete control over the markup rendered in your authentication flows. Rendered markup accepts a &lt;code&gt;className&lt;/code&gt; prop for easy styling with CSS frameworks such as Tailwind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extend with Component Libraries&lt;/strong&gt;: Clerk Elements also support the &lt;code&gt;asChild&lt;/code&gt; prop, popularized by component libraries like &lt;a href="https://www.radix-ui.com/primitives/docs/guides/composition" rel="noopener noreferrer"&gt;Radix&lt;/a&gt;. Bring your existing component library and it'll take care of the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more, visit the &lt;a href="https://go.clerk.com/X87UqBt" rel="noopener noreferrer"&gt;Clerk Changelog&lt;/a&gt; and &lt;a href="https://go.clerk.com/a3KoX3J" rel="noopener noreferrer"&gt;Clerk Elements docs →&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Google One Tap
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://go.clerk.com/PtfFHr1" rel="noopener noreferrer"&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%2Fpi18qlhqjcagxw0vk447.jpg" alt="Google One Tap"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google One Tap support introduces seamless, one-click user sign-ins and sign-ups. This allows your users to effortlessly access your services without having to remember passwords or undergo lengthy registration processes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New &lt;code&gt;&amp;lt;GoogleOneTap /&amp;gt;&lt;/code&gt; Component&lt;/strong&gt;: If you have already configured Google OAuth with custom credentials, adding support for Google One Tap is as easy as including the new &lt;code&gt;&amp;lt;GoogleOneTap /&amp;gt;&lt;/code&gt; component.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support for Custom Flows &amp;amp; Non-React Environments&lt;/strong&gt;: For those building applications that require custom flows or do not use React, Google One Tap is supported via &lt;a href="https://clerk.com/docs/references/javascript/overview#clerk-js" rel="noopener noreferrer"&gt;clerk-js&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more, visit the &lt;a href="https://go.clerk.com/PtfFHr1" rel="noopener noreferrer"&gt;Clerk Changelog&lt;/a&gt; and &lt;a href="https://go.clerk.com/098Ao1L" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;GoogleOneTap /&amp;gt;&lt;/code&gt; component docs →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Other Features, Fixes &amp;amp; Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://go.clerk.com/8FLBsVK" rel="noopener noreferrer"&gt;Improved Clerk + Expo Docs&lt;/a&gt;&lt;/strong&gt;: We have fully refactored our Expo docs with:

&lt;ul&gt;
&lt;li&gt;New &lt;a href="https://go.clerk.com/IiUyofQ" rel="noopener noreferrer"&gt;quickstart guide&lt;/a&gt; and accompanying &lt;a href="https://github.com/clerk/clerk-expo-quickstart" rel="noopener noreferrer"&gt;starter repo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Updated examples for &lt;a href="https://go.clerk.com/s6XitZ1" rel="noopener noreferrer"&gt;Impersonation&lt;/a&gt;, &lt;a href="https://go.clerk.com/DKMSFnr" rel="noopener noreferrer"&gt;MFA&lt;/a&gt;, and &lt;a href="https://go.clerk.com/DKMSFnr" rel="noopener noreferrer"&gt;OAuth&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Added section that addresses common Expo deployment questions.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://x.com/ClerkDev/status/1793675973667569949" rel="noopener noreferrer"&gt;Clerk + Next.js 15&lt;/a&gt;&lt;/strong&gt;: Clerk is now fully compatible with Next.js 15, with support for the React 19 RC. To use Clerk with Next.js 15, upgrade &lt;a href="https://www.npmjs.com/package/@clerk/nextjs" rel="noopener noreferrer"&gt;@clerk/nextjs&lt;/a&gt; to v5.1.2 or later.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://go.clerk.com/b2FhnzK" rel="noopener noreferrer"&gt;Neon + Clerk Integration Guide&lt;/a&gt;&lt;/strong&gt;: We've published a new integration guide demonstrating how to use &lt;a href="https://neon.tech/" rel="noopener noreferrer"&gt;Neon&lt;/a&gt; with Clerk in a Next.js application, utilizing &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;drizzle-orm&lt;/a&gt; for database interactions.&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Events &amp;amp; Community Updates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clerk OSS Fellowship
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://go.clerk.com/j68JCxO" rel="noopener noreferrer"&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%2F5gttz3moh7quzh3xyrsq.jpg" alt="Clerk OSS Fellowship"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are thrilled to announce the launch of Clerk's Open Source Fellowship program, created to foster continued innovation in the open source software community. Our inaugural recipient is &lt;a href="https://x.com/colinhacks" rel="noopener noreferrer"&gt;Colin McDonnell&lt;/a&gt;, the creator of &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, who will receive funding while he works on bringing Zod to its next version.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1800947431489798163-488" src="https://platform.twitter.com/embed/Tweet.html?id=1800947431489798163"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1800947431489798163-488');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1800947431489798163&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;&lt;a href="https://go.clerk.com/j68JCxO" rel="noopener noreferrer"&gt;Read the announcement →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Clerk + Backdrop Build
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://backdropbuild.com/" rel="noopener noreferrer"&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%2Fd5kyf870yfrjsecbetcu.png" alt="Clerk + Backdrop Build"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are excited to announce our partnership with Backdrop Build, a hackathon-centric accelerator that enables thousands of builders to take on the challenge of building and launching an idea in just 4 weeks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://backdropbuild.com/" rel="noopener noreferrer"&gt;Apply to Build →&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Summer Hackathon with Xata, Clerk, Inngest &amp;amp; Prisma
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://xata.io/blog/summer-launch-pxci-hackathon" rel="noopener noreferrer"&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%2F6sf27jvl7wnoxym6ewlj.jpg" alt="Summer Hackathon with Xata, Clerk, Inngest &amp;amp; Prisma"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've partnered with &lt;a href="https://xata.io/" rel="noopener noreferrer"&gt;Xata&lt;/a&gt;, &lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt;, and &lt;a href="https://www.inngest.com/" rel="noopener noreferrer"&gt;Inngest&lt;/a&gt; to bring you a two-week summer hackathon challenge. Winners will be announced on the &lt;a href="https://xata.io/discord" rel="noopener noreferrer"&gt;Xata Community Discord&lt;/a&gt; via live stream on July 19, 2024, at 12:00 PM EST.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xata.io/blog/summer-launch-pxci-hackathon" rel="noopener noreferrer"&gt;Read the announcement →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://go.clerk.com/jTwrt0D" rel="noopener noreferrer"&gt;Building a Hybrid Sign-Up/Subscribe Form with Stripe Elements&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/brianmmdev"&gt;@brianmmdev&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://go.clerk.com/tYj5zUj" rel="noopener noreferrer"&gt;Build a Modern Authenticated Chat Application with Next.js, Ably &amp;amp; Clerk&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/bookercodes"&gt;@bookercodes&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://go.clerk.com/pHZ9e9z" rel="noopener noreferrer"&gt;How to use Clerk with PostHog Identify in Next.js&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/brianmmdev"&gt;@brianmmdev&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://turso.tech/blog/working-with-clerk-and-per-user-databases" rel="noopener noreferrer"&gt;Working with Clerk and Per-User Databases&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/notrab"&gt;@notrab&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/faarehahmed/zoom-clone-using-nextjs-14-clerk-tailwindcss-streamsdk-4gh2"&gt;Zoom-Clone using NextJS-14, Clerk, TailwindCSS &amp;amp; StreamSDK&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/faarehahmed"&gt;@faarehahmed&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/conermurphy/how-to-build-your-own-chatgpt-clone-using-react-aws-bedrock-1827"&gt;How to Build Your Own ChatGPT Clone Using React &amp;amp; AWS Bedrock&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/conermurphy"&gt;@conermurphy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://go.clerk.com/G8KDmRu" rel="noopener noreferrer"&gt;How to Secure API Gateway Using JWT &amp;amp; Lambda Authorizers with Clerk&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/brianmmdev"&gt;@brianmmdev&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.netlify.com/guides/getting-started-with-react-vite-and-clerk-auth-on-netlify/" rel="noopener noreferrer"&gt;Getting Started with React, Vite &amp;amp; Clerk Auth on Netlify&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/pauliescanlon"&gt;@pauliescanlon&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.designengineer.xyz/posts/using-clerk-with-liveblocks-without-ais-help" rel="noopener noreferrer"&gt;Using Clerk with Liveblocks&lt;/a&gt; by &lt;a href="https://kejk.tech/" rel="noopener noreferrer"&gt;Karl Koch&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=N_uNKAus0II" rel="noopener noreferrer"&gt;Build a Finance SaaS Platform With Nextjs, React &amp;amp; Hono&lt;/a&gt; by &lt;a href="https://x.com/YTCodeAntonio" rel="noopener noreferrer"&gt;Code with Antonio&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@redouane.achouri/how-to-authenticate-api-requests-with-clerk-and-fastapi-6ac5196cace7" rel="noopener noreferrer"&gt;How to Authenticate API Requests with Clerk &amp;amp; FastAPI&lt;/a&gt; by &lt;a href="https://x.com/redouaneoachour" rel="noopener noreferrer"&gt;Redouane Achouri&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@syedahmedullahjaser/title-unlocking-the-power-of-convex-and-clerk-a-guide-to-seamless-authentication-and-data-beea3fbb52b8" rel="noopener noreferrer"&gt;Unlocking the Power of Convex &amp;amp; Clerk&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/syedahmedullah14"&gt;@syedahmedullah14&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://go.clerk.com/6HpPtVm" rel="noopener noreferrer"&gt;Build a team-based task manager with Next.js, Neon &amp;amp; Clerk&lt;/a&gt; by &lt;a class="mentioned-user" href="https://dev.to/brianmmdev"&gt;@brianmmdev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you have feedback or suggestions, we want to hear them! Let us know at &lt;a href="https://feedback.clerk.com/" rel="noopener noreferrer"&gt;feedback.clerk.com&lt;/a&gt;. For the latest on our product releases, follow &lt;a href="https://twitter.com/clerkdev" rel="noopener noreferrer"&gt;@ClerkDev on 𝕏&lt;/a&gt; or join the &lt;a href="https://clerk.com/discord" rel="noopener noreferrer"&gt;Clerk Community on Discord&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>clerk</category>
      <category>news</category>
    </item>
    <item>
      <title>Ultimate Guide to Magic Link Authentication</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Sat, 24 Feb 2024 04:05:00 +0000</pubDate>
      <link>https://forem.com/clerk/ultimate-guide-to-magic-link-authentication-10ag</link>
      <guid>https://forem.com/clerk/ultimate-guide-to-magic-link-authentication-10ag</guid>
      <description>&lt;p&gt;Data breaches and password overload have made companies and their users wary of using a traditional username/password authentication system. Companies know that handling user passwords is both technically challenging &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt; costly, as it requires stringent security measures, robust infrastructure for storage, and continuous monitoring to prevent unauthorized access. Users find it cumbersome to manage multiple complex passwords for various accounts, especially with the prevalence of methods like SSO, and often end up reusing passwords across different platforms, as a matter of convenience; this not only leaves the individual vulnerable, but also makes companies jobs of securing data that much harder, highlighting the critical requirements for improved security measures.&lt;/p&gt;

&lt;p&gt;Luckily, email magic links have emerged as an elegant solution to secure user sessions in a &lt;a href="https://clerk.com/docs/authentication/configuration/sign-in-options#authentication-strategy"&gt;passwordless&lt;/a&gt; context. Their rising popularity is underpinned by their dual advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An augmented security posture through the minimization of phishing opportunities and password theft.&lt;/li&gt;
&lt;li&gt;An optimized user experience devoid of the need to memorize and manage many login credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are Magic Links?
&lt;/h2&gt;

&lt;p&gt;Magic links are a token-based authentication (TBA) strategy that uses a unique, time-sensitive URL, which leverages a securely-generated token to serve as a credential for user authentication.&lt;/p&gt;

&lt;p&gt;The links are sent directly to the user's registered email or phone number, providing a straightforward, secure authentication method. When a user clicks on a magic link, the embedded token is validated against the server to authenticate the user's identity. This process, by design, eliminates the traditional risks associated with password-based systems and simplifies the login experience for the user.&lt;/p&gt;

&lt;p&gt;In this guide, we will show you why you should consider authentication with magic links and how they work at a high level, before going through &lt;a href="https://clerk.com/blog/secure-authentication-nextjs-email-magic-links"&gt;a Next.js App Router implementation&lt;/a&gt; to show how you can add magic links to your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benefits of Magic Links
&lt;/h2&gt;

&lt;p&gt;Magic links bolster the security architecture of authentication systems by adopting a token-based, stateless interaction model. Each link is cryptographically unique and typically accompanied by an expiration timestamp, making it resilient against replay attacks. Given their ephemeral nature, even if a magic link were to be intercepted or exposed, its short-lived validity constrains the window of opportunity for malicious exploitation.&lt;/p&gt;

&lt;p&gt;But there are a few other benefits to magic links for companies and users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Streamlined user experience&lt;/strong&gt;. Authentication with magic links eliminate the cognitive burden of remembering and managing many complex passwords, by providing a single-click authentication pathway. This frictionless access modality can enhance user engagement and satisfaction, reducing abandonment rates during the sign-in or sign-up processes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance and data protection&lt;/strong&gt;. By using magic links to minimize passwords, organizations can reduce the risk of breaches involving personal data and avoid the consequences of compromised credentials, which can include heavy fines and reputational damage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduction in credential exposure&lt;/strong&gt;. With magic links, the threat of credential stuffing attacks—where compromised credentials are used to gain unauthorized access to multiple user accounts—is mitigated. Since magic links do not rely on reusable passwords, the standard vector of credential exposure is eliminated, enhancing overall system security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved accessibility&lt;/strong&gt;. For users with disabilities or those less familiar with technology, magic links offer a more accessible authentication mode. The simplification of the login process—replacing typing and memory demands with a single action—can be particularly advantageous for individuals facing physical or cognitive challenges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Magic Links Work
&lt;/h2&gt;

&lt;p&gt;A magic link is structurally composed of two critical elements: the URL, which provides the link for the user's web interaction, and the embedded token, a cryptographically-generated string serving as the temporary credential.&lt;/p&gt;

&lt;p&gt;In essence, a typical magic link may resemble the following structure:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://example.com/authenticate?token=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this example, the &lt;code&gt;token&lt;/code&gt; query parameter carries the weight of authentication, substantiating the user's claim without revealing identity until verified by the server.&lt;/p&gt;

&lt;p&gt;Here’s an example of how the process works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2pqarqojlkww9ijuq5c0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2pqarqojlkww9ijuq5c0.png" alt="Example Magic Link Authentication Process" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Token Validity
&lt;/h3&gt;

&lt;p&gt;Magic links are designed to become invalid under certain conditions for security purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expiration&lt;/strong&gt;: The token embedded in the link has a short lifespan and is often configurable based on application security requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage Limitation&lt;/strong&gt;: Once a magic link is used, it becomes invalid, preventing multiple uses, which could lead to unauthorized access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revocation&lt;/strong&gt;: The system can programmatically revoke a token, thus invalidating the magic link in response to specific triggers or anomalies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Token Generation
&lt;/h3&gt;

&lt;p&gt;The lifecycle of a magic link commences with the generation of a unique token. This process employs cryptographic algorithms to ensure each token is a random, high-entropy string, making it virtually impossible to predict or reproduce through brute force or other cryptographic attacks. Typically, the token generation utilizes &lt;a href="https://en.wikipedia.org/wiki/HMAC"&gt;HMAC&lt;/a&gt; or AES combined with a &lt;a href="https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator"&gt;CSPRNG&lt;/a&gt; to guarantee the robustness of the token against collision and preimage attacks.&lt;/p&gt;

&lt;p&gt;Once generated, the token is stored on the server along with metadata that includes the user's identifier, the token's expiration time, and any other relevant session data. The magic link is then composed by appending the token to a predetermined URL structure, forming a complete, ready-to-use hyperlink.&lt;/p&gt;

&lt;p&gt;This link is dispatched to the user's email address or phone number via SMTP or SMS protocols. The communication channel must be secure, leveraging TLS for email and similarly secure protocols for SMS to safeguard the link during transit.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Interaction and Verification
&lt;/h3&gt;

&lt;p&gt;pically interacts with the magic link by clicking on it, which initiates a secure request to the service's endpoint. The service extracts the token from the URL and verifies it against the stored data. This verification process involves several checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authenticity&lt;/strong&gt;: Validates that the token matches a recently generated and stored token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrity&lt;/strong&gt;: Ensures the token has not been tampered with during transit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeliness&lt;/strong&gt;: Confirms the token has not expired based on the predefined validity period.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-reuse&lt;/strong&gt;: Ensures the token has not been used before, adhering to the principle of one-time use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server considers the authentication request legitimate if the token passes these checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Session Initialization
&lt;/h3&gt;

&lt;p&gt;Post-verification, the server establishes a session for the user. This session is typically stateless, with a new session token or cookie generated to maintain the user's authenticated state in the application. This session token is separate from the magic link token. It has its own security considerations, such as being HttpOnly and Secure, to prevent access via client-side scripts and ensure transmission over HTTPS only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Magic Link System
&lt;/h2&gt;

&lt;p&gt;Let’s create our own email with magic link authentication. In this example, we’ll use &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; 13 with the App Router. We’ll also use &lt;a href="https://supabase.com/"&gt;Supabase&lt;/a&gt; for our backend database to store users and tokens.&lt;/p&gt;

&lt;p&gt;First, let’s create a new Next.js project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;
  Follow the prompts to select how you want to configure your app, but be sure to select “Yes” for “Would you like to&lt;br&gt;
  use App Router? (recommended)”.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Once you have created and configured your project, &lt;code&gt;cd&lt;/code&gt; into the created directory and run &lt;code&gt;npm run dev&lt;/code&gt; to start it. You’ll see just the default Next.js homepage when you load &lt;code&gt;localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Before we start building out our project, we need to install a few dependencies that we’ll use. Install them using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;nodemailer jsonwebtoken @supabase/supabase-js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What do these do?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/nodemailer"&gt;nodemailer&lt;/a&gt;: A library that allows for easy email sending.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/jsonwebtoken"&gt;jsonwebtoken&lt;/a&gt;: A library to implement JSON Web Tokens for secure data transmission.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@supabase/supabase-js"&gt;@supabase/supabase-js&lt;/a&gt;: The Supabase client library for Javascript.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With those installed, let’s open up the project in an IDE. In total, we’re going to have two pages, two API routes, and two helper libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;page.js&lt;/code&gt; will be our homepage. It will have a simple email field, and will call our &lt;code&gt;requestMagicLink&lt;/code&gt; API reroute.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;requestMagicLink.js&lt;/code&gt; will be the API route that will create our magic link, send it to the email address passed from &lt;code&gt;page.js&lt;/code&gt;, and save the token on our Supabase database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify.js&lt;/code&gt; will be the API route called when the user clicks on the magic link in their email. It will verify the token and redirect the user to the protected dashboard page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dashboard/page.js&lt;/code&gt; will be a simple mock “protected page” (that would require the user to be logged in to view it).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/database.js&lt;/code&gt; will be a number of database helper functions to save, load, and delete Supabase data.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lib/supabaseClient.js&lt;/code&gt; will set up our Supabase client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  page.js
&lt;/h3&gt;

&lt;p&gt;Let’s start with what the user will see, &lt;code&gt;page.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/page.js&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RequestMagicLink&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="o"&gt;=&amp;gt;&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="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/auth/requestMagicLink&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&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="s1"&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="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Magic link sent! Check your email to log in.&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setMessage&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="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An error occurred. Please try again.&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="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="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;An error occurred. Please try again.&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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Enter your email"&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sending...&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="s1"&gt;Send Magic Link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code example above, we are requesting the &lt;code&gt;RequestMagicLink&lt;/code&gt; component, which is responsible for handling the functionality of requesting a magic link via an email address. This component provides a UI for users to request a magic link. Users enter their email and submit the form, triggering a request to the server. Feedback is given to the user through messages and button state changes during the process.&lt;/p&gt;

&lt;p&gt;First, we set up the state variables for the page. In the Next.js App Router, you can only use &lt;code&gt;useState&lt;/code&gt; in client-side components. As all components default to server-side, we have to use the &lt;code&gt;“use client”&lt;/code&gt; directive at the top of the file to show this page has to be rendered on the client. We have three state variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;email&lt;/code&gt;: Stores the user's email address. It's updated every time the user types into the email input field.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;message&lt;/code&gt;: Used to display messages to the user, like confirmation or error messages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isLoading&lt;/code&gt;: Indicates whether the request is being processed. It's used to disable the submit button and change the button text while the request is in progress.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, we have the &lt;code&gt;handleSubmit&lt;/code&gt; function. This is triggered when the form is submitted, and initially prevents the default form submission action with &lt;code&gt;event.preventDefault()&lt;/code&gt;. It then sets &lt;code&gt;isLoading&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; to indicate the start of an asynchronous operation and clears any previous messages stored in &lt;code&gt;message&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, comes the core part of the component. We make an async &lt;code&gt;POST&lt;/code&gt; request to the &lt;code&gt;/api/auth/requestMagicLink&lt;/code&gt; endpoint with the user's email in the request body. We then update the &lt;code&gt;message&lt;/code&gt; state based on the success or failure of the request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the request is successful (&lt;code&gt;response.ok&lt;/code&gt; is true), it sets a success message.&lt;/li&gt;
&lt;li&gt;If the request fails, it displays an error message, either from the response or a generic error message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have some basic error catching, and then set &lt;code&gt;isLoading&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The actual form presented to the user is basic, with just an email input field that updates the &lt;code&gt;email&lt;/code&gt; state variable on change and a submit button that is disabled and changes its text based on the &lt;code&gt;isLoading&lt;/code&gt; state. We have an &lt;code&gt;onSubmit&lt;/code&gt; event handler linked to &lt;code&gt;handleSubmit&lt;/code&gt; to send the form details and a paragraph that displays any messages stored in the &lt;code&gt;message&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;page.js&lt;/code&gt; file calls &lt;code&gt;requestMagicLink.js&lt;/code&gt;; let’s dig into that, next.&lt;/p&gt;

&lt;h3&gt;
  
  
  requestMagicLink.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/auth/requestMagicLink.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;nodemailer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;saveToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getUserByEmail&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../../../lib/database&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUserByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&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="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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User note found&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="c1"&gt;// Create a magic link token&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="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&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;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1h&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headersList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;headers&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;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headersList&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;host&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;magicLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/auth/verify?token=&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="c1"&gt;// Save token in your database with an expiration time&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveToken&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="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="c1"&gt;// Set up email transporter and send the magic link&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// upgrade later with STARTTLS&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="o"&gt;&amp;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;await&lt;/span&gt; &lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your Magic Link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Click here to log in: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;magicLink&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="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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Magic link sent!&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example code handles the API requests related to generating and sending the magic link for user authentication. Let's go through the major components and functions of the code.&lt;/p&gt;

&lt;p&gt;The function takes &lt;code&gt;req&lt;/code&gt; (request) and &lt;code&gt;res&lt;/code&gt; (response) objects as parameters, and then extracts the &lt;code&gt;email&lt;/code&gt; from the request's JSON body. We then use &lt;code&gt;getUserByEmail&lt;/code&gt; from &lt;code&gt;lib/database.js&lt;/code&gt; to search for a user in the database using the provided email. If the user doesn’t exist we send a JSON response indicating the user was not found.&lt;/p&gt;

&lt;p&gt;The function then creates the token using &lt;code&gt;jsonwebtoken&lt;/code&gt; to create a JWT with the user's email, signing it with a secret from environment variables and setting an expiration of 1 hour. With that token we can create our magic link. We retrieve the host from the request headers and construct a URL with the generated token as a query parameter.&lt;/p&gt;

&lt;p&gt;The generated token is then saved in the database with an associated user ID using &lt;code&gt;saveToken&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After that, we configure a &lt;code&gt;nodemailer&lt;/code&gt; transporter with SMTP settings (host, port, security, and authentication credentials) and send an email to the user with the magic link authentication.&lt;/p&gt;

&lt;p&gt;Finally, we send back a JSON response indicating that the magic link was sent.&lt;/p&gt;

&lt;p&gt;Here, we using one of our helper libraries, &lt;code&gt;database.js&lt;/code&gt;. Let’s go through that next.&lt;/p&gt;

&lt;h3&gt;
  
  
  database.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/database.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./supabaseClient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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="o"&gt;=&amp;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;data&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;magic_tokens&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;token&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="na"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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;3600000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// expires in 1 hour&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="nx"&gt;error&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUserByEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&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="kd"&gt;const&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="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;single&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;data&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="nx"&gt;error&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTokenData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="o"&gt;=&amp;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;data&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;magic_tokens&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;single&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="nx"&gt;error&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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="nx"&gt;expires_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Token expired&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deleteUserToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &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="o"&gt;=&amp;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;data&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;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;magic_tokens&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&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="k"&gt;if &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="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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a collection of utility functions designed to interact with Supabase. Each function is designed to interact with specific tables in a Supabase database, which each handle different aspects like token generation, user lookup, token validation, and cleanup. Let's break down each function:&lt;/p&gt;

&lt;h3&gt;
  
  
  saveToken
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: To save a newly generated token in the database, fulfilling the requirements for secure token management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameters&lt;/strong&gt;: Accepts &lt;code&gt;userId&lt;/code&gt; (the user's ID) and &lt;code&gt;token&lt;/code&gt; (the magic link token).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functionality&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Inserts a new record into the &lt;code&gt;magic_tokens&lt;/code&gt; table in Supabase with the user's ID, the token, and an expiration time (set to 1 hour ahead of the current time).&lt;/li&gt;
&lt;li&gt;If an error occurs during the database operation, it throws an error with the message received from Supabase.&lt;/li&gt;
&lt;li&gt;Returns the data received from Supabase if the operation is successful.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  getUserByEmail
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: To fetch a user's data from the database using their email address.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameters&lt;/strong&gt;: Accepts &lt;code&gt;email&lt;/code&gt;, which is the email address of the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functionality&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Queries the &lt;code&gt;users&lt;/code&gt; table in Supabase for a record matching the provided email address.&lt;/li&gt;
&lt;li&gt;Logs the email and the data received for debugging purposes.&lt;/li&gt;
&lt;li&gt;If an error occurs, it throws an error with the message from Supabase.&lt;/li&gt;
&lt;li&gt;Returns the user data if a matching record is found.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  getTokenData
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: To retrieve token data from the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameters&lt;/strong&gt;: Accepts &lt;code&gt;token&lt;/code&gt;, the magic link token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functionality&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Queries the &lt;code&gt;magic_tokens&lt;/code&gt; table for a record with a token matching the provided one.&lt;/li&gt;
&lt;li&gt;Checks if the token has expired by comparing the &lt;code&gt;expires_at&lt;/code&gt; field with the current time. If the token is expired, it throws an error.&lt;/li&gt;
&lt;li&gt;If an error occurs during the query, it throws an error with the message from Supabase.&lt;/li&gt;
&lt;li&gt;Returns the token data if a matching and non-expired token is found.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  deleteUserToken
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purpose&lt;/strong&gt;: To delete a token from the database, complying with the requirements for token lifecycle management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameters&lt;/strong&gt;: Accepts &lt;code&gt;token&lt;/code&gt;, the magic link token to be deleted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functionality&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Deletes the record from the &lt;code&gt;magic_tokens&lt;/code&gt; table that matches the provided token.&lt;/li&gt;
&lt;li&gt;If an error occurs during this operation, it throws an error with the message from Supabase.&lt;/li&gt;
&lt;li&gt;Returns the data received from Supabase upon successful deletion.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This file, in turn, is calling on the other helper library &lt;code&gt;supabaseClient.js&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  supabaseClient.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/supabaseClient.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabaseUrl&lt;/span&gt; &lt;span class="o"&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;SUPABASE_URL&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabaseAnonKey&lt;/span&gt; &lt;span class="o"&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;SUPABASE_ANON_KEY&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supabaseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supabaseAnonKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a straightforward setup for initializing a client instance of Supabase in a Javascript application.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;export const supabase = createClient(supabaseUrl, supabaseAnonKey);&lt;/code&gt;creates and exports an instance of the Supabase client, which is used to interact with your Supabase project. The &lt;code&gt;createClient&lt;/code&gt; function takes the Supabase URL and the anonymous key as arguments and returns the initialized client.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SUPABASE_URL&lt;/code&gt; and &lt;code&gt;SUPABASE_ANON_KEY&lt;/code&gt; (along with &lt;code&gt;JWT_SECRET&lt;/code&gt;) are pulled from our environment variables file, &lt;code&gt;.env.local&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  .env.local
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;supabase-url&amp;gt;
&lt;span class="nv"&gt;SUPABASE_ANON_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;supabase-anon-key&amp;gt;
&lt;span class="nv"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;jwt-secret&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get &lt;code&gt;SUPABASE_URL&lt;/code&gt; and &lt;code&gt;SUPABASE_ANON_KEY&lt;/code&gt; from your Supabase dashboard.&lt;/p&gt;

&lt;p&gt;You’ll see from above, we also need to set up two tables in Supabase. We need a &lt;code&gt;users&lt;/code&gt; table with an &lt;code&gt;email&lt;/code&gt; field, and a &lt;code&gt;magic_tokens&lt;/code&gt; table with these fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;user_id&lt;/code&gt;, which comes from the users table.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;token&lt;/code&gt;, which is the generated token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expires_at&lt;/code&gt;, to add an expiry time to the token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s get back to the main code. If we fill out the email field on the homepage and click “Send Magic Link,” &lt;code&gt;requestMagicLink&lt;/code&gt; will be called and an email sent to the entered email address. Clicking on that link calls the &lt;code&gt;verify.js&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  verify.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/api/auth/verify.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getTokenData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deleteUserToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../../../lib/database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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;req&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&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="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&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;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Logs the token value&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getTokenData&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="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;tokenData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid or expired token&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&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;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;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Delete or invalidate the token&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;deleteUserToken&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="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="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Or wherever you want to redirect the user after login&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines the API route for verifying the magic link token as part of an authentication process.&lt;/p&gt;

&lt;p&gt;The code is designed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract a token from the request URL.&lt;/li&gt;
&lt;li&gt;Verify the token's validity.&lt;/li&gt;
&lt;li&gt;Perform actions based on the token's validity (such as user redirection or error response).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code retrieves the token from the URL query string by splitting the URL at the &lt;code&gt;=&lt;/code&gt; character and taking the second part (&lt;code&gt;req.url.split("=")[1]&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;getTokenData&lt;/code&gt; function is called to fetch the token data from the database. If no data is found (implying the token is invalid or expired), it returns a JSON response with an error message. Using &lt;code&gt;jwt.verify&lt;/code&gt;, we validate the token against the secret key stored in &lt;code&gt;process.env.JWT_SECRET&lt;/code&gt;. This also extracts the payload (&lt;code&gt;email&lt;/code&gt;) from the token.&lt;/p&gt;

&lt;p&gt;We then call &lt;code&gt;deleteUserToken&lt;/code&gt; to remove the token from the database, ensuring it cannot be reused. Finally, it redirects the user to the &lt;code&gt;/dashboard&lt;/code&gt; route upon successful token verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  dashboard/page.js
&lt;/h3&gt;

&lt;p&gt;There isn’t much to &lt;code&gt;dashboard/page.js&lt;/code&gt;:&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="c1"&gt;// app/dashboard/page.js&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a production application, this is the page you would build out in your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;There is a lot to think about with email magic links. The above example doesn’t go into robust authentication checking with the user, nor does it add rate-limiting or have significant error handling. An unfortunate truth of authentication is that there is no silver bullet. While magic links take away the headache of managing user credentials, you still have to manage token generation, storage and expiry. Plus, you are still managing and storing user data.&lt;/p&gt;

&lt;p&gt;Any good developer is going to take the time to understand, at least, the basic strategies leveraged by the tools they use to speed up their processes (good work understanding magic links!). With that being said, a simpler and more secure alternative to all the code above is to use &lt;a href="https://clerk.com"&gt;Clerk&lt;/a&gt;. We built Clerk to make it quick and easy to add advanced authentication techniques into your application. To learn more about magic links, visit our &lt;a href="https://clerk.com/docs/custom-flows/magic-links"&gt;documentation&lt;/a&gt; or this article on &lt;a href="https://clerk.com/blog/secure-authentication-nextjs-email-magic-links"&gt;implementing magic links with Next.js and Clerk&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Exploring the Intricacies of OTP Authentication in Next.js</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Mon, 24 Jul 2023 01:35:51 +0000</pubDate>
      <link>https://forem.com/clerk/exploring-the-intricacies-of-otp-authentication-in-nextjs-3hlg</link>
      <guid>https://forem.com/clerk/exploring-the-intricacies-of-otp-authentication-in-nextjs-3hlg</guid>
      <description>&lt;p&gt;Passwords aren’t great. They’re often weak, reused, and need to be stored indefinitely making them susceptible to attacks and leaks. &lt;a href="https://haveibeenpwned.com/"&gt;have i been pwned?&lt;/a&gt; tells me passwords associated with just one of my email addresses have been leaked 18 times, and I have over 800 individual passwords stored in my password manager.&lt;/p&gt;

&lt;p&gt;This is why &lt;a href="https://clerk.com/blog/secure-authentication-nextjs-email-magic-links"&gt;magic links&lt;/a&gt;, &lt;a href="https://clerk.com/blog/social-sso-in-next-js"&gt;SSO&lt;/a&gt;, and one-time passwords (OTPs) are becoming standard authentication methods. They provide better or additional security for your users.&lt;/p&gt;

&lt;p&gt;Here we’re going to look at OTPs. One-time passwords are a great option for improving the security of your application. Let’s go through exactly what one-time passwords are, how they can improve the security of your application, the best practices for using them in authentication, and how you can &lt;a href="https://clerk.com/features/email-sms-passcodes-otp"&gt;implement OTPs in Next.js&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a one-time password?
&lt;/h2&gt;

&lt;p&gt;OTP or One-Time Password authentication is a method in which a unique code is sent to a user's device and the user enters this code into an application to verify their identity. It’s valid for only one login session and usually time-limited.&lt;/p&gt;

&lt;p&gt;There are two ways OTPs are implemented:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;As the main factor for logging in a user. If you don’t want to use any username/password combinations in your authentication flow, you can send a one-time password for log in. You can also use it as an alternative to username/password. This is not particularly common, but can be seen on sites such as local social networking site Nextdoor.&lt;/li&gt;
&lt;li&gt;As an additional step in &lt;a href="https://clerk.com/features/multifactor-authentication"&gt;multi-factor authentication (MFA)&lt;/a&gt;. After a user has logged in with their username/password or another authentication provider (such as Google, Twitter, or GitHub), they are then sent an OTP to a registered email address or phone number as a secondary layer of security for applications. This second option is much more common.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You’d think that one-time passwords would be long and complicated like the suggested passwords from a password manager. But because they are transient in nature and rarely subject to dictionary or brute force attacks, they can be much simpler. OTPs can have different formats, but they usually consist of a series of alphanumeric characters or purely numeric characters. The length of OTPs can vary, but a common format is a 6 or 8-digit numeric code.&lt;/p&gt;

&lt;p&gt;The choice between numeric and alphanumeric OTPs depends on the context and requirements. Numeric OTPs can be easier to enter, especially on mobile devices, which makes them a popular choice for SMS-based OTPs. However, alphanumeric OTPs provide a larger possible combination set for the same number of characters, which can be more secure against brute-force attacks.&lt;/p&gt;

&lt;p&gt;There are six main steps in the OTP flow:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---P6QYKH_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m0tjsf583gsxg80jglro.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---P6QYKH_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m0tjsf583gsxg80jglro.png" alt="OTP Flow" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt;. The user initiates the OTP process. This could be a login attempt, a transaction verification, or any other scenario where identity needs to be confirmed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTP generation&lt;/strong&gt;. The server generates an OTP. This is typically a random string or number that is time-limited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTP delivery&lt;/strong&gt;. The generated OTP is sent to the user through a predetermined method. This could be an SMS to their phone, an email to their registered email address, or generated in a hardware token or software app, such as Google Authenticator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User input&lt;/strong&gt;. The user receives the OTP and enters it into the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OTP verification&lt;/strong&gt;. The server then verifies the OTP. The server also verifies that the OTP is used within the allowed time period, and hasn't been used before.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;: If the OTP is verified, the server authenticates the user and allows them to proceed. If the OTP is incorrect or expired, the server rejects the request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Though this is only six steps, there are a lot of moving parts in OTP use. You need extra frontend UI design and logic to handle the OTP inputs; you need to integrate a delivery mechanism such as SMS, email, or an authenticator app; you need extra authentication logic to handle OTP errors; and you need the OTP generation and verification logic as well.&lt;/p&gt;

&lt;p&gt;There are several ways to generate and verify an OTP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time-Synchronized&lt;/strong&gt;: In this method, the OTP is generated by applying a cryptographic hash function to a shared secret and the current time, typically measured in intervals of 30 seconds. The Google Authenticator app uses this method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counter-Based&lt;/strong&gt;: Here, the OTP is generated by hashing a shared secret with a counter which increments with each new OTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Algorithm-Based&lt;/strong&gt;: In this method, a mathematical algorithm is applied to the previous password to generate the new one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a time-based or counter-based OTP was used, the server repeats the same OTP generation process during verification and checks if the received OTP matches the one it generated.&lt;/p&gt;

&lt;p&gt;These algorithms aren’t easy to implement. As you are dealing with an authentication factor, there are specific designs that you must use and RFCs that you must follow, such as this one for &lt;a href="https://datatracker.ietf.org/doc/html/rfc6238"&gt;Time-Based OTPs&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt; This is the biggest challenge around building your own one-time password (or any authentication) system–implementation.&lt;/p&gt;
&lt;h2&gt;
  
  
  The security benefits (and challenges) of OTPs
&lt;/h2&gt;

&lt;p&gt;You can quickly see the complexity involved in setting up OTP authentication. But doing so is worth it as they provide two key security benefits.&lt;/p&gt;
&lt;h3&gt;
  
  
  Risk mitigation
&lt;/h3&gt;

&lt;p&gt;The mitigation of risk with OTPs comes from two different avenues.&lt;/p&gt;

&lt;p&gt;The first is mitigating the risk associated with static passwords. Traditional static passwords, if stolen, provide ongoing access until they are changed. OTPs are dynamic and expire after a single use or after a short period of time, limiting the potential damage if they are intercepted or stolen. Another problem is users reusing the same password across multiple services. If one service is compromised, all accounts using the same password are at risk. OTPs eliminate this risk because they are unique for each login session.&lt;/p&gt;

&lt;p&gt;The second is the reduction of attack vectors. Because OTPs are typically time-limited, it makes brute-force attacks infeasible. An attacker doesn't have the time to try all possible combinations before the password expires. They also help with phishing. Even if a user is tricked into entering their OTP into a phishing site, the attacker can't reuse that OTP to gain future access to the account.&lt;/p&gt;

&lt;p&gt;Additionally, since an OTP is valid for only one login session or transaction, it cannot be reused, preventing replay attacks. In a replay attack, an attacker tries to reuse a password that was intercepted in a previous session. However, if there's a flaw in the system's design where the OTP doesn't expire immediately after use or isn't time-bound, there's a possibility for replay attacks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Identity security and verification
&lt;/h3&gt;

&lt;p&gt;OTPs also play a significant role in enhancing identity security and verification processes. They provide an additional layer of protection beyond traditional static passwords, aiding in the confirmation of a user's identity in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OTPs are often used as part of a two-factor authentication process. In addition to a traditional username and password (something the user knows), the user must enter an OTP (something the user has, typically their mobile device). This confirms that the user has access to a specific device (like a phone) that is associated with the account, verifying the identity of the user.&lt;/li&gt;
&lt;li&gt;OTPs are commonly used in financial transactions or account changes to verify the identity of the user making the transaction. For example, when conducting a bank transfer or changing account details, an OTP might be sent to the registered mobile number or email address. The user enters the OTP to confirm the transaction, verifying that they are the account holder.&lt;/li&gt;
&lt;li&gt;OTPs are often used in password recovery processes to verify the user's identity. The service sends an OTP to the user's registered email address or mobile number, which the user then enters to verify their identity and proceed with resetting their password.&lt;/li&gt;
&lt;li&gt;When a user logs in from a new device, an OTP might be sent to their registered contact information. The user must enter the OTP to verify they have access to the registered device, confirming their identity and that the new device is trusted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By integrating OTPs into authentication and verification processes, services can add an extra level of security and significantly reduce the risk of unauthorized access or identity theft.&lt;/p&gt;
&lt;h2&gt;
  
  
  Best practices for using one-time passwords
&lt;/h2&gt;

&lt;p&gt;OTPs aren’t infallible, though. But the associated risks are generally around poor implementation rather than inherent to the method. To ensure OTP effectiveness and avoid some of the above potential vulnerabilities, you can follow some best practices.&lt;/p&gt;
&lt;h3&gt;
  
  
  Basic security practices
&lt;/h3&gt;

&lt;p&gt;These practices are the principles of any good system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secure delivery channel&lt;/strong&gt;. Use a secure delivery channel for sending the OTP. If you're sending the OTP via email or SMS, ensure the communication channel is secure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypt communication&lt;/strong&gt;. Ensure all communications between the client and the server are done over HTTPS to prevent any interception of the OTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use secure storage&lt;/strong&gt;. When storing OTPs on the server side, consider hashing them. This ensures that even if someone gains access to your storage, they cannot obtain the actual OTPs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Good OTP design
&lt;/h3&gt;

&lt;p&gt;These relate to how well you design your OTP algorithm and logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use a strong OTP&lt;/strong&gt;. Use an OTP that is long and complex enough to resist brute-force attacks. An OTP with at least 6 digits is usually recommended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-Bound OTP&lt;/strong&gt;. Make sure the OTP is valid only for a short period of time. This reduces the window an attacker has to use a stolen OTP. Usually, OTPs are valid for about 2-10 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit OTP attempts&lt;/strong&gt;. Implement rate limiting on your OTP endpoints to protect against brute force attacks. After a certain number of incorrect attempts, either block the user or implement a cool-down period.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry after use.&lt;/strong&gt; The OTP should expire immediately after it has been used once, to prevent replay attacks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Good UX
&lt;/h3&gt;

&lt;p&gt;These help users use your OTP and make sure they don’t turn it off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backup codes&lt;/strong&gt;. For applications using OTP as a second factor, provide backup codes that the user can write down and use if they lose access to their OTP delivery method (like losing their phone).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback Options&lt;/strong&gt;. In case the primary delivery channel fails (e.g., SMS not being delivered), have a secondary option like email or voice call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also consider educating users about using OTPs. One of the main threats to OTP use are physical–if an attacker steals a user's phone then they’ll have access to the SMS or email used with OTPs. Or a fraudster can fake a user’s identity to trick a telecoms company into assigning a new SIM with the user’s phone number to them (known as &lt;a href="https://en.wikipedia.org/wiki/SIM_swap_scam"&gt;SIM swapping&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;While OTPs can greatly enhance security, they are not foolproof. Implementation within a broader security strategy is key.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up OTP Authentication in Next.js
&lt;/h2&gt;

&lt;p&gt;Let’s walk through setting up a one time password system within Next.js. If you already have a Next.js app up and running you can add this code directly. Otherwise create a new app using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll also need to use a few modules to help us with our OTPs, namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="http://twilio.com"&gt;Twilio&lt;/a&gt;&lt;/strong&gt;. Twilio allows us to send SMS messages programmatically. Here we’re going to use it to send the OTP to the user's phone number via SMS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/bcryptjs"&gt;bcryptjs&lt;/a&gt;&lt;/strong&gt;. bcryptjs is a JavaScript library for hashing and comparing passwords. We’re going to use it to hash the OTP before storing it in the database for added security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.notion.so/ae2dfc8eb2b645a0b54e607e5e0a2835?pvs=21"&gt;MongoDB&lt;/a&gt;&lt;/strong&gt;. MongoDB is a NoSQL database that we’ll use to store hashed OTPs along with the associated phone numbers and expiry times.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://upstash.com"&gt;&lt;strong&gt;Upstash&lt;/strong&gt;&lt;/a&gt;. Upstash is a serverless data platform. We’re going to use it’s rate limiter and Redis functionality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install these with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="nx"&gt;twilio&lt;/span&gt; &lt;span class="nx"&gt;bcryptjs&lt;/span&gt; &lt;span class="nx"&gt;mongodb&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;upstash&lt;/span&gt;&lt;span class="sr"&gt;/ratelimit @upstash/&lt;/span&gt;&lt;span class="nx"&gt;redis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Twilio and MongoDB, you’ll also need to sign up for accounts and then need your &lt;strong&gt;TWILIO_ACCOUNT_SID&lt;/strong&gt;, your &lt;strong&gt;TWILIO_AUTH_TOKEN&lt;/strong&gt;, and your &lt;strong&gt;MONGODB_URI&lt;/strong&gt;. For Twilio, you’ll also need to buy a TWILIO_PHONE_NUMBER that will be used to send your SMS messages.&lt;/p&gt;

&lt;p&gt;You’ll also need an account with Upstash, and then your &lt;strong&gt;UPSTASH_REDIS_REST_URL&lt;/strong&gt; and &lt;strong&gt;UPSTASH_REDIS_REST_TOKEN&lt;/strong&gt; variables.&lt;/p&gt;

&lt;p&gt;With that done, we’ll first create the API route that will generate our OTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/generateOTP.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;twilio&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;twilio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bcryptjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MongoClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mongodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Method Not Allowed&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Generate a six digit number using the crypto module&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;randomInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;999999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Hash the OTP&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashedOtp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Initialize the Twilio client&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;twilio&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;TWILIO_ACCOUNT_SID&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;TWILIO_AUTH_TOKEN&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Send the OTP via SMS&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&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;`Your OTP is: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;otp&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;from&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;TWILIO_PHONE_NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// your Twilio number&lt;/span&gt;
      &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// your user's phone number&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Store the hashed OTP in the database along with the phone number and expiry time&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongoClient&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;MongoClient&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;MONGODB_URI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongoClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&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;otps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mongoClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;otps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;otps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hashedOtp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;expiry&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="nx"&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;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&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="c1"&gt;// OTP expires after 10 minutes&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongoClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Respond with a success status&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="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;err&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not send OTP&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this code we initially import all our dependencies, then create a handler function for our POST endpoint. The body of the POST request will contain the phone number of the user that we’ll get from the frontend. Within the endpoint, we’re doing a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a six-digit random OTP&lt;/li&gt;
&lt;li&gt;Hashing that OTP with bcryptjs for storage&lt;/li&gt;
&lt;li&gt;Creating a Twilio client and then sending the OTP to the user’s phone number&lt;/li&gt;
&lt;li&gt;Creating a MongoDB client and storing the OTP along with the user’s phone number and an expiry time for the password.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Is this best practice? Absolutely not. We are doing a few things right, such as setting an expiry time on the OTP and hashing them. But our OTP generating ‘algorithm’ is laughably simple.&lt;/p&gt;

&lt;p&gt;Let’s quickly create a frontend for this now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&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;OTPGenerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="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;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPhone&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setOTP&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;otpSent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setOtpSent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;handleSendOTP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="o"&gt;=&amp;gt;&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;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// reset message&lt;/span&gt;

    &lt;span class="k"&gt;try&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/generateOTP&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OTP has been sent to your phone.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setOtpSent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;setMessage&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="nx"&gt;error&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="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred. Please try again.&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleVerifyOTP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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="o"&gt;=&amp;gt;&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;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// reset message&lt;/span&gt;

    &lt;span class="k"&gt;try&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/verifyOTP&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;otp&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OTP verification successful!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setOtpSent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setPhone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;setOTP&lt;/span&gt;&lt;span class="p"&gt;(&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="k"&gt;else&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;setMessage&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="nx"&gt;error&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="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setMessage&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="nx"&gt;console&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;otpSent&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSendOTP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Phone Number:
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"tel"&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setPhone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;required&lt;/span&gt;
            &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sending...&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;Send OTP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleVerifyOTP&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            Enter OTP:
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;otp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setOTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="na"&gt;required&lt;/span&gt;
            &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Verifying...&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;Verify OTP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;OTPGenerator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All this code will just show a single form on the page. On first load this form will ask for the user’s phone number.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kjB3a_8i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fvxec0czed8rauexxyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kjB3a_8i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fvxec0czed8rauexxyh.png" alt="Send OTP" width="800" height="82"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the user enters their phone number and hits submit, the above generateOTP endpoint will be called. This will send the OTP to the user’s phone number:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fCrWpVbt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4eu23ex5p00vi4aqs21w.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fCrWpVbt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4eu23ex5p00vi4aqs21w.jpeg" alt="generateOTP" width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hitting submit will also change the form to accept the OTP as the input. The user can then check their phone and enter the six-digit code: and hit submit again to send the OTP and phone number to a verifyOTP endpoint for verification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/verifyOTP.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bcryptjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MongoClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mongodb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@upstash/ratelimit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@upstash/redis&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;rateLimiter&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;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromEnv&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="na"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slidingWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3 s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;user_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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-forwarded-for&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user_ip&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;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Too Many Requests&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Method Not Allowed&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;mongoClient&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;MongoClient&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;MONGODB_URI&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongoClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&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;otps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mongoClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;otps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fetch the OTP record from the database&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otpRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;otps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&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;otpRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid phone number or OTP&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="c1"&gt;// Check if the OTP has expired&lt;/span&gt;
    &lt;span class="k"&gt;if&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="nx"&gt;now&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;otpRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OTP has expired&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="c1"&gt;// Check if the OTPs match&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;otpMatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;otpRecord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;otp&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;otpMatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid phone number or OTP&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="c1"&gt;// OTP is valid and has not expired, so we can delete it now&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;otps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Respond with a success status&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="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;err&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not verify OTP&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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongoClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When called, this API loads the OTP MongoDB database and finds the one associated with the phone number. It checks whether it has expired, and if not, matches the hashed OTPs. If it's valid, the OTP gets deleted from the database and a 200 code is returned to the frontend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VMoEs3cc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ese26psccqhx58a4yow4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VMoEs3cc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ese26psccqhx58a4yow4.png" alt="OTP Verification" width="800" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We could then work that response into any other authentication flow we had set up. We also have a basic rate limiter set into that will make sure a user can’t input more than 2 codes in 3 seconds, to try and prevent brute force attacks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zNyTZJ0W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jax17zxq40z6odaufxa9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zNyTZJ0W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jax17zxq40z6odaufxa9.png" alt="Too Many OTP Requests" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s it. You have one-time passwords working in Next.js.&lt;/p&gt;

&lt;h2&gt;
  
  
  OTPs are intricate but worth implementing
&lt;/h2&gt;

&lt;p&gt;This code gives you a one-time password option for &lt;a href="https://clerk.com/solutions/nextjs-authentication"&gt;Next.js authentication&lt;/a&gt;. But we’ve only scratched the surface. We haven’t implemented this within any other authentication flow–this just generates, sends, and verifies the OTP, it doesn’t use it to authenticate the user.&lt;/p&gt;

&lt;p&gt;We’ve also generated the OTP in the most basic manner, not following the RFCs and guidelines. We do have some nice best practices–rate-limiting, time-bounding, and expiry–but again these are all basic implementations. We also had to buy a new phone number!&lt;/p&gt;

&lt;p&gt;This is the intricacy of OTPs. They are easy to set up, but difficult to get right. Like most authentication methods, it is better to use a provider than trying to create your own. Check out Clerk’s &lt;a href="https://clerk.com/features/email-sms-passcodes-otp"&gt;OTP solution here&lt;/a&gt; to have these intricacies taken care of for you.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>otp</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Secure Authentication in Next.js with Email Magic Links</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Tue, 06 Jun 2023 23:13:36 +0000</pubDate>
      <link>https://forem.com/clerk/secure-authentication-in-nextjs-with-email-magic-links-29cc</link>
      <guid>https://forem.com/clerk/secure-authentication-in-nextjs-with-email-magic-links-29cc</guid>
      <description>&lt;h1&gt;
  
  
  Secure Authentication in Next.js with Email Magic Links
&lt;/h1&gt;

&lt;p&gt;Traditional username and password systems are ubiquitous but mostly suck. Every user needs a complicated random 8-20 character password that they can’t possibly remember for every site. Password managers make this easier, but they are ultimately just papering over the cracks.&lt;/p&gt;

&lt;p&gt;Added to this cognitive load for users is the cognitive load for developers, who have to implement these systems securely or constantly worry about their sites becoming targets for malicious activity.&lt;/p&gt;

&lt;p&gt;Imagine if you could simplify the user experience, decrease your system’s attack surface, and reduce your workload when it comes to user authentication. This is the promise of Magic Links, a powerful approach to passwordless authentication that is rapidly gaining popularity in the world of web development.&lt;/p&gt;

&lt;p&gt;Here we want to show you not just the benefits of magic links but how exactly they work, going through an implementation process in Next.js. We’ll also show you why, like most authentication patterns, you don’t want to do this all yourself and how Clerk can help you, just like magic!&lt;/p&gt;

&lt;h2&gt;
  
  
  Magical magic links
&lt;/h2&gt;

&lt;p&gt;So, why use magic links? Magic links can greatly improve user experience in several ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ease of use&lt;/strong&gt;: Magic links simplify the login process, as users just need to click a link sent to their email to authenticate themselves. They don’t have to remember any usernames or passwords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: Since there are no passwords to guess, magic links can increase security. This can be especially beneficial for users who often reuse passwords or use weak passwords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Magic links streamline the registration and login process. Rather than having to fill out forms, users just need to enter their email address, then check their inbox and click a link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced friction&lt;/strong&gt;: Magic links eliminate the common frustration of forgotten passwords and the need for password reset processes. 5.** Mobile-friendly**: Typing passwords on mobile devices can be challenging, especially for complex or long passwords. With magic links, users simply click a link in their email, which is much easier on a mobile device.&lt;/li&gt;
&lt;li&gt;Trust: When used properly, magic links can build trust, as they demonstrate a commitment to both user convenience and security.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are downsides. For one, the user obviously needs to have access to their email. Plus, there is the slight friction element of the user having to leap from the app to their email and back again. In theory, if the user’s email is compromised, an attacker can gain easy access to the app (you can get past this by using &lt;a href="https://clerk.com/docs/authentication/custom-flows/multifactor"&gt;two-factor authentication&lt;/a&gt; in your authentication flow).&lt;/p&gt;

&lt;p&gt;Creating and validating time-sensitive tokens, like those used in magic links, typically involves the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User Identification&lt;/strong&gt;: When the user requests a magic link, your application should first ensure that the email provided is valid and linked to a user account in your system. If the account exists, the server generates a unique, temporary token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Generation&lt;/strong&gt;: Generate a unique token using a secure method. This might involve using a secure random number generator or a library or function designed for generating secure tokens, such as JWT (JSON Web Tokens). The token should be associated with the user’s account in your database, along with a timestamp indicating when it was created.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-Sensitive Mechanism&lt;/strong&gt;: Attach an expiry time to the token. This could be a specific expiry date/time or a duration after which the token will expire. This is usually stored along with the token in the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email Delivery&lt;/strong&gt;: Send an email to the user containing the magic link. The link should point to your website or app and include the token as a parameter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Validation&lt;/strong&gt;: When the user clicks the magic link, your application should validate the token. This involves checking that the token exists in your database, is linked to a user, and has not expired. If all these checks pass, the user is authenticated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Deletion/Invalidation&lt;/strong&gt;: Once a magic link has been used or has expired, it’s important to invalidate it so that it cannot be used again. This can be done by deleting the token from your database or marking it as invalid.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;OK, so that’s magic links at a high level. How do you implement them in an application? Let’s go through two ways to do this. Firstly, we’ll show how you can do this with minimal additional libraries in Next.js to show what is required to generate, send, and validate time-sensitive tokens such as magic links. Then we’ll go through how you can implement them quicker and more securely with Clerk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build your own magic links in Next.js
&lt;/h2&gt;

&lt;p&gt;We need to expand the high-level flow above into more granular detail for what we need from our application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The user enters their email&lt;/strong&gt;: The user will enter their email address, which you’ll send to your backend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate a unique token&lt;/strong&gt;: On your server, generate a unique token tied to the user’s email. This can be a JWT, a UUID, or some other kind of unique identifier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send an email with the magic link&lt;/strong&gt;: Include the unique token in the magic link and email it to the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User clicks the link&lt;/strong&gt;: The user will click the link, which sends the token back to your server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the token&lt;/strong&gt;: On your server, verify that the token is valid and matches the user’s email.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a session&lt;/strong&gt;: If the token is valid, create a new session for the user and send it back to the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store the session on the client&lt;/strong&gt;: On the client, store the session (usually in a cookie or local storage) so that the user remains logged in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorize the user&lt;/strong&gt;: Whenever the user makes a request, check that they have a valid session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So beyond Next.js, for this to work, we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A way to generate and verify our tokens. In this instance, we’re going to use JWT for our tokens and use &lt;code&gt;jsonwebtoken&lt;/code&gt; to generate and verify them.&lt;/li&gt;
&lt;li&gt;A way to send emails. We’ll use &lt;code&gt;nodemailer&lt;/code&gt;. You’ll also need SMTP information for your email provider.&lt;/li&gt;
&lt;li&gt;A way to create cookies to store session data. We’ll use the &lt;code&gt;cookie&lt;/code&gt; library here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s all you need. Let’s first spin up a Next.js app called ‘magic-links’ as the bare bones of what we’ll create:&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="nx"&gt;npx&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;latest&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;magic&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;links&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll be asked a bunch of questions. Here we’re not using TypeScript and neither are we using the App directory (which is the new, better way of using Next.js that we’ll use later. But it doesn’t play as nicely with some of the API routes we’re creating here).&lt;/p&gt;

&lt;p&gt;After that we’ll install the necessary packages:&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="nx"&gt;npm&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then run npm run dev to start the development server. At the moment all you’ll get at &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; is the regular Next.js start page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K6JBWc1V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/e31247c8534a6bc92acfb54d8be11422ff7c074e-1999x1264.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K6JBWc1V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/e31247c8534a6bc92acfb54d8be11422ff7c074e-1999x1264.png" alt="image1.png" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously we need a login component as the first step. We’ll create a /components directory and add a login.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/login.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Login&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&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;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/login&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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;application/json&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&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;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Send Magic Link&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Login&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So there are two main parts to this component. At the bottom we have the actual form a user will fill in with their email address. As they type their email in, the state of the &lt;code&gt;email&lt;/code&gt; variable will change to contain their email address.&lt;/p&gt;

&lt;p&gt;When the press ‘Send Magic Link,’ The &lt;code&gt;handleSubmit&lt;/code&gt; function will be called. This function will call a backend api route, &lt;code&gt;/api/login&lt;/code&gt;. We’re going to send a POST request to this endpoint with the email address in the body.&lt;/p&gt;

&lt;p&gt;Let’s add this to our index page. Delete all the boilerplate code within that file and replace it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/index.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Inter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/font/google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Login&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../components/login&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;inter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Inter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;subsets&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;latin&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`flex min-h-screen flex-col items-center justify-between p-24 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;inter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Login&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re importing the &lt;code&gt;Login&lt;/code&gt; component and adding it to this page. Now a user will see this at &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PRO_HI08--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/f92c06581c57df46dfbf7dbd31ce2dafcb0dc877-1999x1264.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PRO_HI08--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/f92c06581c57df46dfbf7dbd31ce2dafcb0dc877-1999x1264.png" alt="image8.png" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If they were to add their email into that field and press ‘Send Magic Link,’ guess what would happen?&lt;/p&gt;

&lt;p&gt;Absolutely nothing! Let’s make something happen. What we need is that &lt;code&gt;/api/login&lt;/code&gt; the Login component calls. We’ll create a &lt;code&gt;login.js&lt;/code&gt; file in the api directory. Though this is within the &lt;code&gt;pages&lt;/code&gt; directory, it won’t be treated as a page by Next.js, it will act like an API endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/login.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodemailer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Create a magic link token&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="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&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;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;15m&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;transporter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nodemailer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mail.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;587&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&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="c1"&gt;// Generate magic link&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;magicLink&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&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;/api/verify?token=&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;transporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"Your Name" &amp;lt;your-email@example.com&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// sender address&lt;/span&gt;
    &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// list of receivers&lt;/span&gt;
    &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Magic Link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Subject line&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Click on this link to log in: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;magicLink&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="c1"&gt;// plain text body&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the first part of the magic of magic links happens. Let’s step through this to see what’s happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First we’re importing the libraries we need, &lt;code&gt;jsonwebtoken&lt;/code&gt; and &lt;code&gt;nodemailer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Within our &lt;code&gt;handler&lt;/code&gt; function, we’ll get the &lt;code&gt;email&lt;/code&gt; from the body of the request.&lt;/li&gt;
&lt;li&gt;With that email as the payload, we’ll use the sign method from &lt;code&gt;jsonwebtoken&lt;/code&gt; to create our token. There are two extra parameters we need to pass to sign:

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;JWT_SECRET&lt;/code&gt;. You can create a secret (for these purposes, not for production) by running &lt;code&gt;openssl rand -base64 32&lt;/code&gt; in the terminal to generate a key. You then store that key in a .env file in the root of the project for the project to read.&lt;/li&gt;
&lt;li&gt;An expiresIn time. Here, we’ve set this to 15 minutes.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Now we have our token, we need to send it to the user’s email address. Here we’re using the &lt;code&gt;createTransport&lt;/code&gt; method from &lt;code&gt;nodemailer&lt;/code&gt; to create an object called a &lt;code&gt;transporter&lt;/code&gt; that contains all our (i.e. the sender) SMTP email information.&lt;/li&gt;
&lt;li&gt;We’ll then create the actual magic link, which will be the origin URL (our URL) with the token appended as a query.&lt;/li&gt;
&lt;li&gt;Then we’ll call &lt;code&gt;sendMail&lt;/code&gt; on the &lt;code&gt;transporter&lt;/code&gt; object to send the email to the recipient&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now if the user presses ‘Send Magic Link,’ they should get an email like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t3sRrQ0G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/e6395766363d25d1f351680920e1e2f63485d730-1999x221.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t3sRrQ0G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/e6395766363d25d1f351680920e1e2f63485d730-1999x221.png" alt="image5.png" width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now if they click on that guess what would happen?&lt;/p&gt;

&lt;p&gt;Absolutely nothing!&lt;/p&gt;

&lt;p&gt;We need an endpoint to verify the token and set a cookie on the client with the user's email. First, let’s create another file in the api directory, this one called &lt;code&gt;verify.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/verify.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;serialize&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Verify the token - this throws if the token is invalid&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;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verify&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;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;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// The token is valid, so we create a session&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sessionToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;email&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;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1h&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Set-Cookie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sessionToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;secure&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;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Use secure cookies in production&lt;/span&gt;
        &lt;span class="na"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Expires after 1 hour&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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="c1"&gt;// Redirect the user to the homepage&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/secrets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&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;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The token was invalid, return an error&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid token&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top part of this is similar to the API route, but let’s step through the entire thing for clarity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First we’re importing the libraries we need, &lt;code&gt;jsonwebtoken&lt;/code&gt; again and cookie for session management.&lt;/li&gt;
&lt;li&gt;Within our &lt;code&gt;handler&lt;/code&gt; function, we’ll get the token from the query of the request.&lt;/li&gt;
&lt;li&gt;With that token as the payload, we’ll use the verify method from &lt;code&gt;jsonwebtoken&lt;/code&gt; to verify our token. This again uses our &lt;code&gt;JWT_SECRET&lt;/code&gt; that we signed it with to verify.&lt;/li&gt;
&lt;li&gt;If that’s a valid token, we’ll then create a session for the user, again using the &lt;code&gt;sign&lt;/code&gt; method from &lt;code&gt;jsonwebtoken&lt;/code&gt;. We set an expiresIn time of an hour. After that, our user will be logged out.&lt;/li&gt;
&lt;li&gt;We add the token to our cookie using &lt;code&gt;serialize&lt;/code&gt; from the &lt;code&gt;cookie&lt;/code&gt; library, then add the cookie to the headers of our result.&lt;/li&gt;
&lt;li&gt;We’ll then redirect the user to a logged-in-only page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That page, &lt;code&gt;/secrets&lt;/code&gt; doesn’t yet exist. Let’s create it in the &lt;code&gt;/pages&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/secrets.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Inter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/font/google&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;inter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Inter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;subsets&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;latin&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Secrets&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;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&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="c1"&gt;// Fetch data from our API route&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/secure-endpoint&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="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&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="c1"&gt;// If the response was not ok, throw an error&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&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 fetch&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&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;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;setData&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="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;err&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An error occurred: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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="c1"&gt;// Render data or loading message&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;
      &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`flex min-h-screen flex-col items-center justify-between p-24 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;inter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"z-10 w-full max-w-5xl items-center justify-between font-mono text-sm lg:flex"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&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;secret&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&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;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;This isn&lt;span class="ni"&gt;&amp;amp;apos;&lt;/span&gt;t a secret &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will conditionally render either a data object with the fields &lt;code&gt;secret&lt;/code&gt; and &lt;code&gt;email&lt;/code&gt; if the user is authenticated, or the text ‘This isn’t a secret’ if they aren’t. The data object comes from a call to our final api endpoint that we need to create, &lt;code&gt;secure-endpoint.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/secure-endpoint.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;auth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&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;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&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="k"&gt;try&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;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&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;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Now you have the authenticated user's email&lt;/span&gt;
    &lt;span class="c1"&gt;// Do your secure stuff here...&lt;/span&gt;

    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a secret!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This authorizes the user by verifying with &lt;code&gt;verify&lt;/code&gt; from &lt;code&gt;jsonwebtoken&lt;/code&gt; the session token and, if the user is authenticated, passing back an object with a &lt;code&gt;secret&lt;/code&gt; (‘This is a secret!’) and an &lt;code&gt;email&lt;/code&gt;. Now if a user fills in their email and clicks on the link, they’ll be sent to the secret page with the &lt;code&gt;secret&lt;/code&gt; and their &lt;code&gt;email&lt;/code&gt; showing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xKcveCxo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/ca4c4a91d3010cf0e8f4e308548af46cb6233c91-1999x1264.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xKcveCxo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/ca4c4a91d3010cf0e8f4e308548af46cb6233c91-1999x1264.png" alt="image4.png" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! With these routes, you now have a basic magic link authentication system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Clerk for your magic links
&lt;/h2&gt;

&lt;p&gt;The above code works. It’s also a terrible idea.&lt;/p&gt;

&lt;p&gt;We aren’t doing any error handling. We aren’t checking for any edge cases. We’re not overly protective of our JWT token. If an attacker learns your JWT secret, they can create valid tokens and impersonate any user. There’s no rate limiting. The cookie storage isn’t ideal.&lt;/p&gt;

&lt;p&gt;Plus we have to have our own email server that is configured to send out a lot of transactional emails (which regular email providers don’t really like as they get punished for spam). And, yeah, we know, our login and authenticated pages aren’t exactly winning any design awards.&lt;/p&gt;

&lt;p&gt;Basically, there are a number of issues with rolling your own magic links.&lt;/p&gt;

&lt;p&gt;That’s why services like Clerk exist–to deal with all of the above and make it much easier for developers to implement strong authentication frameworks such as magic links. Let’s go through this again, but with Clerk as the magic link provider.&lt;/p&gt;

&lt;p&gt;We’ll spin up a new Next.js app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npx&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;latest&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;magic&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;links&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, you’ll get the questions. Clerk is written for the latest developments in Next.js, so you can use the App Router.&lt;/p&gt;

&lt;p&gt;After that all we need to install is Clerk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npm&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;clerk&lt;/span&gt;&lt;span class="sr"&gt;/nextj&lt;/span&gt;&lt;span class="err"&gt;s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we npm run dev now and go to &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt; we again get a regular Next.js start page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gt2pyDa8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/4bb435f2579af449d2b3fd21d8e9fef9552cc92f-1999x1264.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gt2pyDa8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/4bb435f2579af449d2b3fd21d8e9fef9552cc92f-1999x1264.png" alt="image3.png" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we get to the code, we’ll also want to do some other set up. First, we’ll need our NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and our CLERK_SECRET_KEY. You can get both of these from your &lt;a href="https://dashboard.clerk.com/last-active?path%3Dapi-keys&amp;amp;sa=D&amp;amp;source=editors&amp;amp;ust=1685995554103219&amp;amp;usg=AOvVaw2erAIc8kfee3P2mGQJWupV"&gt;Dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Add these to an .env file in the root of your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY &lt;span class="o"&gt;=&lt;/span&gt; pk_test_xxxx&lt;span class="p"&gt;;&lt;/span&gt;
CLERK_SECRET_KEY &lt;span class="o"&gt;=&lt;/span&gt; sk_test_xxxx&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll also want to configure our Clerk set up to use magic links. We need to change two settings from the defaults in our dashboard. The first is to choose Email verification link as the authentication factor. To do that, go to &lt;strong&gt;User &amp;amp; Authentication &amp;gt; Email, Phone, Username&lt;/strong&gt; in your dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J4kgXSLI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/540c62b637a385ef8cd77f9cdca5044807fbfe6b-3346x2116.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J4kgXSLI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/540c62b637a385ef8cd77f9cdca5044807fbfe6b-3346x2116.png" alt="CleanShot 2023-06-05 at 15.57.39@2x.png" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then go to &lt;strong&gt;Contact information&lt;/strong&gt; in that subsection and toggle on &lt;strong&gt;Email address&lt;/strong&gt; and check &lt;strong&gt;Email Verification Link&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ie-j4YNC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/cc5d1162222b94bf1c5be87853f1e248bbdfb9d9-3346x2116.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ie-j4YNC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/cc5d1162222b94bf1c5be87853f1e248bbdfb9d9-3346x2116.png" alt="CleanShot 2023-06-05 at 15.56.18 2@2x.png" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can start with the code. The first step is to wrap our entire Next.js application in the &lt;code&gt;&amp;lt;ClerkProvider&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Inter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/font/google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ClerkProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&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;inter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Inter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;subsets&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;latin&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create Next App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generated by create next app&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&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="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&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;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ClerkProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;inter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ClerkProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is going to provide all we need to pass session and user information to Clerk for the authentication logic. This step is the same for any authentication pattern you are going to use with Clerk in Next.js.&lt;/p&gt;

&lt;p&gt;The next step is to use Clerk to protect pages within our application. To do this, we’ll create a file in the &lt;code&gt;/src&lt;/code&gt; directory. This will use regular expressions to pattern match against the pages you want to protect. Here, we’re going to protect all our pages:&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="c1"&gt;//middleware.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&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;/((?!.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;..*|_next).*)&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;/&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;/(api|trpc)(.*)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you load up &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;, you’ll be redirected to a login/signup page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gec3o8u---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/8d71d81b4d43e75cc90dd58eb82a086bbcfac4d8-1999x1264.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gec3o8u---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/8d71d81b4d43e75cc90dd58eb82a086bbcfac4d8-1999x1264.png" alt="image9.png" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A user enters their email address here and gets sent a link to click:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7jJvd3MJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/b9be271aeb2174a5c010fa2b21b2cfc74dac7d07-1999x965.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7jJvd3MJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/b9be271aeb2174a5c010fa2b21b2cfc74dac7d07-1999x965.png" alt="image7.png" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on that link sends them back to &lt;a href="http://localhost:3000"&gt;http://localhost:3000&lt;/a&gt;, but now they can access the site again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gt2pyDa8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/4bb435f2579af449d2b3fd21d8e9fef9552cc92f-1999x1264.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gt2pyDa8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/4bb435f2579af449d2b3fd21d8e9fef9552cc92f-1999x1264.png" alt="image3.png" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Much easier (and much better looking signup pages and emails as well!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Better security and a user experience with magic links
&lt;/h2&gt;

&lt;p&gt;Magic links can really help you streamline your sign up and sign in processes. They lessen the burden on your users while providing strong security for them and your application.&lt;/p&gt;

&lt;p&gt;But implementing them manually puts the burden on your developers. Managing creation and validation of the tokens, sending emails (and maintaining the system to do so), then coding up the right modals, pages, error states, and edge cases is a huge hassle.&lt;/p&gt;

&lt;p&gt;It’s worth developers going through and trying this manually just to see how magic links are implemented. But if you are looking for a production-ready option that you can incorporate into your app today, you can use &lt;a href="https://clerk.com"&gt;Clerk&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>passwordless</category>
      <category>magiclink</category>
    </item>
    <item>
      <title>Social SSO in Next.js</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Tue, 06 Jun 2023 23:12:00 +0000</pubDate>
      <link>https://forem.com/clerk/social-sso-in-nextjs-k3o</link>
      <guid>https://forem.com/clerk/social-sso-in-nextjs-k3o</guid>
      <description>&lt;p&gt;Authentication has always been a critical aspect of web development, with user data security being paramount. &lt;/p&gt;

&lt;p&gt;As the digital world continues to evolve, so does the need for more efficient and secure authentication methods. However, users do not necessarily want to remember secure logins for every app they use. &lt;/p&gt;

&lt;p&gt;This is where OAuth Single Sign-On (SSO) comes into play. OAuth simplifies the user authentication process by allowing users to log in with accounts they already have with other services like Google, Facebook, or Microsoft. &lt;/p&gt;

&lt;p&gt;In this article, we will explore how to incorporate OAuth SSO into a Next.js project with &lt;a href="https://jwt.io/introduction"&gt;JSON Web Tokens (JWTs)&lt;/a&gt; and Next.js’s new app router, and later see how Clerk, an authentication service, can help simplify this and make it more secure. We will focus on GitHub SSO for simplicity, but most other OAuth providers can be implemented similarly.&lt;/p&gt;

&lt;p&gt;Before we get started, if you ever need to look at the full source, code, the &lt;a href="https://github.com/AsyncBanana/nextjs-sso"&gt;example code is on GitHub&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Before we start, make sure you are on Node v18+. You can check this by running &lt;code&gt;node -v&lt;/code&gt; in a terminal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaffolding
&lt;/h3&gt;

&lt;p&gt;We will use &lt;a href="https://nextjs.org/docs/getting-started/installation"&gt;create-next-app&lt;/a&gt; to scaffold our project. First, you will want to open a terminal in the directory you want to put the project in and run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to use a different package manager, use their own counterpart to &lt;code&gt;npx&lt;/code&gt;, like &lt;code&gt;pnpx&lt;/code&gt;. create-next-app will ask you some questions about your project. Name the project whatever you want; it doesn’t matter. For TypeScript, ESLint, Tailwind, and using &lt;code&gt;/src&lt;/code&gt;, select no. For the app router, select yes. Finally, leave the import aliases unmodified. After, the output should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s-jBOHGg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/3831e3d2719600d436fc5a7f55c3528434e9b6e0-2000x1250.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s-jBOHGg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/3831e3d2719600d436fc5a7f55c3528434e9b6e0-2000x1250.png" alt="output of create-next-app" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, open that directory in a code editor of your choice. You will need to install jsonwebtoken, which we will use for verifying JWTs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i jsonwebtoken
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a GitHub OAuth App
&lt;/h3&gt;

&lt;p&gt;Next, we will need to create the OAuth app in GitHub. Go to &lt;a href="https://github.com/settings/applications/new"&gt;github.com/settings/applications/new&lt;/a&gt; and fill out the information. Set the homepage URL to &lt;code&gt;http://localhost:3000&lt;/code&gt; and the callback URL to &lt;code&gt;http://localhost:3000/callback&lt;/code&gt; (if you are running the Next.js server on a port other than 3000, you can replace 3000 with that port). You can leave device flow unchecked. Click register and you will be sent to the new application page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dm5K7agU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/387ca25acbfc80693dda994683b9ed4a0ba430ae-2000x1250.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dm5K7agU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/387ca25acbfc80693dda994683b9ed4a0ba430ae-2000x1250.png" alt="GitHub OAuth creation page" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let’s go back to the code for a minute. Create a new file called &lt;code&gt;.env.local&lt;/code&gt;. This is where you will store your environment variables, which are variables that specify service keys or other configuration that is specific to each deployment and are specified by writing &lt;code&gt;VARIABLE_NAME=variable_value&lt;/code&gt;, with each variable being on one line.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;.env.local&lt;/code&gt; file, create a variable named &lt;code&gt;NEXT_PUBLIC_GITHUB_OAUTH_ID&lt;/code&gt;. This will contain your GitHub OAuth application’s id. Note that the &lt;code&gt;NEXT_PUBLIC&lt;/code&gt; prefix tells Next.js to allow the env variable to be accessed by replacing all calls to &lt;code&gt;process.env.NEXT_PUBLIC_GITHUB_OAUTH_ID&lt;/code&gt; with the value of the variable at build time. If you did not include this prefix, only the server would be able to access the value, which is good for secrets. With that aside, we need to get the GitHub OAuth application id, which you can find on the application dashboard you were sent to earlier. Copy it and paste it into the .env file.&lt;/p&gt;

&lt;p&gt;We also need to get a secret, which we will put with the variable &lt;code&gt;GITHUB_OAUTH_SECRET&lt;/code&gt;. On the dashboard, you can click “Generate a client secret” to get a new secret. Copy it and paste it in the .env file under the new variable.&lt;/p&gt;

&lt;p&gt;Finally, we need &lt;code&gt;JWT_SECRET&lt;/code&gt;, which should be a random password we use for encrypting JWTs. You can use a &lt;a href="https://passwordsgenerator.net/"&gt;password generator&lt;/a&gt; for this.&lt;/p&gt;

&lt;p&gt;That is it for set up! Remember not to give anyone any of the secrets unless you trust them, as they can be used to impersonate users and gain unauthorized access to the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implement sign in
&lt;/h2&gt;

&lt;p&gt;Our next step is to allow people to sign in by adding a sign in button on the homepage that links to the GitHub OAuth verification page. Open &lt;code&gt;app/page.js&lt;/code&gt; and insert the following below the Next.js logo (You should be able to find it by looking for an &lt;code&gt;&amp;lt;Image&amp;gt;&lt;/code&gt; with &lt;code&gt;src=src="/next.svg"&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;
  &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`https://github.com/login/oauth/authorize?scope=user:email&amp;amp;client_id=&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;NEXT_PUBLIC_GITHUB_OAUTH_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;buttonStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Sign in with GitHub
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The link will send people to a page where they can authorize the OAuth app’s access to their account information to allow the app to verify their identity. You might notice &lt;code&gt;className&lt;/code&gt; contains a style that does not currently exist. We will fix this next. In &lt;code&gt;app&lt;/code&gt;, create a file named &lt;code&gt;button.module.css&lt;/code&gt; and paste the following in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;user-select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#24292e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fafbfc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.button&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f3f4f6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.button&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#c0d3eb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.button&lt;/span&gt;&lt;span class="nd"&gt;:active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#edeff2&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;This just provides some simple styling for the button. In order to use it, we need to import it in &lt;code&gt;page.js&lt;/code&gt;. Add this line to the top of &lt;code&gt;page.js&lt;/code&gt;.&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;buttonStyle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./button.module.css&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;To get the layout correct, we also have to add this to &lt;code&gt;.center&lt;/code&gt; in &lt;code&gt;page.module.css&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;flex-direction&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;column&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, run &lt;code&gt;npm run dev&lt;/code&gt; and go to &lt;code&gt;http://localhost:3000&lt;/code&gt;. You should see this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QUsmBzR6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/247a788c35e5ea1c9dd8c876cbac8cded81fb1ff-3840x2160.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QUsmBzR6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn.sanity.io/images/e1ql88v4/production/247a788c35e5ea1c9dd8c876cbac8cded81fb1ff-3840x2160.png" alt="Your website homepage" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click “Sign in with GitHub,” you should be redirected to a GitHub page asking you to authorize your OAuth app. However, if you click authorize, you will get a 404 error. That is because we have not implemented a callback page yet, which we will do next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a callback
&lt;/h2&gt;

&lt;p&gt;Before we start this step, a bit of explaining is required. You might have seen OAuth flows implemented differently, and this is perfectly fine. There are multiple ways to handle OAuth codes and retrieve access tokens. In this case, we are using &lt;a href="https://oauth.net/2/grant-types/authorization-code/"&gt;Authorization Code Grant&lt;/a&gt;, which is where the OAuth provider (GitHub in this case) sends a request to the specified callback URL (which we are implementing now) with a code. Then, the server sends a request to GitHub with the code, and GitHub responds with the access token. This is generally the most secure method, but for mobile apps or SPAs, it does not always work. With that explained, we will get started on implementing the callback.&lt;/p&gt;

&lt;p&gt;You first want to create a folder named &lt;code&gt;callback&lt;/code&gt; in &lt;code&gt;app&lt;/code&gt; and a file named &lt;code&gt;route.js&lt;/code&gt; inside &lt;code&gt;callback&lt;/code&gt;. This tells Next.js’s filesystem router that &lt;code&gt;route.js&lt;/code&gt; should handle API requests sent to &lt;code&gt;/callback&lt;/code&gt;. Now, paste this into &lt;code&gt;route.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;code&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No code provided&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;try&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;tokenResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/login/oauth/access_token&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&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="na"&gt;accept&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/json&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;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/json&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;client_id&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;NEXT_PUBLIC_GITHUB_OAUTH_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;client_secret&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;GITHUB_OAUTH_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;code&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;tokenResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&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;access_token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tokenData&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;access_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GitHub login failed&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jwtToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;access_token&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;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1h&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="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jwtToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;secure&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;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lax&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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="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="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="nx"&gt;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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal server error&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="na"&gt;status&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="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will go through this step by step. First, we import a few different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;jsonwebtoken&lt;/code&gt; is for creating JWTs, which we use to store the userdata and allow us to verify that bad actors did not change it (we will do this later)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cookies&lt;/code&gt; from &lt;code&gt;next/headers&lt;/code&gt; is for setting cookies on the client. This function is an abstraction over the &lt;code&gt;Set-Cookie&lt;/code&gt; HTTP header, hence the import location.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redirect&lt;/code&gt; from &lt;code&gt;next/navigation&lt;/code&gt; is for redirecting users. Similar to the &lt;code&gt;cookies&lt;/code&gt; function, this function abstracts over HTTP responses by automatically responding with an HTTP 3xx status code (read: a redirect) with the &lt;code&gt;Location&lt;/code&gt; header set to the value passed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, we export a new function named &lt;code&gt;GET&lt;/code&gt;, which takes &lt;code&gt;req&lt;/code&gt; as a parameter. This function handles all HTTP requests to &lt;code&gt;/callback&lt;/code&gt; with the HTTP method &lt;code&gt;GET&lt;/code&gt;, which is the method used for typical requests that do not contain a body. &lt;code&gt;req&lt;/code&gt; contains all of the information about the request.&lt;/p&gt;

&lt;p&gt;The next part retrieves the code from the &lt;code&gt;code&lt;/code&gt; query parameter in the URL. You can refer to the explanation at the start of this section to learn how this is used. If there is no code, the function returns a 400 error.&lt;/p&gt;

&lt;p&gt;Now that we have the code, we send it back to GitHub to get the access token. After getting the response, we parse the JSON body and extract the access token. If there is no access token, another error is returned.&lt;/p&gt;

&lt;p&gt;We sign the token to allow us to store it in a cookie with confidence that it cannot change without the JWT secret. Using the signed token, we set the &lt;code&gt;auth&lt;/code&gt; cookie for the user. The parameters we passed are very important. &lt;code&gt;httpOnly&lt;/code&gt; makes sure JavaScript cannot access the cookie’s value, &lt;code&gt;secure&lt;/code&gt; makes sure that when deployed in production, the cookie cannot be sent over unencrypted HTTP, and &lt;code&gt;sameSite&lt;/code&gt; prevents the cookie from being sent to third party websites. &lt;code&gt;sameSite=lax&lt;/code&gt; is the default in modern browsers, but it is still a good idea to make it explicit.&lt;/p&gt;

&lt;p&gt;Finally, we redirect the user to &lt;code&gt;/dashboard&lt;/code&gt;. If you run this code, you should end up at another 404 at &lt;code&gt;/dashboard&lt;/code&gt;. We will create the dashboard next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a protected dashboard
&lt;/h2&gt;

&lt;p&gt;Once again, we need to create a file named &lt;code&gt;dashboard&lt;/code&gt; and a file inside it named &lt;code&gt;page.js&lt;/code&gt;. Because this is a page rather than an API route, the naming is different. Copy the following into &lt;code&gt;page.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;buttonStyle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../button.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Page&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;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://github.com/login/oauth/authorize?scope=user:email&amp;amp;client_id=&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;NEXT_PUBLIC_GITHUB_OAUTH_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="k"&gt;return&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;JWT_SECRET&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.github.com/user&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="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="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &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="nx"&gt;access_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="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-store&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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-align&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;center&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;margin-top&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;100px&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="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Welcome &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; to the dashboard!
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;buttonStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Logout
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, we will go through this step by step. The imports are largely the same as the callback. The only difference is the addition of &lt;code&gt;../button.module.css&lt;/code&gt;, which is the CSS file we created earlier for button styling.&lt;/p&gt;

&lt;p&gt;This time, instead of a function named &lt;code&gt;GET&lt;/code&gt;, because this is a page, we export a function named &lt;code&gt;Page&lt;/code&gt;. Of course, because it is a default export in this case, the naming doesn’t really matter.&lt;/p&gt;

&lt;p&gt;Next, we get the auth cookie. If no auth cookie is found, the user is not signed in, so we redirect the user to the sign in.&lt;/p&gt;

&lt;p&gt;If there is an auth cookie, we verify the JWT contained in it. This is how we make sure the value was not tampered with. JWTs have a header, payload, and signature. The payload’s value can be accessed without the JWT secret. However, the JWT cannot be modified, as the signature is generated using the JWT secret and payload value. Therefore you need both of those to generate a valid signature. What we are doing here is both decrypting the payload and making sure the signature is valid.&lt;/p&gt;

&lt;p&gt;Now that we have verified the JWT and extracted the payload, we use the access token in the JWT to request the user’s data from the GitHub API. The &lt;code&gt;cache&lt;/code&gt; parameter disables Next.js’s AOT caching/static generation. Normally we would do this inside a &lt;code&gt;useEffect()&lt;/code&gt; call, but because we are using &lt;a href="https://nextjs.org/docs/getting-started/react-essentials#server-components"&gt;React Server Components&lt;/a&gt; with Next.js, this is the idiomatic way of using &lt;code&gt;fetch()&lt;/code&gt; (remember that this renders once on the server and does not render on the client period). We then parse the response body to get the user data.&lt;/p&gt;

&lt;p&gt;After that, we just have some markup that renders a welcome message with the user’s name and a logout button. We use inline styles for the header because creating another CSS file just for two rules didn’t make sense. You might notice that the logout button links to &lt;code&gt;/logout&lt;/code&gt;, which does not exist yet. We will fix that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: If you have lots of protected routes and want to avoid code repetition, you could implement the authentication as middleware or just a function imported in the relevant routes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Adding a logout route
&lt;/h2&gt;

&lt;p&gt;This is pretty simple. The first thing to do is to create another route, just like what we did for &lt;code&gt;/callback&lt;/code&gt;. Create a new folder in &lt;code&gt;app&lt;/code&gt; called &lt;code&gt;logout&lt;/code&gt; and a file called &lt;code&gt;route.js&lt;/code&gt; in the new folder. Insert the following in the new file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookies&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/headers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, this script is pretty simple. We just intercept GET requests to &lt;code&gt;/logout&lt;/code&gt;, delete the cookie named &lt;code&gt;auth&lt;/code&gt;, and redirect the user to the homepage. Note that if the user tries to log in again, it might seem as if they are still signed in, as GitHub will silently send the access token and redirect the user when they click login. However, if you implement a more complete sign in solution, this will allow users to sign in to another account with a different method.&lt;/p&gt;

&lt;p&gt;That is all! You now have a basic system for OAuth authentication. However, there are a few issues&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems with this Implementation
&lt;/h2&gt;

&lt;p&gt;While this works, there are some problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no protection against CSRF (Cross Site Request Forgery)&lt;/li&gt;
&lt;li&gt;There are no refresh tokens, and access tokens can last indefinitely (this is a limitation of GitHub OAuth apps)&lt;/li&gt;
&lt;li&gt;GitHub is currently the only sign in method (non OAuth sign requires many more security measures due to the need to store information in a database)&lt;/li&gt;
&lt;li&gt;There is support if issues arise with authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We could solve most of these problems, but it would take a significant amount of time (this tutorial is already almost 3,000 words) and increase maintenance. However, there is a better solution: you can use a service like &lt;a href="https://clerk.com/"&gt;Clerk&lt;/a&gt;. Clerk is a user management platform that allows you to easily and securely implement most of the popular OAuth providers along with standard email/password sign in, magic link/passwordless, and even Metamask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Clerk for Authentication
&lt;/h2&gt;

&lt;p&gt;Once again, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and select the same choices as previously (any name, everything “no” except for the app router). Then, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @clerk/nextjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs the Clerk SDK for Next.js. Now, we need to set up a Clerk app. Go to &lt;a href="https://dashboard.clerk.com/apps/new"&gt;Clerk’s app creation page&lt;/a&gt; (create an account if you did not already) and go through the configuration. For authentication providers, you can stick to just GitHub again or add as many providers as you want. It doesn’t change the process, and you can change it later. After that, you should be redirected to a page where you see a snippet containing &lt;code&gt;NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY&lt;/code&gt; and &lt;code&gt;CLERK_SECRET_KEY&lt;/code&gt;. Copy this and paste it into a new file named &lt;code&gt;.env.local&lt;/code&gt;. This includes the environment variables that store your public and private Clerk keys.&lt;/p&gt;

&lt;p&gt;To allow us to use Clerk React components in our project, we first need to insert the &lt;code&gt;&amp;lt;ClerkProvider&amp;gt;&lt;/code&gt; component into &lt;code&gt;layout.js&lt;/code&gt;. Replace all content in &lt;code&gt;layout.js&lt;/code&gt; with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./globals.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Inter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/font/google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ClerkProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&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;inter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Inter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;subsets&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;latin&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create Next App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generated by create next app&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;inter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ClerkProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ClerkProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;ClerkProvider&amp;gt;&lt;/code&gt; provides the context necessary to allow Clerk components to function properly, and due to it being &lt;code&gt;layout.js&lt;/code&gt;, it is inserted into every page.&lt;/p&gt;

&lt;p&gt;Next, we need to create the sign in button, just like in the first version. Open &lt;code&gt;page.js&lt;/code&gt; in &lt;code&gt;app&lt;/code&gt; and insert this below the Next.js logo (the image with &lt;code&gt;src="/next.svg"&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard"&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;buttonStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Dashboard
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SignInButton&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"modal"&lt;/span&gt; &lt;span class="na"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;buttonStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign In&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SignInButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks if the user is signed in. If they are, it shows a link to the dashboard, and if not, it shows a button to sign in. To make this work, you will also need to import two things at the top of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SignInButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;buttonStyle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./button.module.css&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;The first line contains the functions and components needed from the Clerk SDK, and the second is for the styling. You will want the same styling as in the previous version, so create a new file named &lt;code&gt;button.module.css&lt;/code&gt; and insert this into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inline-block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;vertical-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;user-select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#24292e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fafbfc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.button&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f3f4f6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.button&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#c0d3eb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.button&lt;/span&gt;&lt;span class="nd"&gt;:active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#edeff2&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;Then, add this to &lt;code&gt;.center&lt;/code&gt; in &lt;code&gt;page.module.css&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;flex-direction&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;column&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now sign in should work, but we still need to implement the dashboard. First, we need to create a JavaScript file at the project root named &lt;code&gt;middleware.js&lt;/code&gt; (make sure to put this at the project root, &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;/app&lt;/code&gt;). Paste the following in the new file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authMiddleware&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;authMiddleware&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;matcher&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;/((?!.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;..*|_next).*)&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;/&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;/(api|trpc)(.*)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a basic setup for the authentication middleware. You can configure it more to exclude specific routes, but this should work in most cases.&lt;/p&gt;

&lt;p&gt;The next step is to create a folder called &lt;code&gt;dashboard&lt;/code&gt; and a file named &lt;code&gt;page.js&lt;/code&gt; inside it, just like with the implementation we already made. Inside the file, paste this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RedirectToSignIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SignOutButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@clerk/nextjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;buttonStyle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../button.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Page&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;userdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;currentUser&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;userdata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RedirectToSignIn&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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-align&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;center&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;margin-top&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;100px&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="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Welcome &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emailAddresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;emailAddress&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; to the dashboard!
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SignOutButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;buttonStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SignOutButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will go through this step by step.&lt;/p&gt;

&lt;p&gt;First, we import some packages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We import multiple different functions and components from the Clerk SDK. The first, &lt;code&gt;currentUser()&lt;/code&gt;, helps us get the current user’s information. The second, &lt;code&gt;RedirectToSignIn&lt;/code&gt;, not to be confused with the function &lt;code&gt;redirectToSignIn&lt;/code&gt;, which is also exported, is a component that redirects the user to a sign in page. Finally, we have &lt;code&gt;SignOutButton&lt;/code&gt;, which creates a button to allow the user to log out.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;buttonStyle&lt;/code&gt; is just CSS from our &lt;code&gt;button.module.css&lt;/code&gt; file, which we use for the log out button.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we export a function called &lt;code&gt;Page&lt;/code&gt;, which has been explained previously.&lt;/p&gt;

&lt;p&gt;After that, we use &lt;code&gt;currentUser()&lt;/code&gt; to get the user’s information. If it does not exist (meaning the user is signed out), we redirect to a sign in page using a client side redirect.&lt;/p&gt;

&lt;p&gt;Finally, using the userdata, we return markup containing a header with the user’s email and a button to log out.&lt;/p&gt;

&lt;p&gt;Now, sign in, the dashboard, and sign out should all work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy SSO
&lt;/h2&gt;

&lt;p&gt;That’s it! You now should have two implementations of OAuth authentication, one that is hand built and more limited and one that is built with Clerk and more feature rich. If you want to learn more about Clerk, check out the &lt;a href="https://clerk.com/docs"&gt;Clerk Docs&lt;/a&gt;. I hoped you learned something, and thanks for reading!&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>nextjs</category>
      <category>sso</category>
    </item>
    <item>
      <title>Feature Engineering for Fraud Detection</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Wed, 07 Dec 2022 17:40:00 +0000</pubDate>
      <link>https://forem.com/nickparsons/feature-engineering-for-fraud-detection-5d8</link>
      <guid>https://forem.com/nickparsons/feature-engineering-for-fraud-detection-5d8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Fraud detection is critical in keeping remediating fraud and services safe and functional. First and foremost, it helps to protect businesses and individuals from financial loss. By identifying potential instances of fraud, companies can take steps to prevent fraudulent activity from occurring, which can save them a significant amount of money. Fraud detection also saves users a lot of headaches and instills trust in them when they know these protections are in place.&lt;/p&gt;

&lt;p&gt;There is a wide variety of applications under the broader umbrella of fraud/risk detection — credit card transaction fraud, payment fraud, identity fraud, account takeover, etc. All of these types of fraud or risk can be broadly categorized into two types — first-party fraud (i.e., fraudulent users trying to trick a bank) or third-party fraud (i.e., a bad actor who is trying to compromise the account of a legitimate user). Most of these applications have a similar structure when it comes to ML and feature engineering, which we will explore in this post. First, we will briefly describe how the modeling problem is formulated and how the labels are obtained. And finally, we will do a deep dive into feature engineering for fraud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling
&lt;/h2&gt;

&lt;p&gt;The fraud detection problem is often modeled as a supervised learning-based binary classification, and Gradient Boosting Decision Trees (GBDTs) are often used as the model. GBDTs work by training a service of decision trees on the data, where each tree is built to correct the mistakes made by the previous trees in the sequence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w6MiYMjH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://fennel.ai/blog/content/images/2022/12/GBDT-Diagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w6MiYMjH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://fennel.ai/blog/content/images/2022/12/GBDT-Diagram.png" alt="GBDT-Diagram" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the case of fraud detection, the features input into the GBDTs might include information about the transaction, the customer involved, and any other relevant financial or behavioral data, and whether training data includes labels that indicate if a transaction is fraudulent or not.&lt;/p&gt;

&lt;p&gt;The GBDT algorithm uses this information to identify patterns and relationships, even complex non-linear relationships, that are indicative of fraudulent activity. By training the model on a large dataset of known fraudulent and non-fraudulent transactions, the GBDT algorithm can learn to make highly accurate predictions about the likelihood of a given transaction being fraudulent.&lt;/p&gt;

&lt;p&gt;Fraud detection often involves working with large and highly unbalanced datasets, where the number of fraudulent transactions is relatively small compared to the number of non-fraudulent transactions. This can make it difficult to identify the features that are most relevant and useful for training the model. To mitigate this, there are a few approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Oversampling the fraudulent transactions or undersampling the legitimate transactions - this can balance out the dataset, but can also lead the model to believe that fraud is more frequent than it is.&lt;/li&gt;
&lt;li&gt;Changing the metrics used to evaluate the model – you can use metrics like precision, rather than accuracy, or use the F1 score to evaluate how well the model is performing.&lt;/li&gt;
&lt;li&gt;Using algorithms like ensemble methods or cost-sensitive learning – Both of these algorithms can help improve the performance of the machine learning model on the minority class. GBDT (which itself is an ensemble) is a great algorithm for fraud detection because it is powerful and flexible, and relatively easy to use and interpret, but some other ensemble methods are particularly effective at handling imbalanced datasets, and cost-sensitive learning is particularly adept at considering the costs associated with false positives and false negatives.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Labels
&lt;/h2&gt;

&lt;p&gt;Since the problem is formulated as supervised learning, ground truth labels are needed to train the model — in this case, whether transactions as fraudulent or non-fraudulent.  &lt;/p&gt;

&lt;p&gt;In some problems, e.g., credit card transaction fraud, if a transaction was incorrectly classified as non-fraudulent and permitted to happen, the end user may dispute the charge. That can serve as good quality labels. But in general, obtaining high-quality labels in a timely fashion is a huge problem for fraud detection, unlike recommendation systems where what the user clicked on can easily serve as good-quality labels. &lt;/p&gt;

&lt;p&gt;As a result, typically, humans often need to be involved to manually provide the ground truth labels along with a bunch of automated augmentation/correction mechanisms. &lt;/p&gt;

&lt;p&gt;Another challenge with labels is that there is a delay in receiving labels. There is typically a 2-3 week delay in processing fraudulent activity on an account. In some problems, like account takeovers, malicious actors may wait months before starting to use the accounts to say post spam or conduct transactions. In such cases, the system may not find out the correct label of a decision for account fraud prediction for many months. In general, it can take around 6 months to get 95% of all labels which means models have to be trained on several months old data at any point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Engineering
&lt;/h2&gt;

&lt;p&gt;In order to create effective features for fraud detection, it is necessary to understand the underlying data and the problem that the model is trying to solve (first or third-party fraud, as well as the types of fraud therein). This often involves a combination of domain expertise, data exploration, and experimentation to identify the most relevant and informative features to use as input (often accomplished through exploratory data analysis, or “EDA”). Once these features have been identified, they may need to be transformed or combined in order to make them more useful for training a machine learning model (also included in EDA). This process of feature engineering is crucial for achieving good performance in fraud detection and other machine learning tasks.&lt;/p&gt;

&lt;p&gt;One of the challenges with feature engineering for fraud in particular is that, like with labels, much of the most useful features need to be handwritten. There are some features that can be used from the raw data, but, more often than not, the most useful features are ones that combine several different pieces of data, like the amount and the location and time of day (e.g., someone is likely to spend more money on dinner in a high cost-of-living area than they are to spend at a coffee shop in a low cost-of-living area) and/or splitting the values for a specific piece of data (like splitting the date from the time or the dollars from the cents of a transaction amount).&lt;/p&gt;

&lt;p&gt;Some of the most common patterns of features for fraud detection include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Affinity features built on top of rolling counter-based features — how many times an event of some kind happened in rolling windows (e.g., how many times a user has made an ATM withdrawal in this city in the last 6 months or the average transaction amount for a user with a particular credit card).&lt;/li&gt;
&lt;li&gt;Velocity features — how quickly events happen (e.g., the ratio of the purchases the user made in the last hour to the average number of transactions they made per hour in the last 30 days). These are also ratios of some counters, even though the focus is on capturing the velocity of the actions taken vs. the affinity between two entities.&lt;/li&gt;
&lt;li&gt;Reputation features — some “reputation” score for various things like the email domain of the user, the IP address the activity is coming from, the vendor the purchase was made from, etc. Some of these are, again, using counters of past behavior.&lt;/li&gt;
&lt;li&gt;External API-based features — e.g., the credit score of the user or location of an IP, etc.&lt;/li&gt;
&lt;li&gt;Relatively static profile features — e.g., the zip code from which the request originated or the age of the user’s account, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned above, determining which of these features is most useful involves a lot of industry knowledge and experimentation (since the normal log and wait process would require you to wait months for results, due to the nature of the data being received and the rate a which it is received). When doing experimentation via exploratory data analysis (EDA), the engineer uses different statistics and visualizations of the data to find the best data points and combinations by looking for patterns, anomalies, and relationships between data (which can also inform the engineer that only one piece of data from the relationship needs to be used).&lt;/p&gt;

&lt;p&gt;Note that even though fraud models are trained on very old training data, realtime ML still gives you a massive leg up in fraud detection because of its adeptness with long-tail, as referenced in &lt;a href="https://fennel.ai/blog/seven-reasons-why-realtime-ml-is/"&gt;this post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unique Feature Engineering Challenges
&lt;/h2&gt;

&lt;p&gt;Feature engineering for fraud detection can be challenging for a number of reasons. As was mentioned before, fraud is a complex and dynamic problem that can take many different forms, which makes it difficult to identify the specific patterns and relationships that are indicative of fraudulent activity. &lt;/p&gt;

&lt;p&gt;Fraud detection often requires working with sensitive financial data, which may have strict privacy and security requirements. This can make it difficult to obtain the data that is needed for feature engineering and can also limit the types of transformations and manipulations that can be performed on the data. When working with sensitive data, your best chances of success are to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure data is properly secured – to protect against unauthorized access, implement security measures such as encryption, access controls, and monitoring.&lt;/li&gt;
&lt;li&gt;Consider which data is actually necessary – during the feature engineering stage, using tactics such as exploratory data analysis (EDA) helps you to see if any of the features in consideration have relationships with others, which can allow you to select the less sensitive metric for the model that will be put into production.&lt;/li&gt;
&lt;li&gt;Anonymize the data as much as possible – your use of sensitive data is more likely to be allowed to continue if proper protections are put in place. The first layer of ensuring this is making sure only the people who should have access to the data do, as mentioned in the first bullet. It is also a good practice to anonymize data within your system, in case of situations such as compromised credentials being utilized by bad actors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another challenge is that, as previously mentioned, labels are often delayed, so training data consists of very old examples (sometimes several months old). This means that, if you were to employ the popular practice of logging and waiting, you would need to wait months to test a new feature. This is why a practice such as EDA is often used. Another “issue” with labels being delayed is that point-in-time data feature reconstruction needs to cover a long window (often times a year) to get the necessary data volume. This also increases the risk of feature code changing, which further necessitates immutability or versioning of features to prevent confusion and ensure that you’re working with the correct data, in the correct format. &lt;/p&gt;

&lt;p&gt;Earlier, we mentioned that it is especially common to create features for fraud detection models by combining or splitting the features you receive from the raw data. This often means that you need more data sources to get enough data of the types you need, which increases the need to hit external APIs. Sometimes, this comes in the form of batched data dumps (like the credit scores for a large number of people as of a certain date) that need to be “merged” with live requests (like to a credit bureau) in a lambda-like architecture. With live requests, it is also important to consider the latency constraints mandated by the system or use case. In most cases with fraud detection, it is very sensitive to lag, since a bad actor can do a lot of damage in even a few seconds. This means that features must be only a few seconds old, if that. In addition, serving latencies of the models using those features need to be incredibly low; often, a serving latency of around 200ms end-to-end (including feature extraction and model scoring) is required to make a decision as to whether a transaction is fraud in an acceptable amount of time.&lt;/p&gt;

&lt;p&gt;Another reason that working with external APIs (e.g., hitting credit bureau APIs for credit scores) is hard is that it can be difficult to create correct point-in-time values; since the data is external, you don’t always have easy access to historical data or the details you need from it, meaning you need to ensure you record all of the right information when you first access the data. For example, we mentioned that the IP address is a feature that can be used to predict fraud; one issue with this is that the location of an IP changes over time, so you need to know the location of the IP at the time of training, which mandates careful managing of the data obtained from external APIs, so that you have the details you need when you need them in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Feature engineering is a complex artform, and the precision and thoughtfulness that needs to go into it when working with sensitive data and high stakes, like in the case of fraud detection, further complicates its planning and implementation. While there are a good number of things to account for when performing feature engineering for fraud detection, this article helps to highlight many of the challenges, technologies, and strategies that can be employed throughout this process to give you a leg up when going through the process. &lt;/p&gt;

&lt;p&gt;If you would like to see an example of how these strategies can be employed, NVIDIA did an &lt;a href="https://developer.nvidia.com/blog/leveraging-machine-learning-to-detect-fraud-tips-to-developing-a-winning-kaggle-solution/"&gt;awesome writeup&lt;/a&gt; of the winning solution from Kaggle’s IEEE CIS Fraud Detection competition.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For more high-quality technical content on all things machine learning, visit the &lt;a href="https://fennel.ai/blog/"&gt;Fennel Blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>machinelearning</category>
      <category>datascience</category>
      <category>frauddetection</category>
    </item>
    <item>
      <title>Build an iOS 1-on-1 Video Chat App with SwiftUI and Dolby.io</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Wed, 27 May 2020 18:59:03 +0000</pubDate>
      <link>https://forem.com/nickparsons/build-a-1-on-1-video-chat-with-swiftui-and-dolby-io-5c41</link>
      <guid>https://forem.com/nickparsons/build-a-1-on-1-video-chat-with-swiftui-and-dolby-io-5c41</guid>
      <description>&lt;p&gt;In this tutorial, we'll integrate video chat into an iOS application. To do this, we integrate &lt;a href="https://dolby.io" rel="noopener noreferrer"&gt;Dolby.io&lt;/a&gt;'s &lt;a href="https://dolby.io/products/interactivity-apis" rel="noopener noreferrer"&gt;Interactivity APIs&lt;/a&gt;, formally known as &lt;a href="https://www.voxeet.com/" rel="noopener noreferrer"&gt;Voxeet&lt;/a&gt;, into our application. Video chat can easily be integrated with &lt;a href="https://getstream.io/chat/" rel="noopener noreferrer"&gt;Stream Chat&lt;/a&gt; for a seamless communication experience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: the library is still named Voxeet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this part, the application will support 1-on-1 private chat. Since Dolby is a pure client-side library, we only configure our &lt;code&gt;ios&lt;/code&gt; application. However, to facilitate the UI for indicating whether a user has a call waiting, we use a few endpoints in the backend. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Because these are minor and largely stub implementations, we don't go into them in this tutorial. Please refer to the source code on &lt;a href="https://github.com/nparsons08/the-stream-swiftui" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; if you're curious. Also, ensure the backend is running if following along with this tutorial.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The app performs these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialize Voxeet libraries&lt;/li&gt;
&lt;li&gt;When a user navigates to the "People" screen, show a video icon next to a user's name.&lt;/li&gt;
&lt;li&gt;When a user clicks this icon, start and join a Voxeet conference with a unique alias. The user waits for the other party to join. The application informs the backend of the new call.&lt;/li&gt;
&lt;li&gt;When the other user enters following the previous steps, they'll be placed in a 1-on-1 conference. &lt;/li&gt;
&lt;li&gt;When either user leaves, the call is ended in the application. The backend is informed of the call ending.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voxeet's &lt;a href="https://github.com/voxeet/voxeet-uxkit-ios" rel="noopener noreferrer"&gt;UXKit&lt;/a&gt; takes care of the connection and presentation for this 1-on-1 call. Our application needs to create and join the call, and the UXKit will overlay the video call UI.&lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Dolby Account and Install Voxeet Dependencies 🔗
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://dolby.io" rel="noopener noreferrer"&gt;dolby.io&lt;/a&gt; and create an account. Once registered, navigate to the Dashboard (you can click in the top-right if you're not there). Find the list of applications:&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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F17ced3a1b1d98914293b073515b5ec92%2Fdolby-applications.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D715%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" 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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F17ced3a1b1d98914293b073515b5ec92%2Fdolby-applications.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D715%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" alt="Dolby Applications"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you already have an app ("my first app"), click into it. If you don't, create an app by hitting "Add New App". Now grab the API key and secret for our application. The keys we need are under the "Interactivity APIs" section. :&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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F332031393361a1440938855301e5d3eb%2Fdolby-keys.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D647%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" 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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F332031393361a1440938855301e5d3eb%2Fdolby-keys.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D647%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" alt="Dolby Keys"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on "Show Secret" to view the secret. &lt;/p&gt;

&lt;p&gt;Next, add &lt;code&gt;VoxeetUIKit&lt;/code&gt; to our Podfile and &lt;code&gt;pod install&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Since the &lt;code&gt;WebRTC&lt;/code&gt; dependency is larger than GitHub's file limit, we did not include the Pods in the repo. Please ensure you run &lt;code&gt;pod install&lt;/code&gt; before attempting to run!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Configure the Voxeet UXKit Library 🔖
&lt;/h2&gt;

&lt;p&gt;Since the Voxeet library is not tied to a &lt;code&gt;backend&lt;/code&gt; user account, we can configure it on the application load. We do this in the &lt;code&gt;AppDelegate&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Change &lt;code&gt;&amp;lt;VOXEET_CONSUMER_KEY&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;VOXEET_CONSUMER_SECRET&amp;gt;&lt;/code&gt; to the values you retrieved in Step 1. We configure Voxeet not to have any push notifications, turn on the speaker and video by default, and appear maximized. We also set &lt;code&gt;telecom&lt;/code&gt; to true. When this is set, the conference will behave like a cellular call, meaning when either party hangs up or declines the request (decline not implemented in this tutorial), it will end the call. &lt;/p&gt;

&lt;p&gt;If you'd like to use push notifications to notify a user when a call is incoming, check out &lt;a href="https://developer.apple.com/documentation/callkit" rel="noopener noreferrer"&gt;CallKit&lt;/a&gt; combined with &lt;code&gt;VoxeetSDK.shared.notification.push.type = .callKit&lt;/code&gt;. This is out of scope for this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Starting a Call 📱
&lt;/h2&gt;

&lt;p&gt;Next, we add a video action to the list to people, in between the start chat and follow icons from previous parts of this series. Here's what our view will look like:&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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F13f0b6476305901409a38875e3a7bd35%2Fpeople-list.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" 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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F13f0b6476305901409a38875e3a7bd35%2Fpeople-list.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" alt="People List"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do this, we just add another image view in our &lt;code&gt;ListView&lt;/code&gt; in &lt;code&gt;PeopleView&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is a simple system icon that we load via &lt;code&gt;systemName&lt;/code&gt;. With the icon in place, we can add a click handler via &lt;code&gt;onTapGesture&lt;/code&gt; to start our 1-on-1 call. Ignore the icon &lt;code&gt;foregroundColor&lt;/code&gt; call for now. We'll get to that in a bit.&lt;/p&gt;

&lt;p&gt;Let's look at &lt;code&gt;startConferenceCall&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Here we create our conference call using Voxeet with an &lt;code&gt;alias&lt;/code&gt;. We use this &lt;code&gt;alias&lt;/code&gt; as an identifier, so the other user's application knows how to join the same call. The call to &lt;code&gt;.create&lt;/code&gt; yields us a conference object. First, we call our &lt;code&gt;backend&lt;/code&gt; via &lt;code&gt;startCall&lt;/code&gt; to register the call, so the other user knows there's a call waiting. This is simply a &lt;code&gt;POST&lt;/code&gt; request:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: We use Voxeet's conference implementation as it's perfect for facilitating a video chat between two people. The conference object is more powerful than this (multiple users, broadcast streams, screen sharing, etc.), but here only use it for a 1-on-1 call. The terms conference and calls are used interchangeably in this tutorial, given the scope of our application.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Once we've notified the &lt;code&gt;backend&lt;/code&gt; of the call, we join the conference via &lt;code&gt;.join&lt;/code&gt;. Since we're using Voxeet's &lt;a href="https://github.com/voxeet/voxeet-uxkit-ios" rel="noopener noreferrer"&gt;UXKit&lt;/a&gt; a video chat UI slides up from the bottom automatically:&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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2Ffa7941c3e09e9f944985e33d66900a62%2Fvideo-waiting.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" 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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2Ffa7941c3e09e9f944985e33d66900a62%2Fvideo-waiting.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" alt="Video Waiting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Joining a Call 🤳
&lt;/h2&gt;

&lt;p&gt;Now that the user has started a call with someone, we want the other user to see a call started so they can join. To keep things simple, we just turn the video icon red if there's a call active. Recall from above that we are changing the video icon color via &lt;code&gt;foregroundColor&lt;/code&gt; via a call to &lt;code&gt;.videoIconColor&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Here we'll check a &lt;code&gt;@State&lt;/code&gt; var &lt;code&gt;calls&lt;/code&gt; for a call from the other user. If we do find one, we color the icon red. The &lt;code&gt;calls&lt;/code&gt; var gets initialized when &lt;code&gt;PeopleView&lt;/code&gt; appears via &lt;code&gt;fetch&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We call to &lt;code&gt;account.fetchCalls&lt;/code&gt; to retrieve a list of active calls for the current user:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is simply a &lt;code&gt;GET&lt;/code&gt; request against our mock &lt;code&gt;backend&lt;/code&gt; (refer to source). The response object will have a &lt;code&gt;from&lt;/code&gt; field, indicating who the user started the call.  &lt;/p&gt;

&lt;p&gt;Now that we know a user is calling the user will see a screen 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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F9cf389ad87100264851c7010f78aaa9c%2Fpeople-list-active.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" 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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F9cf389ad87100264851c7010f78aaa9c%2Fpeople-list-active.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" alt="People List - Active"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the user joins the call, the UXKit UI will change to show the call has started:&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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F9a83341789e5e7f4ed0cebfe46649f1b%2Fvideo-active.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" 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%2Fstream-blog-v2.imgix.net%2Fblog%2Fwp-content%2Fuploads%2F9a83341789e5e7f4ed0cebfe46649f1b%2Fvideo-active.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" alt="Video Active"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Leaving a Call ☎️
&lt;/h2&gt;

&lt;p&gt;When a user is done, they hang up using the end call icon. We don't need to do anything special on our side to end the call, Voxeet takes care of that. We need to listen for the conference call ending so we can notify the backend of the change. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: In a production application, you'd likely want to use Voxeet's push notifications and &lt;code&gt;CallKit&lt;/code&gt; bindings or look at binding Dolby's Interactivity API WebSockets to the backend. While the approach below works, it is less robust than using the full Notification system built into Dolby's Interactivity APIs.&lt;/p&gt;

&lt;p&gt;We'll bind to the notification center and listen for the conference call to end. Upon ending, we'll notify the backend the call has finished:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We use SwiftUI's convenient &lt;code&gt;.onReceive&lt;/code&gt; method to bind to the NotificationCenter event &lt;code&gt;.VTConferenceDestroyed&lt;/code&gt;. When this happens, we know the call has finished. Since we configured &lt;code&gt;VoxeetUXKit.shared.telecom = true&lt;/code&gt; this coincides on both sides when either party hangs up. When we get the notification, we simply call to the backend to stop the call via &lt;code&gt;account.stopCall&lt;/code&gt; and fetch the new set to refresh the view (effectively removing the red video indicator). The &lt;code&gt;stopCall&lt;/code&gt; is a simple &lt;code&gt;DESTROY&lt;/code&gt; call to the &lt;code&gt;backend&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And we're done! We now have a 1-on-1 chat between two parties integrated into our application using Dolby's Voxeet offering. For more information on how to integrate &lt;a href="https://getstream.io/chat/" rel="noopener noreferrer"&gt;Stream Chat&lt;/a&gt; within your application, have a look at the &lt;a href="https://github.com/nparsons08/the-stream-swiftui" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; repo.&lt;/p&gt;

&lt;p&gt;Happy coding! 🤓&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swiftui</category>
      <category>tutorial</category>
      <category>chat</category>
    </item>
    <item>
      <title>Encrypted Chat on iOS (Swift)</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Fri, 08 May 2020 18:52:22 +0000</pubDate>
      <link>https://forem.com/nickparsons/encrypted-chat-on-ios-swift-338k</link>
      <guid>https://forem.com/nickparsons/encrypted-chat-on-ios-swift-338k</guid>
      <description>&lt;p&gt;In this tutorial, we'll build encrypted chat on iOS using Swift. We'll combine &lt;a href="https://getstream.io/chat/"&gt;Stream Chat&lt;/a&gt; and &lt;a href="https://virgilsecurity.com/"&gt;Virgil Security&lt;/a&gt;. Both Stream Chat and Virgil make it easy to create a solution with high security with all the features you expect. These two services allow developers to integrate chat that is zero-knowledge. The application embeds Virgil Security's &lt;a href="https://github.com/VirgilSecurity/virgil-e3kit-x"&gt;E3Kit&lt;/a&gt; with &lt;a href="https://github.com/GetStream/stream-chat-swift"&gt;Stream Chat's Swift&lt;/a&gt; components.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: All source code for this application is available on &lt;a href="https://github.com/nparsons08/stream-ios-encrypted-chat"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is end-to-end encryption?
&lt;/h2&gt;

&lt;p&gt;End-to-end encryption means that two people can have trade messages via the internet without anyone else being able to read them, even if the transmission or storage is compromised. To do this, the app encrypts the message before it leaves a user's device, and only the intended recipient can decrypt the message.&lt;/p&gt;

&lt;p&gt;Virgil Security is a vendor that allows us to create end-to-end encryption via public/private key technology. Virgil provides a platform that allows us to create, store, and offer robust end-to-end encryption securely.&lt;/p&gt;

&lt;p&gt;During this tutorial, we will create a Stream Chat app that uses Virgil's encryption to prevent anyone except the intended parties from reading messages. No one in your company, nor any cloud provider you use, can read these messages. Even if a malicious person gained access to the database containing them, all they would see is encrypted text, called ciphertext.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an Encrypted Chat Application
&lt;/h2&gt;

&lt;p&gt;To build this application, we'll mostly rely on a few libraries from Stream Chat and Virgil (please check out the dependencies in the source to see what versions). Our final product will encrypt text on the device before sending a message. Decryption and verification will both happen in the receiver's device. Stream's Chat API will only see ciphertext, ensuring our user's data is never seen by anyone else, including you.&lt;/p&gt;

&lt;p&gt;To accomplish this, the app performs the following process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user authenticates with your backend.&lt;/li&gt;
&lt;li&gt;The iOS app requests a Stream auth token and API key from the &lt;code&gt;backend&lt;/code&gt;. The Swift app creates a &lt;a href="https://getstream.io/chat/docs/#init_and_users"&gt;Stream Chat Client&lt;/a&gt; for that user.&lt;/li&gt;
&lt;li&gt;The mobile app requests a Virgil auth token from the &lt;code&gt;backend&lt;/code&gt; and registers with Virgil, which generates their private and public key. The app stores the private key locally, and the public key in Virgil Cloud.&lt;/li&gt;
&lt;li&gt;Once the user decides who they want to chat with, the app creates and joins a &lt;a href="https://getstream.io/chat/docs/#initialize_channel"&gt;Stream Chat Channel&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The app asks Virgil's API, via E3Kit, for the receiver's public key.&lt;/li&gt;
&lt;li&gt;The user types a message and encrypts it with the receiver's public key using E3Kit, then sends it to Stream.  After that, Stream Chat relays the message to the receiver. Stream only receives ciphertext, meaning they will never see the original message.&lt;/li&gt;
&lt;li&gt;When the message is received, the app decrypts and verifies it is using E3Kit and passes it along to Stream Chat's iOS components for display.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While this looks complicated, Stream and Virgil do most of the work for us. We'll use Stream's out-of-the-box UI components to render the chat UI and Virgil to do all of the cryptography and key management. We simply combine these services. &lt;/p&gt;

&lt;p&gt;The code is split between the iOS frontend contained in the &lt;code&gt;ios&lt;/code&gt; folder, and the Express (Node.js) backend is found in the &lt;code&gt;backend&lt;/code&gt; folder. See the &lt;code&gt;README.md&lt;/code&gt; in each folder to see installing and running instructions. If you'd like to follow along with running code, make sure you get both the &lt;code&gt;backend&lt;/code&gt; and &lt;code&gt;ios&lt;/code&gt; running before continuing.&lt;/p&gt;

&lt;p&gt;Let's walk through and look at the critical code needed for each step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Basic knowledge of iOS (Swift) and Node.js is expected. This code is intended to run locally on your machine. &lt;/p&gt;

&lt;p&gt;You will need an account with &lt;a href="https://getstream.io/accounts/signup/"&gt;Stream&lt;/a&gt; and &lt;a href="https://dashboard.virgilsecurity.com/signup"&gt;Virgil&lt;/a&gt;. Once you've created your accounts, you can place your credentials in &lt;code&gt;backend/.env&lt;/code&gt; if you'd like to run the code. You can use &lt;code&gt;backend/.env.example&lt;/code&gt; as a reference for the required credentials.  Please see the README in the &lt;code&gt;backend&lt;/code&gt; directory for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0. Set up the Backend
&lt;/h2&gt;

&lt;p&gt;For our Swift app to securely interact with Stream and Virgil, the &lt;code&gt;backend&lt;/code&gt; provides four endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;POST /v1/authenticate&lt;/code&gt;: This endpoint generates an auth token that allows the iOS application to communicate with the other endpoints. To keep things simple, this endpoint enables the client to be any user. The frontend tells the backend who it wants to authenticate as. In your application, this should be replaced with real authentication appropriate for your app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;POST /v1/stream-credentials&lt;/code&gt;: This returns the data required for the iOS app to establish a session with Stream. In order return this info we need to tell Stream this user exists and ask them to create a valid auth token:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The response payload has this shape:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apiKey&lt;/code&gt; is the Stream account identifier for your Stream instance. It is needed to identify what account your frontend is trying to connect with.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;token&lt;/code&gt; JWT token to authorize the frontend with Stream.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;user&lt;/code&gt;: This object contains the data that the frontend needs to connect and render the user's view.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /v1/virgil-credentials&lt;/code&gt;: This returns the authentication token used to connect the frontend to Virgil. We use the Virgil Crypto SDK to generate a valid auth token for us:&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In this case, the frontend only needs the auth token.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/users&lt;/code&gt;: Endpoint for returning all users, which exists just to get a list of people to chat with. Please refer to the source if you're curious. Please note the &lt;code&gt;backend&lt;/code&gt; uses in-memory storage, so if you restart, it will forget all of the users.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1. User Authenticates With Backend
&lt;/h2&gt;

&lt;p&gt;The first step is to authenticate a user and get our Stream and Virgil credentials. To keep thing simple, we have an insecure form that allows you to log in as anyone:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y_fwz_DB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/27480b616641aba4791fe00c4253abc0/login.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y_fwz_DB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/27480b616641aba4791fe00c4253abc0/login.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" alt="Image shows a form with a single username field and a login button" width="800" height="1732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a simple form that takes any arbitrary name, effectively allowing us to log in as anyone (please use an appropriate authentication method for your application). First, let's add to &lt;code&gt;Main.storyboard&lt;/code&gt;. We add a "Login View Controller" scene that's backed by a custom controller &lt;code&gt;LoginViewController&lt;/code&gt; (to be defined). This controller is embedded in a Navigation Controller. Your storyboard should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iyGZyM3R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/22dc265aa6119394b173db55ad59b435/login-storyboard.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D1193%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iyGZyM3R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/22dc265aa6119394b173db55ad59b435/login-storyboard.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D1193%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" alt="Image shows a storyboard with a navigation controller pointing to a simple form controller" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form is a simple &lt;code&gt;Stack View&lt;/code&gt; with a &lt;code&gt;username&lt;/code&gt; field and a submit button. Let's look at our custom &lt;code&gt;LoginViewController&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;usernameField&lt;/code&gt; is bound to the storyboard's &lt;code&gt;Username Field&lt;/code&gt;, and the login method is bound to the &lt;code&gt;Login&lt;/code&gt; button. When a user clicks login, we check if there's a username and if so, we log in via &lt;code&gt;Account.shared.login&lt;/code&gt;. &lt;code&gt;Account&lt;/code&gt; is a shared object that will store our credentials for future backend interactions. Once the user logs in, we initiate the &lt;code&gt;UsersSegue&lt;/code&gt;, which boots our next scene. We'll see how this is done in a second, but first, let's see how the &lt;code&gt;Account&lt;/code&gt; object logs in.&lt;/p&gt;

&lt;p&gt;Here's how we define &lt;code&gt;Account&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;First, we set up our shared object that will store our login state in the &lt;code&gt;authToken&lt;/code&gt; and &lt;code&gt;userId&lt;/code&gt; properties. Note, &lt;code&gt;apiRoot&lt;/code&gt;  is the IP address where our backend is running. Please follow the instructions in the &lt;code&gt;backend&lt;/code&gt; to run it. Our &lt;code&gt;login&lt;/code&gt; method uses Alamofire (&lt;code&gt;AF&lt;/code&gt;) to make a &lt;code&gt;post&lt;/code&gt; request to our backend with the user to log in. Upon success, we store the &lt;code&gt;authToken&lt;/code&gt; and &lt;code&gt;userId&lt;/code&gt;, and then we call &lt;code&gt;setupStream&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The method &lt;code&gt;setupStream&lt;/code&gt; initializes our Stream Chat client. Here's the implementation:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We call to the &lt;code&gt;backend&lt;/code&gt; with our credentials from &lt;code&gt;login&lt;/code&gt;. We get back a &lt;code&gt;token&lt;/code&gt;, which is a Stream Chat token. This token allows our mobile application to communicate directly with Stream without going through our &lt;code&gt;backend&lt;/code&gt;. We also get an &lt;code&gt;apiKey&lt;/code&gt;, which identifies the Stream account we're using. We need this data to initialize our Stream &lt;code&gt;Client&lt;/code&gt; instance and set the user. Last, we initialize Virgil via &lt;code&gt;setupVirgil&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This method requests our Virgil credentials and configures a &lt;code&gt;VirgilClient&lt;/code&gt; class we defined, which wraps Virgil's E3Kit library. Once that's done, we call the &lt;code&gt;completion&lt;/code&gt; to indicate success and allow the application to move on.&lt;/p&gt;

&lt;p&gt;Let's see what the beginning of our &lt;code&gt;VirgilClient&lt;/code&gt; implementation looks like:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;VirgilClient&lt;/code&gt; is a singleton that is set up via &lt;code&gt;configure&lt;/code&gt;. We take the &lt;code&gt;identity&lt;/code&gt; (which is our username) and token, and then we generate a &lt;code&gt;tokenCallback&lt;/code&gt;. The &lt;code&gt;EThree&lt;/code&gt; client uses this callback to get a new JWT token. In our case, we've kept it simple by just returning the same token, but in a real application, you'd likely want to replace this with the rest call to the backend.&lt;/p&gt;

&lt;p&gt;We use this token callback and identity to initialize &lt;code&gt;eThree&lt;/code&gt;. We then use this instance to register the user. &lt;/p&gt;

&lt;p&gt;Now we're set up to start chatting!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Listing Users
&lt;/h2&gt;

&lt;p&gt;Next, we'll create a view to list users. In the &lt;code&gt;Main.storyboard&lt;/code&gt; we add a &lt;code&gt;UITableView&lt;/code&gt; and back it by our custom &lt;code&gt;UsersViewController&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vlK9Rq93--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/1a6ddd64c7560b3459c87cf14b6d26eb/users-storyboard.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D1089%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vlK9Rq93--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/1a6ddd64c7560b3459c87cf14b6d26eb/users-storyboard.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D1089%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" alt="Image shows a storyboard with a navigation controller with a segue to the login form and a segue to the users controller" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's the first few lines of &lt;code&gt;UsersViewController&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;First, we fetch the users when the view loads. We do this via the &lt;code&gt;Account&lt;/code&gt; instance configured during login. This action simply hits the &lt;code&gt;/v1/users&lt;/code&gt; endpoint. Refer to the source if you're curious. We store the users in a &lt;code&gt;users&lt;/code&gt; property and reload the table view. &lt;/p&gt;

&lt;p&gt;Let's see how we configure the table cells:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;To render the table correctly we indicate the number of rows via &lt;code&gt;users.count&lt;/code&gt; and set the text of the cell to the user at that index. With this list of users, we can set up a click action on a user's row to start a private 1-on-1 chat with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Starting an Encrypted Chat Channel
&lt;/h2&gt;

&lt;p&gt;First, add a new blank view to &lt;code&gt;Main.storyboard&lt;/code&gt; backed by a new custom class &lt;code&gt;EncryptedChatViewController&lt;/code&gt; (we'll see its implementation in a minute):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xxljdJ3x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/42317993ca95b0b435b376715aed6964/chat-storyboard.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D1006%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xxljdJ3x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/42317993ca95b0b435b376715aed6964/chat-storyboard.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D1006%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" alt="Image shows the same storyboard, now with an empty encrypted chat controller" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add a segue between from the user's table row to show the new controller:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MdgSJeIp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/7801e2120700b244499e24a7e14b2828/segue.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D1337%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MdgSJeIp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/7801e2120700b244499e24a7e14b2828/segue.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D1337%26ixlib%3Dphp-1.2.1%26w%3D2048%26wpsize%3D2048x2048" alt="Image shows the same storyboard, now highlighting the segue between the users and encrypted chat controller" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that setup, we can hook into the segue via the &lt;code&gt;UITableViewController&lt;/code&gt;'s &lt;code&gt;prepare&lt;/code&gt; method:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Before transitioning to the new view, we need to set the view controller up. We generate a unique channel id using the user ids. We initialize a &lt;code&gt;ChannelPresenter&lt;/code&gt; from the Stream library, set the type to messaging, and restrict the users. We grab the view controller from the segue and back it with the &lt;code&gt;ChannelPresenter&lt;/code&gt;. This will tell the controller which channel to use. We also tell it what users are communicating.&lt;/p&gt;

&lt;p&gt;Let's see how the controller creates this view:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Luckily, Stream comes with excellent UI components out of the box. We'll simply inherit from &lt;code&gt;ChatViewController&lt;/code&gt; to do the hard work of displaying a chat. The presenter we set up tells the Stream UI component how to render. All we need to do now is hook into the message lifecycle to encrypt and decrypt messages on the fly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Sending an Encrypted Message
&lt;/h2&gt;

&lt;p&gt;Now we're ready to send our first encrypted message. Since we're using Stream's built-in UI, all we need to do is hook into the message sending cycle. We'll do this via the &lt;code&gt;messagePreparationCallback&lt;/code&gt; on the &lt;code&gt;ChannelPresenter&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Upon loading the view, we set up the callback, which is a hook provided by Stream, to make any modifications we'd like to the message before sending it over the wire. Since we want to encrypt the message fully, we grab it and modify the text with the &lt;code&gt;VirgilClient&lt;/code&gt; object. &lt;/p&gt;

&lt;p&gt;For this to work, we need to look up the public key of the other user. Since this action requires a call to the Virgil API, it's asynchronous. We don't want to do this during message preparation, so we do it ahead of time via &lt;code&gt;VirgilClient.shared.prepareUser(otherUser!)&lt;/code&gt;. Let's see how that method is implemented:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is relatively simple with Virgil's E3Kit. We find the user's &lt;code&gt;Card&lt;/code&gt; which stores all of the information we need to encrypt and decrypt messages. We'll use this &lt;code&gt;Card&lt;/code&gt; in the &lt;code&gt;encrypt&lt;/code&gt; method during message preparation:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Once again, this is made easy by Virgil. We simply pass the text and the correct user card to &lt;code&gt;authEncrypt&lt;/code&gt;, and we're done! Our message is now ciphertext ready to go over the wire. Stream's library will take care of the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step #5: Decrypting a Message
&lt;/h2&gt;

&lt;p&gt;Since we're using the &lt;code&gt;ChatViewController&lt;/code&gt; to render the view, we only need to hook into the cell rendering. We need to decrypt the message text and pass it along. We override the &lt;code&gt;messageCell&lt;/code&gt; method:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We make a copy of the message and set the message's text to the decrypted value. In this case, we need to know if it's our message or theirs. First, we'll look at how to decrypt ours:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Since the message was encrypted initially on the device, we have everything we need. We simply ask Virgil E3Kit to decrypt it via &lt;code&gt;authDecrypt&lt;/code&gt;. Decrypting the other user's messages is a bit more work:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In this case, we use the same method, but we pass the user card that we retrieved earlier, which verifies the message's authenticity. Now we can see our full chat:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PyS5wScW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/d0cf368a8c52f14439c5a05865c80189/chat.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PyS5wScW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/d0cf368a8c52f14439c5a05865c80189/chat.png%3Fauto%3Dcompress%252Cformat%26fit%3Dscale%26h%3D2048%26ixlib%3Dphp-1.2.1%26w%3D946%26wpsize%3D2048x2048" alt="Image shows an encrypted conversation between two users" width="800" height="1732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's all! We now have an application that uses end-to-end encryption to protect a user's conversation. Happy coding!&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>chat</category>
      <category>encrypted</category>
    </item>
    <item>
      <title>Learn How Dubsmash Powers Millions of Users with Stream</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Thu, 16 Apr 2020 16:37:31 +0000</pubDate>
      <link>https://forem.com/nickparsons/learn-how-dubsmash-powers-millions-of-users-with-stream-26om</link>
      <guid>https://forem.com/nickparsons/learn-how-dubsmash-powers-millions-of-users-with-stream-26om</guid>
      <description>&lt;p&gt;Our team had a chance to sit down with &lt;a href="https://twitter.com/scriptedSheep"&gt;Tim Specht&lt;/a&gt;, Co-Founder and CTO at &lt;a href="https://dubsmash.com/"&gt;Dubsmash&lt;/a&gt;. If you’re not familiar with the organization, Dubsmash is a company that provides a social platform for users to share videos via their mobile applications. Users can choose an audio recording or soundbite from TV shows, movies, music, and other internet trends and record a video of themselves dubbing over that clip of audio, which can then be posted and shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Market Differentiators
&lt;/h2&gt;

&lt;p&gt;Dubsmash’s differentiating factor from other platforms on the market is its &lt;strong&gt;focus on creators&lt;/strong&gt;. The format, features, and functionality of the app keep the creators of the content in mind instead of curating everything toward the consumer due to Dubsmash’s prioritized belief that &lt;strong&gt;creators deserve attention&lt;/strong&gt;. The technology is geared toward helping creators make compelling content so that they can keep engagement up within their followership. Dubsmash hosts a program for artists and creators so that they can boost their careers following their passions- this initiative proves that Dubsmash is dedicated to giving back to its community. &lt;/p&gt;

&lt;p&gt;Additionally, Dubsmash has always and will always value the diversity of all kinds. It has been revealed that one of Dubsmash’s main competitors, &lt;a href="https://www.tiktok.com/"&gt;TikTok&lt;/a&gt;, has been explicitly modifying its content not to promote posts featuring unattractive people or poor neighborhoods in their ‘For You’ tab of ‘Recommended Videos.’ TikTok revolves its algorithms around elements that will generate the most amount of engagement, like ranking videos featuring the traditional standard of beauty or clean, modern homes as backdrops to grab society’s attention in the fastest, easiest way possible. &lt;/p&gt;

&lt;p&gt;At Dubsmash, there is not nearly as much censorship so that everyone has a fair shot at getting their ideas, art, and content out there, regardless of how someone looks or where they're filming their videos. One advantage of not being TikTok is that the app feels less crowded by semi-pro creators and influencers. That gives users the vibe that they’re more likely to hit the Trending or Explore page on Dubsmash. The Trending page is dominated by hot new songs and flashy dances, even if they’re shot with a lower production quality that feels accessible. Dubsmash gives space for more opportunity by making its 'Explore' page about discovering accounts and all the content they’ve made rather than specific videos. On Tiktok, popular clips may have multi-million views while on Dubsmash, there are only tens of thousands. However, there is still enough visibility to make shooting on Dubsmash worth it, especially for lesser-known artists who want to be discovered.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/mFTH9gNMSdJFbGav3z/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/mFTH9gNMSdJFbGav3z/giphy.gif" alt="Example of user-generated content" width="360" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dubsmash Market Share
&lt;/h2&gt;

&lt;p&gt;Dubsmash &lt;a href="https://www.appannie.com/"&gt;has captured 27% of the U.S. short-form video market share&lt;/a&gt; by installs, second to TikTok, which pulls 59%. Dubsmash holds 73% of the U.S. market outside of TikTok as far as active users go, compared to just 23% on Triller, 3.6% on Firework, and 0% on Facebook’s Lasso. Dubsmash has three times as many active users and saw 38% more first-time downloads in 2018 than 2019. 30% of Dubsmash’s daily users are creating content, resulting in 30% month-after-month retention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where is Dubsmash Hosted?
&lt;/h2&gt;

&lt;p&gt;Dubsmash uses a hybrid approach to hosting their application. Because the app is fundamentally a mobile application for iOS and Android devices, only the backend, and backend related services need to be hosted. For this, Dubsmash relies heavily on &lt;a href="https://www.heroku.com/deploy-with-docker"&gt;Heroku’s Container Service&lt;/a&gt; – all Docker images are stored in &lt;a href="https://quay.io/"&gt;Quay&lt;/a&gt;, allowing for near real-time rollbacks in the event that something goes wrong.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://heroku.com/"&gt;Heroku&lt;/a&gt; allows Dubsmash to scale both horizontally and vertically in an infinite way without having to worry about employing a large number of backend infrastructure engineers. Simply build, push, and deploy.&lt;/p&gt;

&lt;p&gt;Aside from Heroku, Dubsmash makes heavy use of &lt;a href="https://aws.amazon.com/"&gt;AWS&lt;/a&gt; as their primary service provider with &lt;a href="https://cloud.google.com/"&gt;GCP&lt;/a&gt; coming in second due to their robust cloud data warehouse – &lt;a href="https://cloud.google.com/bigquery/"&gt;BigQuery&lt;/a&gt;. According to Tim Specht, Co-Founder and CTO at Dubsmash, “If AWS offers a service, Dubsmash likely uses it to some extent.”&lt;/p&gt;

&lt;p&gt;GCP’s robust cloud datastore BigQuery offers insight into user data in a fraction of the time that their previously homegrown solution which utilized &lt;a href="https://aws.amazon.com/redshift/"&gt;AWS Redshift&lt;/a&gt;, among other tooling. “What would normally take hours of query time can now be done effectively and cost-efficiently in seconds using GCP’s BigQuery, allowing the team at Dubsmash to increase overall productivity and user experience.”&lt;/p&gt;

&lt;h2&gt;
  
  
  What Type of Infrastructure Does Dubsmash Utilize?
&lt;/h2&gt;

&lt;p&gt;Daily, Dubmash utilizes dozens of third-party services from various vendors to ensure optimal uptime and visibility into user-based metrics.&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="http://www.celeryproject.org/"&gt;Celery&lt;/a&gt; is heavily used as a distributed task queue for video decoding/encoding and used to process millions of tasks daily. &lt;a href="https://aws.amazon.com/memcached/"&gt;Memcached&lt;/a&gt; and &lt;a href="https://aws.amazon.com/redis/"&gt;Redis&lt;/a&gt; are used for caching, allowing for snappy load times; whereas Airflow is used for complex workflows such as analytical reporting and notifications, etc.&lt;/p&gt;

&lt;p&gt;The most important question that we asked is what database of choice Dubamash is using and why? The answer was sweet and simple – &lt;a href="https://aws.amazon.com/rds/postgresql/"&gt;PostgreSQL&lt;/a&gt;. The reasoning behind this is due to the many 1:1 relationships that Dubsmash implements. In contrast, a database such as &lt;a href="https://www.mongodb.com/"&gt;MongoDB&lt;/a&gt; wouldn’t make as much sense due to the lack of 1:N relationships and primary/secondary setup. The reads and writes to Postgres are efficient, as are queries. Postgres works excellent for our use-case, and we (Dubsmash) stand by our decision to use it as our core database.&lt;/p&gt;

&lt;p&gt;While this is not an exhaustive list of the various infrastructure that Dubsmash uses daily, it should give you a good idea of how Dubsmash operates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Primary Coding Languages Used In Production
&lt;/h2&gt;

&lt;p&gt;Python is the primary language of choice at Dubsmash; however, mobile applications are written in native languages. Currently, iOS is utilizing Swift, whereas the Android app is undergoing a migration from Java to Kotlin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Docker?
&lt;/h2&gt;

&lt;p&gt;Docker has been a fantastic product that we heavily utilize here at Dubsmash. Docker allows our developers to not only onboard themselves quickly and efficiently but not have to have in-depth knowledge and understanding of every part of our infrastructure’s stack. For example, our infrastructure relies on Python3, Django, PostgreSQL, Memcached, Celery, and Redis, to name a few.&lt;/p&gt;

&lt;p&gt;Without having Docker and Docker Compose at our disposal, our engineers would be &lt;em&gt;wasting precious time&lt;/em&gt; and valuable resources bringing themselves up to speed with the various products we utilize, best practices, setup, and cleanup scripts, etc.&lt;/p&gt;

&lt;p&gt;“Docker has been a game-changer for our team here at Dubsmash.”, according to Tim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third-Party Services Used
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Analytics
&lt;/h3&gt;

&lt;p&gt;Dubsmash rarely builds in-house products as they prefer to &lt;em&gt;buy vs. build&lt;/em&gt;. However, under certain circumstances, requirements come into play, and you have to make business decisions that ultimately move your business and engineering team forward.&lt;/p&gt;

&lt;p&gt;With this in mind, Dubsmash played around with &lt;a href="https://aws.amazon.com/kinesis/"&gt;AWS Kinesis&lt;/a&gt;, only to find that the product failed to meet their expectations from an analytics and cost perspective.&lt;/p&gt;

&lt;p&gt;Instead, Dubsmash dumps all of their data into GCP’s BigQuery, allowing the team to exercise automated and ad-hoc queries on the fly in a matter of seconds with a simple and easy to use query language. Depending on the type of query, and need for the query, the resulting data pulled from BigQuery will end up in &lt;a href="https://datastudio.google.com/"&gt;Google’s Data Studio&lt;/a&gt; for reporting purposes, or tossed into a workflow that &lt;a href="https://airflow.apache.org/"&gt;Airflow&lt;/a&gt; will take over (e.g., user push notifications, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  Realtime Search
&lt;/h3&gt;

&lt;p&gt;During the early stages of Dubsmash, the engineering team played around with several pieces of technology to build a home-grown search engine for real-time user search capabilities. After many wasted hours and a lot of money spent on hosting &lt;a href="https://aws.amazon.com/elasticsearch-service/"&gt;Elasticsearch&lt;/a&gt;, the team decided to switch their mindset and leave it to the experts at &lt;a href="https://www.algolia.com/"&gt;Algolia&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Algolia focuses 100% on scalable search, offering lightning-fast query response times compared to that of Elasticsearch.&lt;/p&gt;

&lt;p&gt;According to Dubsmash, Elasticsearch had a complicated and proprietary query language, offered poor performance when it came to indexing and reindexing data, and was an overall pain to keep up and running efficiently. With Algolia, Dubsmash was able to cut their time spend on search by a fraction and focus on what matters most – user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feeds as a Service
&lt;/h3&gt;

&lt;p&gt;At the core of Dubsmash lies &lt;a href="https://getstream.io/"&gt;Stream&lt;/a&gt;, the &lt;strong&gt;primary driving force for the Feeds infrastructure&lt;/strong&gt; users rely on to discover content within the application.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Co-Founder and CTO admitted that they had been looking at Stream for a while, and even attempted to build an in-house feed service, but failed when it came to overall cost and performance.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Building a custom in-house infrastructure to power feeds, accompanied with ranking (weights), speed, reliability, and something cost-effective is a true challenge, and we thank Stream for everything that they do.” - Tim Specht&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By utilizing &lt;a href="https://getstream.io/activity-feeds/"&gt;Stream's Feeds Infrastructure&lt;/a&gt;, Dubsmash can scale their service to an infinite number of users while having the ability to make real-time tweaks to sorting and weighting, time decay, etc., while having peace of mind that their application won’t come to a halt due to hiccups that may have come up in a home-grown solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transactional Email
&lt;/h3&gt;

&lt;p&gt;Emails aren’t used all that often with Dubsmash as they rely heavily on SMS and Push for notifications. However, when emails are sent, &lt;a href="https://aws.amazon.com/ses/"&gt;AWS Simple Email Services (AWS SES)&lt;/a&gt; is used.&lt;/p&gt;

&lt;h3&gt;
  
  
  SMS Notifications
&lt;/h3&gt;

&lt;p&gt;SMS is the primary form of communication that Dubsmash utilizes to interact with its users (aside from Push Notifications). To establish a reliable SMS workflow, Dubsmash uses &lt;a href="https://aws.amazon.com/sns/"&gt;AWS Simple Notification Service (AWS SNS)&lt;/a&gt; to send messages to users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push Notifications
&lt;/h3&gt;

&lt;p&gt;Push notifications are how Dubsmash connects with their uses. For example, if a user hasn’t logged into their application in a couple of days, Dubsmash will trigger a push notification to the user to re-engage the user and bring them back into the application.&lt;/p&gt;

&lt;p&gt;At times, Dubsmash has had to send several million push notifications in a single go, sometimes peaking 16+ million push notifications a day. This is hard on any type of infrastructure due to the hardware and network requirements behind the scene. To accomplish this, Dubsmash utilizes a combination of &lt;a href="https://aws.amazon.com/lambda/"&gt;AWS Lambda&lt;/a&gt; and &lt;a href="https://aws.amazon.com/sns/"&gt;AWS SNS&lt;/a&gt; to send notifications to users in an efficient method.&lt;/p&gt;

&lt;p&gt;To accomplish this scale, the engineers at Dubsmash have devised a method to send batched requests to an AWS Lambda instance, which then divides the total number of messages into batches of 500. From those batches of 500 messages, AWS Lambda instances are provisioned on the fly (coded in Python for little overhead) and sent via AWS SNS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Hosting
&lt;/h3&gt;

&lt;p&gt;Similar to many other applications, Dubsmash uses &lt;a href="https://aws.amazon.com/s3/"&gt;AWS S3&lt;/a&gt; to store images in the cloud. From there, images are delivered to the client via &lt;a href="https://aws.amazon.com/cloudfront/"&gt;AWS CloudFront&lt;/a&gt; (Amazon's Web Services CDN).&lt;/p&gt;

&lt;h3&gt;
  
  
  Video Streaming
&lt;/h3&gt;

&lt;p&gt;Streaming video has always been a challenge for many applications. Luckily, Dubmash knows this problem all too well as they specialize in streaming videos to user's devices.&lt;/p&gt;

&lt;p&gt;If you have any background in streaming video, you’ll know that it’s a hard problem to solve. There are many constraints on file sizes, file types (e.g., HLS, MPEG-dash), and even enforcements that are put in place by the user's end browser. To get around these constraints, Dubsmash stores all videos at MP4 files on AWS S3 and streams them via AWS Cloudfront to the user's devices and force MP4 (rather than utilizing adaptive streaming).&lt;/p&gt;

&lt;p&gt;By doing so, this ensures that there is no downgrading of the video, enforcing the best user experience possible– a core value of Dubsmash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Vision
&lt;/h2&gt;

&lt;p&gt;Dubsmash is always looking ahead in terms of how to grow and evolve as a social media platform. Here are some of their goals for the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build more tools dedicated to the content creators. Dubsmash wants to create more opportunities and flexibility for artists so that they can make high-engagement products for their audiences in creative ways.&lt;/li&gt;
&lt;li&gt;Grow user-base inclusively and fairly.&lt;/li&gt;
&lt;li&gt;Increase personalization and customization. Dubsmash wants to curate the most relevant content for each user, making proper and attractive recommendations based on app-activity.&lt;/li&gt;
&lt;li&gt;Continue to make Dubsmash a fun experience for both creators and consumers&lt;/li&gt;
&lt;li&gt;Future features: Filters, speed controls, time stickers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A lot of complex, technical, detailed work goes into this app behind the scenes to create a simple, clean, straightforward user experience. Dubsmash will continue to differentiate themselves by positioning their app as a legitimate tastemaking and community platform, as long as a utility for content-creators. Dubsmash is now up to &lt;em&gt;one billion video views per month&lt;/em&gt;, and this number will likely keep growing as the team keeps innovating and building.&lt;/p&gt;

</description>
      <category>interview</category>
      <category>infrastructure</category>
      <category>heroku</category>
      <category>stream</category>
    </item>
    <item>
      <title>Migrate from Pusher Chatkit to Stream Chat</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Thu, 02 Apr 2020 16:23:22 +0000</pubDate>
      <link>https://forem.com/nickparsons/migrate-from-pusher-chatkit-to-stream-chat-259n</link>
      <guid>https://forem.com/nickparsons/migrate-from-pusher-chatkit-to-stream-chat-259n</guid>
      <description>&lt;p&gt;As &lt;a href="https://getstream.io/blog/pusher-shutting-down-chatkit/"&gt;previously mentioned in a blog post by Stream&lt;/a&gt;, &lt;a href="https://pusher.com"&gt;Pusher&lt;/a&gt; recently announced its intention to shut down their real-time messaging service, &lt;a href="https://pusher.com/chatkit"&gt;Chatkit&lt;/a&gt;, effective &lt;strong&gt;April 23rd, 2020&lt;/strong&gt;, to narrow its product focus to &lt;a href="https://pusher.com/channels"&gt;Channels&lt;/a&gt; and &lt;a href="https://pusher.com/beams"&gt;Beams&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Although Pusher Chatkit fulfilled the basic premise of providing a real-time chat solution, their feature set and capabilities were way behind what the competition and &lt;a href="https://getstream.io/chat/"&gt;Stream Chat&lt;/a&gt; in particular offers, which is why they were ultimately unable to compete.&lt;/p&gt;

&lt;p&gt;The abrupt decision has disrupted critical services for all customers on the Chatkit platform. Unfortunately, it's now up to those customers to scramble to migrate off the Pusher Chatkit platform to another provider.&lt;/p&gt;

&lt;p&gt;The good news is that it’s possible and easy to move your data off Pusher Chatkit and over to Stream Chat, and many Chatkit users have been onboarded successfully to the Stream platform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If you’re curious about how Pusher Chatkit stacks up to Stream Chat, check out this &lt;a href="https://getstream.io/blog/stream-vs-pusher/"&gt;detailed comparison post&lt;/a&gt;. Our team has also compiled a &lt;a href="https://github.com/GetStream/pusher-chatkit-migration"&gt;detailed migration guide which is hosted on GitHub&lt;/a&gt; for your convenience.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How Stream Enhances Your Chatkit App
&lt;/h2&gt;

&lt;p&gt;Both Stream and Chatkit provide you with the basic features of chat: one or one or group messaging, user accounts, typing indicators, unread message counts, and more, as well as backend infrastructure. But that’s about where the similarities end. Stream Chat is far more extensible and easier to use compared to Chatkit, as you’ll see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ready-Made Frontend Components
&lt;/h2&gt;

&lt;p&gt;With Chatkit, you are required to roll a custom interface and implement all of the features that you required within your application. As you may know or are imagining as a developer, this is much work. The process is resource and time-intensive, which results in a large sum of money required to build on top of Chatkit. Stream Chat, on the other hand, provides ready to go components that allow you to build a fully functional real-time messaging application in a matter of hours.&lt;/p&gt;

&lt;p&gt;By using Stream components, you get many basic and advanced features in your app with minimal effort. Features include mentions, slash commands, reactions, threads, gifs, emoticons, file attachments, rich link previews, and much more. You can also decide to roll out a custom interface using the &lt;a href="https://www.npmjs.com/package/stream-chat"&gt;Stream Chat JS client&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VP69p8ad--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/914be745da4892f4118d9e3c84ed6d98/s_BED127FBDA6D0A16BC7F0EB80B08F955ACB413E246786837D94F32062E0FB5ED_1585807779335_image.png%3Fauto%3Dcompress%252Cformat%26ixlib%3Dphp-1.2.1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VP69p8ad--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/914be745da4892f4118d9e3c84ed6d98/s_BED127FBDA6D0A16BC7F0EB80B08F955ACB413E246786837D94F32062E0FB5ED_1585807779335_image.png%3Fauto%3Dcompress%252Cformat%26ixlib%3Dphp-1.2.1" alt="A demo Chat app built with Stream’s React components. Edit on CodePen." width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliable Infrastructure &amp;amp; Scalability
&lt;/h2&gt;

&lt;p&gt;While Pusher's Chatkit product only supports up to 500 members in a single channel, Stream Chat has zero limitations and supports any number of participants in a single channel. Stream Chat has been proven to be highly scalable, with over 500 million users on both its chat and feed APIs. And with an industry-leading 99.99% uptime record and response times as low as 5ms, you are guaranteed stability and robustness when you build on the Stream platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enterprise Ready
&lt;/h2&gt;

&lt;p&gt;Compared to Chatkit (or anyone else in the industry for that matter), Stream is better equipped to support enterprise customers who have unique requirements due to their scale and risk profiles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--blptcCNv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/6ea7b80668be49c4ed40517a366e0b71/s_BED127FBDA6D0A16BC7F0EB80B08F955ACB413E246786837D94F32062E0FB5ED_1585809138942_image.png%3Fauto%3Dcompress%252Cformat%26ixlib%3Dphp-1.2.1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--blptcCNv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stream-blog-v2.imgix.net/blog/wp-content/uploads/6ea7b80668be49c4ed40517a366e0b71/s_BED127FBDA6D0A16BC7F0EB80B08F955ACB413E246786837D94F32062E0FB5ED_1585809138942_image.png%3Fauto%3Dcompress%252Cformat%26ixlib%3Dphp-1.2.1" alt="Stream Chat - Enterprise Ready" width="800" height="734"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to Migrate from Chatkit to Stream
&lt;/h2&gt;

&lt;p&gt;Regardless of your applications scale or active user base, you can migrate to Stream seamlessly in a matter of days and suffer zero data loss. Stream is well experienced with migration procedures haven handled countless migrations for small and large scale customers alike, including &lt;a href="https://getstream.io/blog/layer-shutting-down-all-chat-operations/"&gt;when Layer shutdown&lt;/a&gt; some time ago.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://github.com/GetStream/pusher-chatkit-migration"&gt;detailed migration guide&lt;/a&gt; is now available on GitHub and provides all the steps you need to take to move your app’s data over to the Stream platform.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The migration will start with a data export of your Pusher Chatkit data which is outlined on &lt;a href="https://github.com/GetStream/pusher-chatkit-migration#step-1---pusher-chatkit-export"&gt;here on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Instantiating Your Chat Client
&lt;/h2&gt;

&lt;p&gt;Similar to Pusher's Chatkit, Stream Chat requires that you instantiate a chat client instance for browser/mobile usage. You can also provide additional options, such as API base URL and request timeouts when instantiating the client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chatkit:&lt;/strong&gt;&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;chatkit&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;Chatkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;instanceLocator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;YOUR_INSTANCE_LOCATOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stream:&lt;/strong&gt;&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;client&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;StreamChat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_KEY&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="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Users
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://getstream.io/chat/docs/init_and_users/?language=js"&gt;Users in Stream&lt;/a&gt; are similar to users in Chatkit and are identified using custom IDs. While Chatkit uses an authorization URL based approach when authenticating users, Stream Chat employs a token-based system where a token is generated on the server-side and sent to the client to provide access to the chat for that user.&lt;/p&gt;

&lt;p&gt;Stream also supports custom metadata for users, so it’s easy to migrate your Chatkit user &lt;code&gt;customData&lt;/code&gt;  property to the corresponding Stream user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chatkit:&lt;/strong&gt;&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;chatManager&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;ChatManager&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;instanceLocator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your_instance_locator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tokenProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TokenProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your.auth.url&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chatManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stream:&lt;/strong&gt;&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;currentUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;john&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://getstream.io/random_svg/?name=John&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamxhaGV5In0.OkDbpbujWJ-XIVHaf00Dnqt3v8Yp_nQ6CGzm-Z4QUVc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// authentication token&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rooms &amp;amp; channels
&lt;/h2&gt;

&lt;p&gt;Chatkit has the concept of public and private rooms and supports one-on-one chat as well as group chats (up to 500 members). Stream Chat, on the other hand, provides channels that are highly customizable and can be specialized for a large variety of use cases. Further, group chats can support an unlimited amount of users.&lt;/p&gt;

&lt;p&gt;Stream provides 5 built-in channel types by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://getstream.io/chat/demos/livestream/"&gt;&lt;strong&gt;Livestream&lt;/strong&gt;&lt;/a&gt;: Sensible defaults in case you want to build chat like YouTube or Twitch.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://getstream.io/chat/demos/messaging/"&gt;&lt;strong&gt;Messaging&lt;/strong&gt;&lt;/a&gt;: Configured for one-on-one chat solutions. This roughly maps to Chatkit’s private rooms.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://getstream.io/chat/demos/team/"&gt;&lt;strong&gt;Team&lt;/strong&gt;&lt;/a&gt;: If you want to build your version of Slack or something similar, start here. This is similar to Chatkit’s public rooms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gaming&lt;/strong&gt;: Configured for in-game chat.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://getstream.io/chat/demos/customers/"&gt;&lt;strong&gt;Commerce&lt;/strong&gt;&lt;/a&gt;: Good defaults for building something with similar features to that of Intercom or Drift.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want something custom, you can &lt;a href="https://getstream.io/chat/docs/channel_features/?language=js#creating-a-channel-type"&gt;create a channel type that meets your needs&lt;/a&gt; and configure &lt;a href="https://getstream.io/chat/docs/chat_permission_policies/?language=js"&gt;permissions&lt;/a&gt; for its members as necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chatkit:&lt;/strong&gt;&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;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createRoom&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#general&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;General&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;addUserIds&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="s1"&gt;craig&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="s1"&gt;kate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;customData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stream:&lt;/strong&gt;&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;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;team&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="s1"&gt;#general&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;General&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;members&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="s1"&gt;craig&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="s1"&gt;kate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;custom_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Messages
&lt;/h2&gt;

&lt;p&gt;Stream supports all the properties of Chatkit messages and more. The difference is, unlike Chatkit, Stream Chat does not break a message down into multiple parts, so you need to account for this when migrating your messages. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://getstream.io/chat/docs/send_message/?language=js"&gt;Sending a message in Stream&lt;/a&gt; is as simple as using the &lt;code&gt;sendMessage&lt;/code&gt; method on a channel, and you can add text, attachments, or even mentioned users to a message object (something that Chatkit lacks). Note that messages and &lt;a href="https://getstream.io/chat/docs/file_uploads/?language=js"&gt;attachments&lt;/a&gt; also support custom data, so you can easily migrate this off your Chatkit messages as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chatkit:&lt;/strong&gt;&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMultipartMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myRoom&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;parts&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="na"&gt;type&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="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello world!&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/gif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://gfycat.com/failingforkedheterodontosaurus&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="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#attach&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;customData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stream:&lt;/strong&gt;&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello @Josh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attachments&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;asset_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://bit.ly/2K74TaG&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;thumb_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://bit.ly/2Uumxti&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;myCustomField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;mentioned_users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;josh&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;anotherCustomField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;234&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Further Resources for Migration to Stream Chat
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Follow our &lt;a href="https://github.com/GetStream/pusher-chatkit-migration"&gt;Chatkit Migration Guide&lt;/a&gt; on GitHub.&lt;/li&gt;
&lt;li&gt;Check out our &lt;a href="https://getstream.io/chat/demos/"&gt;demos&lt;/a&gt; that we have made available to see the various types of experiences you can build with the Stream Chat API.&lt;/li&gt;
&lt;li&gt;Read &lt;a href="https://getstream.io/blog/topic/tutorials/"&gt;tutorials&lt;/a&gt; on The Stream Blog.&lt;/li&gt;
&lt;li&gt;Check out the &lt;a href="https://getstream.io/chat/docs/"&gt;Stream Chat docs&lt;/a&gt; to view all the features and integrations available to you.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Please feel free to contact &lt;a href="//mailto:support@getstream.io"&gt;support@getstream.io&lt;/a&gt; if you are ready to migrate to Stream Chat or if you have any questions.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>chat</category>
      <category>messaging</category>
      <category>migration</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Make an Encrypted Messaging App</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Thu, 26 Mar 2020 20:54:21 +0000</pubDate>
      <link>https://forem.com/nickparsons/build-an-encrypted-messaging-app-secure-android-chat-app-4g6m</link>
      <guid>https://forem.com/nickparsons/build-an-encrypted-messaging-app-secure-android-chat-app-4g6m</guid>
      <description>&lt;p&gt;Hey Android Devs!&lt;/p&gt;

&lt;p&gt;Looking to implement encrypted chat/messaging into your Android app?&lt;/p&gt;

&lt;p&gt;There is a great post over on &lt;a href="https://getstream.io/blog/"&gt;The Stream Blog&lt;/a&gt; on &lt;a href="https://getstream.io/blog/encrypted-messaging-app-android/"&gt;How to Build an Encrypted Chat/Messaging App for Android&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everything is written in Kotlin and well detailed. It comes with a complimentary repository if you want to jump right into the code. That can be found &lt;a href="https://github.com/nparsons08/stream-android-encrypted-chat"&gt;here&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;Enjoy and happy coding! 🤓&lt;/p&gt;

</description>
      <category>android</category>
      <category>e2ee</category>
      <category>messaging</category>
      <category>chat</category>
    </item>
    <item>
      <title>Android Chat Bubbles: Building iOS Style Chat in Android</title>
      <dc:creator>Nick Parsons</dc:creator>
      <pubDate>Tue, 10 Mar 2020 18:11:43 +0000</pubDate>
      <link>https://forem.com/nickparsons/android-chat-bubbles-building-ios-style-chat-in-android-4dd6</link>
      <guid>https://forem.com/nickparsons/android-chat-bubbles-building-ios-style-chat-in-android-4dd6</guid>
      <description>&lt;p&gt;In this post, we'll explore how to do two things: &lt;strong&gt;1)&lt;/strong&gt; create live chat message bubbles in Android that are similar to WhatsApp and iMessage and &lt;strong&gt;2)&lt;/strong&gt; customize Stream Chat's &lt;a href="https://github.com/GetStream/stream-chat-android#ui-components--chat-views"&gt;UI Components&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We'll customize &lt;a href="https://github.com/GetStream/stream-chat-android"&gt;Stream Chat Android&lt;/a&gt;'s built-in UI components by plugging in a custom message view. This allows us to focus on the text rendering, while Stream does everything else.&lt;/p&gt;

&lt;p&gt;The source code is available &lt;a href="https://github.com/nparsons08/stream-android-bubble-chat"&gt;here&lt;/a&gt;. Once we're finished, we'll have a chat experience that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oWXauY3r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/Olclwy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oWXauY3r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/Olclwy8.png" alt="" width="800" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;This post assumes a working knowledge of &lt;a href="https://developer.android.com/"&gt;Android&lt;/a&gt; and &lt;a href="https://kotlinlang.org/"&gt;Kotlin&lt;/a&gt;. If you're unfamiliar with either or both, it may be useful to check out a &lt;a href="https://developer.android.com/training/basics/firstapp"&gt;getting started&lt;/a&gt; guide. If you'd like to run the code, you'll also need a Stream account. Please &lt;a href="https://getstream.io/chat/trial/"&gt;register here&lt;/a&gt;. Once you're registered, you'll see a Stream app with an &lt;strong&gt;App ID&lt;/strong&gt;, &lt;strong&gt;API Key&lt;/strong&gt;, and &lt;strong&gt;API Secret&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V4zQNK_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/xxWKu5P.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V4zQNK_o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/xxWKu5P.png" alt="" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We won't be going through how to create the backend to receive our tokens in &lt;em&gt;this&lt;/em&gt; tutorial; please refer to &lt;a href="https://github.com/nparsons08/stream-chat-api"&gt;this repo&lt;/a&gt; for token generation. You can also use &lt;a href="https://www.npmjs.com/package/getstream-cli"&gt;Stream's CLI&lt;/a&gt;; if you look in &lt;a href="https://github.com/nparsons08/stream-android-bubble-chat/blob/master/app/src/main/java/com/example/bubblechat/MainActivity.kt"&gt;&lt;code&gt;MainActivity.kt&lt;/code&gt;&lt;/a&gt;, you'll see placeholders to fill in run the application.&lt;/p&gt;

&lt;p&gt;Let's build!&lt;/p&gt;

&lt;h2&gt;
  
  
  Listing Channels
&lt;/h2&gt;

&lt;p&gt;First, we'll create a view which displays a list of channels for a user to select:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jFbSfGhs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/TJwpltk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jFbSfGhs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/TJwpltk.png" alt="" width="800" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This view is mostly taken care of by Stream's UI Components. To list our channels, the &lt;code&gt;MainActivity.kt&lt;/code&gt; will leverage Stream's &lt;a href="https://github.com/GetStream/stream-chat-android/blob/master/docs/ChannelList.md"&gt;&lt;code&gt;ChannelList&lt;/code&gt;&lt;/a&gt;, and we'll configure it to load all of the channels for our user. Here's the code:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;First, we initialize our &lt;code&gt;StreamChat&lt;/code&gt; instance with our API key. We configure it with the user who'll be using our application. To keep things simple, we'll just declare who's logged in and their frontend token. In a real application, you'd want to perform authentication with a &lt;a href="https://getstream.io/blog/tutorial-user-auth-with-stream-chat-feeds/"&gt;backend&lt;/a&gt; that generates this token.&lt;/p&gt;

&lt;p&gt;We also give the user an &lt;code&gt;id&lt;/code&gt;, a &lt;code&gt;name&lt;/code&gt;, and an &lt;code&gt;image&lt;/code&gt;(profile image). Once we've done this, we can declare the layout as "&lt;code&gt;activity_main&lt;/code&gt;". &lt;/p&gt;

&lt;p&gt;Now, the view needs a view model; in this case, we'll simply use the default, Stream-provided "&lt;code&gt;ChannelListViewModel&lt;/code&gt;". This is great, as it does all the work to interact with Stream's API. We simply need to configure it to &lt;a href="https://getstream.io/chat/docs/query_channels/"&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/a&gt; for our user's channels. &lt;/p&gt;

&lt;p&gt;The last thing we do is set a "click listener" on each channel. We boot a &lt;code&gt;ChannelActivity&lt;/code&gt;, which is where we'll customize the chat messages. Before we look at the code for &lt;code&gt;ChannelActivity&lt;/code&gt; let's look at the layout:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/nparsons08/e7436c12791e8a123cb1e409628c1597"&gt;https://gist.github.com/nparsons08/e7436c12791e8a123cb1e409628c1597&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We use a &lt;code&gt;ConstraintLayout&lt;/code&gt; to hold our &lt;code&gt;ChannelListView&lt;/code&gt; and associated &lt;code&gt;ProgressBar&lt;/code&gt;s. Since the view is mostly taken care of by Stream, we just need to configure when it is that our &lt;code&gt;ProgressBar&lt;/code&gt;s show and what our &lt;code&gt;padding&lt;/code&gt; and &lt;code&gt;margin&lt;/code&gt; are.  &lt;/p&gt;

&lt;p&gt;Now, we're ready to view a specific channel!&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing a Channel
&lt;/h2&gt;

&lt;p&gt;Once a user clicks a channel, our &lt;code&gt;ChannelActivity&lt;/code&gt; starts. Here is the code:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;First, we get our channel information off of the &lt;code&gt;Intent&lt;/code&gt; from our &lt;code&gt;ChannelActivity.newIntent&lt;/code&gt; call, in &lt;code&gt;MainActivity.kt&lt;/code&gt;, and, then, below, we set our content view &lt;code&gt;activity_channel&lt;/code&gt; and &lt;code&gt;viewModel&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;You'll notice this was another &lt;code&gt;ConstraintLayout&lt;/code&gt; with some views and progress bars; we use Stream's UI Components to build the majority of the view. &lt;code&gt;ChannelHeaderView&lt;/code&gt; gives us a nice header with a channel image and channel name (from the parent). &lt;code&gt;MessageListView&lt;/code&gt; displays our messages, and &lt;code&gt;MessageInputView&lt;/code&gt; gives us a helpful default message input.&lt;/p&gt;

&lt;p&gt;We use the built-in Stream view model and feed that to each view. However, we customize the &lt;code&gt;MessageListView&lt;/code&gt; view by providing a custom message view "factory", &lt;code&gt;BubbleMessageViewHolderFactory&lt;/code&gt;. This "factory" hooks into Stream's code to provide a custom view for each message. This class is straightforward since all we're going to do is instantiate our bubble message class, &lt;code&gt;BubbleMessageViewHolder&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/nparsons08/16ead4c7aa2bf6fe6851646eb9c29179"&gt;https://gist.github.com/nparsons08/16ead4c7aa2bf6fe6851646eb9c29179&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we're hooked into the view rendering, we can customize our messages to look like iMessage or WhatsApp!&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering Custom Bubble Messages
&lt;/h2&gt;

&lt;p&gt;We're ready to declare our custom bubble view. To hook into Stream's components, we need to be a subclass of &lt;code&gt;BaseMessageListItemViewHolder&lt;/code&gt;. Here is the basic class definition:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We're passed a &lt;code&gt;viewGroup&lt;/code&gt; resource from the factory (we will be using "&lt;code&gt;bubble_message&lt;/code&gt;"). Then, this view is referred to by &lt;code&gt;itemView&lt;/code&gt;. From this, we grab our message header (which will just be spacing, in our case), our text view that will hold the message, and our username view. We also have a &lt;code&gt;MessageListItem&lt;/code&gt; and &lt;code&gt;ChannelState&lt;/code&gt;, which contain data for our Stream message and channel, respectively. Let's check out our view:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This view is quite simple, as it defines the three pieces we referenced before: the header spacing, the username, and the text. Notice there's no bubble logic here. To get the correct colors and shapes, we'll use &lt;a href="https://developer.android.com/guide/topics/resources/drawable-resource"&gt;drawables&lt;/a&gt; and some logic in our class to define how the message will look; this layout is simply the scaffolding for us to fill in.&lt;/p&gt;

&lt;p&gt;Implementing this abstract class requires us to implement &lt;code&gt;setStyle&lt;/code&gt; and &lt;code&gt;bind&lt;/code&gt;. We'll ignore &lt;code&gt;setStyle&lt;/code&gt; for this tutorial since we'll be defining all of our styles outright. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can check out &lt;a href="https://github.com/GetStream/stream-chat-android/blob/master/library/src/main/java/com/getstream/sdk/chat/view/MessageListViewStyle.java"&gt;&lt;code&gt;MessageViewListStyle&lt;/code&gt;&lt;/a&gt; to see all the ways you can customize the components without building your own class; with that said, in this post, we want to explore how to do it ourselves!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's dive in! The &lt;code&gt;bind&lt;/code&gt; method is where we configure how the message looks:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We're given a &lt;code&gt;ChannelState&lt;/code&gt; and &lt;code&gt;MessageListItem&lt;/code&gt;, which has everything we need to figure out our display. First, we check the type of message. Stream has a few different message types, which represent different things. For example, you may get a message type that indicates a user is typing or a date separator. In our case, we'll focus on two, the actual messages and date separators; we'll ignore any other message type and remove them from rendering, entirely. &lt;/p&gt;

&lt;p&gt;Let's focus on the actual messages to start; we'll look at how to render the date separators after. Here is our &lt;code&gt;configMessage&lt;/code&gt; method:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We do three things: 1) configure the actual text display, 2) decide if we need to show a username 3) determine how much spacing we have between messages.&lt;/p&gt;

&lt;p&gt;First, let's see how we configure the actual text and render our bubble message:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We set the &lt;code&gt;TextView&lt;/code&gt;'s text, then decide if it's "our" message or "theirs". If it's "ours", we display the message on the right, in blue. If it's "theirs", it goes on the left, in grey. To set the justification of the message, we use &lt;code&gt;horizontalBias&lt;/code&gt;; setting it to &lt;code&gt;1&lt;/code&gt; sets the message to the right, and &lt;code&gt;0&lt;/code&gt; sets the message to the left. We'll also set the text color based on the background since blue needs white text and grey needs black. Padding is also set differently for the two cases, since the message will have the "tail" on the right or left, depending on who's message it is.&lt;/p&gt;

&lt;p&gt;Since we're replicating iMessage's style, they don't render the tail unless it's the bottom message. Conveniently, Stream gives us position information and makes it easy to check message placement within a group of messages:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;If the message &lt;em&gt;is&lt;/em&gt; the bottom message, it means it's the only message or the last in a group of messages by the same user. If it's either of those, we add a tail; if it's not, we just do a simple bubble. We'll also set the background of the text view to a "drawable". Let's look at "our" message drawables; here's the drawable with a tail:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We're doing a bit of a smart trick by defining &lt;code&gt;layer-list&lt;/code&gt; with two items. One is a regular rectangle with rounding, the other is a rotated rectangle that is covered by the standard rectangle. We pad the regular rectangle out to give a small gutter, so the edge of the rotated rectangle sticks out. This gives us a beautiful "tail" for the message bubble. &lt;/p&gt;

&lt;p&gt;While we chose to keep it simple here with just rectangles, if you'd like a more stylized tail, such as the one you see in iMessage, you can use &lt;a href="https://developer.android.com/guide/topics/graphics/vector-drawable-resources"&gt;vector drawables&lt;/a&gt; to convert an SVG to a drawable. &lt;/p&gt;

&lt;p&gt;For messages that don't need a tail, we define a simple shape with some spacing for the tail gutter:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Messages for "theirs" is the same pattern but mirrored and grey:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Nice! Now we have some good looking bubbles:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l_ihACSh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/CVQXqfo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l_ihACSh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/CVQXqfo.png" alt="" width="800" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can add the username, if necessary, and space the messages correctly. First, we'll look at &lt;code&gt;configUsername&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;First, we decide if we show the username at all. There are three cases where we want it gone. If it's a 1-on-1 chat, if the message is "ours", or if it's not the top of a group of messages (otherwise, we'd have usernames on every message within a grouping). This logic means we only label groups of messages from other people in group chats. &lt;/p&gt;

&lt;p&gt;If we have that case, we set the text of the username &lt;code&gt;TextView&lt;/code&gt; to the user's name. We set the bias and margin depending on if it's on the right or the left.&lt;/p&gt;

&lt;p&gt;Now we can space our messages depending on their placement in a group of messages:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is simple since our default spacing is in our layout resource. We simply shrink the height if we're not the top message. Now you'll see the nice spacing and appropriate tails with usernames in our group chats:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oWXauY3r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/Olclwy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oWXauY3r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/Olclwy8.png" alt="" width="800" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All that's left is to deal with our date separator messages, and we're done! These &lt;code&gt;MessageListItem&lt;/code&gt;s come with no user and just a date. These are used to provide a horizontal separator between messages that are spaced out in time. These messages are automatically created by Stream when it decides the time between messages has been too long, and it'd be nice to group them. For example, if you message back a day later, you can break up the view with a date marker to inform the user of roughly when messages have come in. Here's &lt;code&gt;configDate&lt;/code&gt;:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is straightforward since our default text view has no styling and the correct spacing. We just need to turn off the username view and add the date. Now we have a convenient date separator:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Adb_qc9c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/XjsqpOl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Adb_qc9c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/XjsqpOl.png" alt="" width="800" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we're done! We now have custom bubble messages hooked into Stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Replicating iOS style messages on Android is relatively simple with drawables. Stream makes it even simpler with it's out of the box UI Components. You can start with Stream default views and customize them or build your own. If you want full control, you can ignore the UI Components and use the lower level Stream libraries directly. It's easy to peel back the layers when you need it!&lt;/p&gt;

&lt;p&gt;For in-depth details on how to use the Stream Chat SDK for Android, check out the interactive tutorial &lt;a href="https://getstream.io/tutorials/android-chat/"&gt;here&lt;/a&gt;. You'll learn how to build everything that is required for real-time chat – typing indicators, reactions, threads, etc.&lt;/p&gt;

&lt;p&gt;Happy coding! 💬&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--XjJRt2KQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pbs.twimg.com/profile_images/1025439518843785217/G9XgQkZK_normal.jpg" alt="Nick Parsons 🚀 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Nick Parsons 🚀
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/nickparsons"&gt;@nickparsons&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kDgU_xDI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Android Chat Bubbles: Building iOS Style Chat in Android via The Stream Blog (&lt;a href="https://twitter.com/getstream_io"&gt;@getstream_io&lt;/a&gt;) &lt;a href="https://t.co/iEgpYXjvFR"&gt;getstream.io/blog/android-b…&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      18:12 PM - 10 Mar 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1237441336334139392" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OXOJJiQT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1237441336334139392" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--foTp-unf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1237441336334139392" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SFHqU4bF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


</description>
      <category>android</category>
      <category>chat</category>
      <category>bubbles</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
