<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: t sriya</title>
    <description>The latest articles on DEV Community by t sriya (@t_sriya_2af6abc7e8d4e87da).</description>
    <link>https://dev.to/t_sriya_2af6abc7e8d4e87da</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2655881%2F8aac6cc0-b05d-4dcc-905c-4af27ed039d7.jpg</url>
      <title>DEV Community: t sriya</title>
      <link>https://dev.to/t_sriya_2af6abc7e8d4e87da</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/t_sriya_2af6abc7e8d4e87da"/>
    <language>en</language>
    <item>
      <title>Part 5 : Building an Authentication System from Scratch</title>
      <dc:creator>t sriya</dc:creator>
      <pubDate>Thu, 02 Jul 2026 17:14:31 +0000</pubDate>
      <link>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-5-building-an-authentication-system-from-scratch-5h7</link>
      <guid>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-5-building-an-authentication-system-from-scratch-5h7</guid>
      <description>&lt;h1&gt;
  
  
  Logout, Deployment &amp;amp; Lessons Learned
&lt;/h1&gt;

&lt;p&gt;Over the previous articles, we built a complete authentication system from scratch.&lt;/p&gt;

&lt;p&gt;We implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend architecture using a layered design.&lt;/li&gt;
&lt;li&gt;User registration with secure password hashing using &lt;strong&gt;bcrypt&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Login using &lt;strong&gt;JWT Access Tokens&lt;/strong&gt; and &lt;strong&gt;Refresh Tokens&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Protected routes and authentication middleware.&lt;/li&gt;
&lt;li&gt;Forgot Password and Reset Password workflows using &lt;strong&gt;Resend&lt;/strong&gt; for email delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this stage, the authentication system is fully functional. However, one important piece is still missing—&lt;strong&gt;logging the user out securely&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this final part, we'll implement the logout flow, deploy the application to production, discuss the challenges encountered during deployment, and summarize the key learnings from the entire project.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Logout Matters
&lt;/h1&gt;

&lt;p&gt;Logging out isn't just about redirecting the user back to the login page.&lt;/p&gt;

&lt;p&gt;In applications that use JWT authentication, the client may still possess valid authentication tokens. If those tokens remain active after logout, they could potentially be reused until they expire.&lt;/p&gt;

&lt;p&gt;A secure logout process ensures that the user's session is completely terminated and prevents the generation of new access tokens.&lt;/p&gt;




&lt;h1&gt;
  
  
  Logout Flow
&lt;/h1&gt;

&lt;p&gt;The logout process is shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User clicks Logout
        │
        ▼
Frontend sends Logout Request
        │
        ▼
Backend authenticates User
        │
        ▼
Invalidate Refresh Token
        │
        ▼
Return Success Response
        │
        ▼
Frontend clears Authentication State
        │
        ▼
Redirect to Login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike login, logout does not require the user to provide credentials. Instead, the request is authenticated using the existing access token.&lt;/p&gt;




&lt;h1&gt;
  
  
  Backend Logout Implementation
&lt;/h1&gt;

&lt;p&gt;The logout request first passes through the authentication middleware, which verifies the access token and identifies the currently logged-in user.&lt;/p&gt;

&lt;p&gt;Once the user is authenticated, the backend removes the stored refresh token from the database.&lt;/p&gt;

&lt;p&gt;By invalidating the refresh token, the application prevents future access token generation, even if the old access token has not yet expired.&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;logout&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;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="o"&gt;=&amp;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;userId&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;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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;authService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logout&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="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="nf"&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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="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="nf"&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="nf"&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;false&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Repository
&lt;/h1&gt;

&lt;p&gt;The repository performs the database operation required for logout.&lt;/p&gt;

&lt;p&gt;Its responsibility is simply to clear the refresh token stored against the authenticated user.&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;updatePassword&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;password_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`UPDATE users SET reset_token= $1, reset_token_expiry =$2, password_hash =$3 where id= $4`&lt;/span&gt;&lt;span class="p"&gt;,&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="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password_hash&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="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;Keeping this logic inside the repository maintains the same clean architecture used throughout the project.&lt;/p&gt;




&lt;h1&gt;
  
  
  Frontend Logout
&lt;/h1&gt;

&lt;p&gt;After receiving a successful response from the backend, the frontend clears all authentication-related data.&lt;/p&gt;

&lt;p&gt;This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access Token&lt;/li&gt;
&lt;li&gt;Refresh Token&lt;/li&gt;
&lt;li&gt;User Information&lt;/li&gt;
&lt;li&gt;Authentication State in Redux&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the application state is cleared, the user is redirected to the login page.&lt;/p&gt;

&lt;p&gt;This ensures that protected routes are no longer accessible.&lt;/p&gt;

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

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




&lt;h1&gt;
  
  
  Deploying the Application
&lt;/h1&gt;

&lt;p&gt;With all authentication features complete, the next step was deploying the application.&lt;/p&gt;

&lt;p&gt;I chose the following platforms:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This setup closely resembles how many real-world applications are deployed.&lt;/p&gt;




&lt;h1&gt;
  
  
  Backend Deployment (Render)
&lt;/h1&gt;

&lt;p&gt;The backend was deployed to &lt;strong&gt;Render&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Deployment required configuring several environment variables, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database credentials&lt;/li&gt;
&lt;li&gt;JWT secrets&lt;/li&gt;
&lt;li&gt;Resend API key&lt;/li&gt;
&lt;li&gt;Frontend URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One important lesson I learned is that &lt;strong&gt;environment variables are never deployed automatically from the local &lt;code&gt;.env&lt;/code&gt; file&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They must be manually added through the Render dashboard.&lt;/p&gt;




&lt;h1&gt;
  
  
  Frontend Deployment (Vercel)
&lt;/h1&gt;

&lt;p&gt;The React application was deployed using &lt;strong&gt;Vercel&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Since this is a Single Page Application (SPA), client-side routing required additional configuration.&lt;/p&gt;

&lt;p&gt;Without proper routing configuration, directly visiting URLs such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/auth/reset-password/:token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;resulted in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;404 NOT FOUND
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configuring Vercel to redirect all requests to &lt;code&gt;index.html&lt;/code&gt; resolved the issue and allowed React Router to handle client-side navigation correctly.&lt;/p&gt;




&lt;h1&gt;
  
  
  Challenges I Faced
&lt;/h1&gt;

&lt;p&gt;Building the authentication system was only part of the journey. Deploying it introduced several real-world challenges.&lt;/p&gt;

&lt;p&gt;Some of the issues I encountered included:&lt;/p&gt;

&lt;h3&gt;
  
  
  SMTP Connection Errors
&lt;/h3&gt;

&lt;p&gt;Initially, I used &lt;strong&gt;Nodemailer&lt;/strong&gt; with Gmail SMTP.&lt;/p&gt;

&lt;p&gt;While it worked perfectly in the local environment, the deployed backend on Render consistently failed with IPv6 connection errors.&lt;/p&gt;

&lt;p&gt;After investigating the issue, I migrated to &lt;strong&gt;Resend&lt;/strong&gt;, which provided a much simpler and more reliable solution for transactional emails.&lt;/p&gt;




&lt;h3&gt;
  
  
  Missing Environment Variables
&lt;/h3&gt;

&lt;p&gt;During deployment, the backend repeatedly failed because required environment variables had not been configured on Render.&lt;/p&gt;

&lt;p&gt;Although the application worked locally, production deployments require all secrets to be added manually through the hosting platform.&lt;/p&gt;

&lt;p&gt;This reinforced the importance of understanding environment management.&lt;/p&gt;




&lt;h3&gt;
  
  
  Frontend Routing Issues
&lt;/h3&gt;

&lt;p&gt;Password reset links worked locally but returned 404 errors after deployment.&lt;/p&gt;

&lt;p&gt;The issue turned out to be related to Vercel's handling of React Router.&lt;/p&gt;

&lt;p&gt;Configuring proper SPA rewrites fixed the problem.&lt;/p&gt;




&lt;h3&gt;
  
  
  CORS Configuration
&lt;/h3&gt;

&lt;p&gt;Since the frontend and backend were deployed on different domains, proper CORS configuration became essential.&lt;/p&gt;

&lt;p&gt;Restricting requests to only trusted frontend origins ensured secure communication between the client and server.&lt;/p&gt;




&lt;h1&gt;
  
  
  Key Learnings
&lt;/h1&gt;

&lt;p&gt;Building this authentication system taught me much more than implementing login and registration.&lt;/p&gt;

&lt;p&gt;Some of the biggest takeaways include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designing a scalable backend using layered architecture.&lt;/li&gt;
&lt;li&gt;Understanding how password hashing works internally.&lt;/li&gt;
&lt;li&gt;Implementing stateless authentication using JWT.&lt;/li&gt;
&lt;li&gt;Managing Access Tokens and Refresh Tokens securely.&lt;/li&gt;
&lt;li&gt;Building a complete password recovery workflow.&lt;/li&gt;
&lt;li&gt;Integrating third-party services like Resend.&lt;/li&gt;
&lt;li&gt;Debugging production deployment issues.&lt;/li&gt;
&lt;li&gt;Managing environment variables across different platforms.&lt;/li&gt;
&lt;li&gt;Deploying a full-stack application to production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More importantly, I learned that building software isn't just about writing code—it's equally about understanding architecture, security, debugging, deployment, and real-world problem solving.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Authentication is one of the foundational building blocks of modern web applications.&lt;/p&gt;

&lt;p&gt;While libraries and third-party authentication providers make implementation easier, building the entire authentication flow from scratch provides a much deeper understanding of how these systems work internally.&lt;/p&gt;

&lt;p&gt;This project helped me understand not only authentication but also backend architecture, API design, database interactions, deployment strategies, and security best practices.&lt;/p&gt;

&lt;p&gt;Although there are many possible enhancements—such as email verification, role-based access control, OAuth login, Redis token blacklisting, and multi-factor authentication—the current implementation already provides a strong, production-ready authentication foundation.&lt;/p&gt;

&lt;p&gt;I hope this series helps anyone looking to understand authentication beyond simply copying code from tutorials.&lt;/p&gt;

&lt;p&gt;Live App: &lt;a href="https://auth-flow-five-iota.vercel.app/auth/" rel="noopener noreferrer"&gt;https://auth-flow-five-iota.vercel.app/auth/&lt;/a&gt;&lt;br&gt;
Backend API: &lt;a href="https://auth-flow-backend-1v2h.onrender.com/" rel="noopener noreferrer"&gt;https://auth-flow-backend-1v2h.onrender.com/&lt;/a&gt;&lt;br&gt;
github url: &lt;a href="https://github.com/sriyaT/Auth-Flow" rel="noopener noreferrer"&gt;https://github.com/sriyaT/Auth-Flow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect : LinkedIn : &lt;a href="https://www.linkedin.com/in/t-sriya-b4234510a/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/t-sriya-b4234510a/&lt;/a&gt;, github : &lt;a href="https://github.com/sriyaT" rel="noopener noreferrer"&gt;https://github.com/sriyaT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy Coding! 🚀&lt;/p&gt;

&lt;p&gt;Author: Sriya T.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Part 4 : Building an Authentication System from Scratch</title>
      <dc:creator>t sriya</dc:creator>
      <pubDate>Thu, 02 Jul 2026 16:50:08 +0000</pubDate>
      <link>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-4-building-an-authentication-system-from-scratch-backend-setup-k2c</link>
      <guid>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-4-building-an-authentication-system-from-scratch-backend-setup-k2c</guid>
      <description>&lt;h1&gt;
  
  
  Implementing Forgot Password &amp;amp; Reset Password with Resend
&lt;/h1&gt;

&lt;p&gt;In the previous article, we implemented secure user authentication using &lt;strong&gt;JWT Access Tokens&lt;/strong&gt; and &lt;strong&gt;Refresh Tokens&lt;/strong&gt;. Users can now register, log in, and access protected resources.&lt;/p&gt;

&lt;p&gt;However, authentication systems must also handle a common real-world scenario—&lt;strong&gt;users forgetting their passwords&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of creating a new account, users should be able to securely reset their existing password.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a complete password recovery workflow using &lt;strong&gt;Resend&lt;/strong&gt;, secure reset tokens, PostgreSQL, and React.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why a Password Reset Flow?
&lt;/h1&gt;

&lt;p&gt;Passwords are intentionally stored as one-way hashes using bcrypt, which means they cannot be recovered—not even by the application itself.&lt;/p&gt;

&lt;p&gt;Because of this, the only secure solution is to let users create a &lt;strong&gt;new password&lt;/strong&gt; after verifying their identity.&lt;/p&gt;

&lt;p&gt;The password reset process should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify that the email belongs to a registered user.&lt;/li&gt;
&lt;li&gt;Generate a secure, unique reset token.&lt;/li&gt;
&lt;li&gt;Expire the token after a limited time.&lt;/li&gt;
&lt;li&gt;Deliver the reset link through email.&lt;/li&gt;
&lt;li&gt;Allow password updates only after validating the token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach keeps user accounts secure while providing a smooth recovery experience.&lt;/p&gt;




&lt;h1&gt;
  
  
  Forgot Password Flow
&lt;/h1&gt;

&lt;p&gt;The complete workflow is shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User clicks "Forgot Password"
          │
          ▼
Enter Registered Email
          │
          ▼
Backend verifies email
          │
          ▼
Generate Secure Reset Token
          │
          ▼
Store Token &amp;amp; Expiry in Database
          │
          ▼
Send Reset Link via Resend
          │
          ▼
User receives Email
          │
          ▼
Clicks Reset Link
          │
          ▼
Reset Password Page
          │
          ▼
Submit New Password
          │
          ▼
Validate Token
          │
          ▼
Hash New Password
          │
          ▼
Update Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Why I Chose Resend
&lt;/h1&gt;

&lt;p&gt;To deliver password reset emails, I chose &lt;strong&gt;Resend&lt;/strong&gt; instead of configuring a traditional SMTP server.&lt;/p&gt;

&lt;p&gt;Although SMTP works perfectly well, it often requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SMTP host configuration&lt;/li&gt;
&lt;li&gt;Port configuration&lt;/li&gt;
&lt;li&gt;App passwords&lt;/li&gt;
&lt;li&gt;TLS configuration&lt;/li&gt;
&lt;li&gt;Provider-specific settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resend provides a much simpler developer experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages of Resend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Simple REST API&lt;/li&gt;
&lt;li&gt;Excellent Node.js SDK&lt;/li&gt;
&lt;li&gt;Reliable email delivery&lt;/li&gt;
&lt;li&gt;Clean documentation&lt;/li&gt;
&lt;li&gt;Easy integration&lt;/li&gt;
&lt;li&gt;No SMTP configuration required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allowed me to focus on the authentication workflow instead of managing email server configuration.&lt;/p&gt;




&lt;h1&gt;
  
  
  Generating a Secure Reset Token
&lt;/h1&gt;

&lt;p&gt;Once the backend confirms that the email exists, it generates a unique reset token.&lt;/p&gt;

&lt;p&gt;I used:&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;resetToken&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="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a cryptographically secure UUID that is extremely difficult to guess.&lt;/p&gt;

&lt;p&gt;The token is then assigned an expiration time.&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;resetTokenExpiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;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;30&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this project, the reset link remains valid for &lt;strong&gt;30 minutes&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Storing the Reset Token
&lt;/h1&gt;

&lt;p&gt;Instead of sending the token directly without tracking it, the application stores both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reset_token&lt;/li&gt;
&lt;li&gt;reset_token_expiry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;inside PostgreSQL.&lt;br&gt;
&lt;/p&gt;

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

Email

Password Hash

Reset Token

Reset Token Expiry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These fields are later used to verify whether the reset request is still valid.&lt;/p&gt;




&lt;h1&gt;
  
  
  Repository Layer
&lt;/h1&gt;

&lt;p&gt;The repository is responsible only for updating the database.&lt;/p&gt;

&lt;p&gt;It stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reset token&lt;/li&gt;
&lt;li&gt;expiration timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;for the corresponding user.&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;saveRefreshToken&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;refresh_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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`UPDATE users SET refresh_token= $1 where id = $2`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;refresh_token&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;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because database logic is isolated inside the repository, the service layer remains focused only on business logic.&lt;/p&gt;




&lt;h1&gt;
  
  
  Service Layer
&lt;/h1&gt;

&lt;p&gt;The service performs the complete Forgot Password workflow.&lt;/p&gt;

&lt;p&gt;It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding the user by email.&lt;/li&gt;
&lt;li&gt;Generating a secure token.&lt;/li&gt;
&lt;li&gt;Setting an expiration time.&lt;/li&gt;
&lt;li&gt;Saving both values in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Constructing the password reset URL.&lt;/li&gt;
&lt;li&gt;Sending the email using Resend.
&lt;/li&gt;
&lt;/ul&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;forgotPassword&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;userEmail&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="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="nx"&gt;userRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userEmail&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;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="s2"&gt;user not 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resetTokenExpiry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;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;30&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reset_token&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="nf"&gt;randomUUID&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;userRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reset_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resetTokenExpiry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userEmail&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;resetUrl&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;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;FRONTEND_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/reset-password/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reset_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;emailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendEmail&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="s2"&gt;onboarding@resend.dev&lt;/span&gt;&lt;span class="dl"&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;userEmail&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;Reset Your Password&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 reset your password: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resetUrl&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="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="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;Password reset token generated successfully&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;Keeping this logic inside the service makes the controller extremely lightweight.&lt;/p&gt;




&lt;h1&gt;
  
  
  Email Service
&lt;/h1&gt;

&lt;p&gt;Rather than sending emails directly from the authentication service, I created a dedicated Email Service.&lt;/p&gt;

&lt;p&gt;This follows the &lt;strong&gt;Single Responsibility Principle&lt;/strong&gt;, making the authentication service responsible only for authentication.&lt;/p&gt;

&lt;p&gt;The Email Service handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initializing the Resend client.&lt;/li&gt;
&lt;li&gt;Preparing the email.&lt;/li&gt;
&lt;li&gt;Sending the email.&lt;/li&gt;
&lt;li&gt;Handling delivery errors.
&lt;/li&gt;
&lt;/ul&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Resend&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resend&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;resend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Resend&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;RESEND_API_KEY&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;sendEmail&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="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&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="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;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
        &amp;lt;div style="font-family:sans-serif"&amp;gt;
          &amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Resend response:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;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="nf"&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;Resend Error:&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="nf"&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="k"&gt;throw&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="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendEmail&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the Email Service is reusable, future features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email Verification&lt;/li&gt;
&lt;li&gt;Welcome Emails&lt;/li&gt;
&lt;li&gt;Account Notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can all use the same implementation.&lt;/p&gt;




&lt;h1&gt;
  
  
  Sending the Reset Link
&lt;/h1&gt;

&lt;p&gt;After generating the reset token, the backend creates a URL pointing to the frontend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-app.com/auth/reset-password/&amp;lt;token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user receives an email similar to:&lt;br&gt;
&lt;/p&gt;

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

Click the link below to reset your password.

https://your-app.com/auth/reset-password/&amp;lt;token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;h1&gt;
  
  
  Reset Password Flow
&lt;/h1&gt;

&lt;p&gt;Clicking the email redirects the user to the Reset Password page.&lt;/p&gt;

&lt;p&gt;The frontend extracts the token from the URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/auth/reset-password/:token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user enters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New Password&lt;/li&gt;
&lt;li&gt;Confirm Password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend then submits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /reset-password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;along with the token.&lt;/p&gt;




&lt;h1&gt;
  
  
  Backend Validation
&lt;/h1&gt;

&lt;p&gt;Before updating the password, the backend verifies:&lt;/p&gt;

&lt;p&gt;✅ Does the token exist?&lt;/p&gt;

&lt;p&gt;✅ Has the token expired?&lt;/p&gt;

&lt;p&gt;If either validation fails, the request is rejected.&lt;/p&gt;

&lt;p&gt;This prevents attackers from reusing old reset links.&lt;/p&gt;




&lt;h1&gt;
  
  
  Updating the Password
&lt;/h1&gt;

&lt;p&gt;Once the token is verified:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hash the new password using bcrypt.&lt;/li&gt;
&lt;li&gt;Update the password hash.&lt;/li&gt;
&lt;li&gt;Remove the reset token.&lt;/li&gt;
&lt;li&gt;Remove the expiration timestamp.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The token is deleted immediately after a successful password reset.&lt;/p&gt;

&lt;p&gt;This guarantees that every reset link can be used only once.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security Measures
&lt;/h1&gt;

&lt;p&gt;Several security practices are implemented throughout the password reset workflow.&lt;/p&gt;

&lt;p&gt;✅ Secure UUID token generation&lt;/p&gt;

&lt;p&gt;✅ Token expiration (30 minutes)&lt;/p&gt;

&lt;p&gt;✅ Password hashing using bcrypt&lt;/p&gt;

&lt;p&gt;✅ Single-use reset links&lt;/p&gt;

&lt;p&gt;✅ Email verification before reset&lt;/p&gt;

&lt;p&gt;✅ Invalid token rejection&lt;/p&gt;

&lt;p&gt;These practices significantly reduce the risk of unauthorized password changes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Testing the Forgot Password Flow
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Request
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /forgot-password
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"userEmail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sriya@gmail.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Password reset token generated successfully"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user receives a reset email.&lt;/p&gt;

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




&lt;h1&gt;
  
  
  Testing the Reset Password Flow
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Request
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /reset-password
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;reset-token&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"NewPassword123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Password updated successfully"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;h1&gt;
  
  
  Challenges Faced During Development
&lt;/h1&gt;

&lt;p&gt;Implementing this feature wasn't completely straightforward.&lt;/p&gt;

&lt;p&gt;Some of the challenges I encountered included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SMTP IPv6 connection failures on Render.&lt;/li&gt;
&lt;li&gt;Migrating from Nodemailer to Resend.&lt;/li&gt;
&lt;li&gt;Missing environment variables during deployment.&lt;/li&gt;
&lt;li&gt;Reset links returning Vercel 404 errors.&lt;/li&gt;
&lt;li&gt;Debugging frontend routing after deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resolving these issues helped me gain a much deeper understanding of cloud deployment, environment management, and production debugging.&lt;/p&gt;




&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In this article we implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forgot Password API&lt;/li&gt;
&lt;li&gt;Secure reset token generation&lt;/li&gt;
&lt;li&gt;Token expiration&lt;/li&gt;
&lt;li&gt;Database token storage&lt;/li&gt;
&lt;li&gt;Email delivery using Resend&lt;/li&gt;
&lt;li&gt;Reset Password API&lt;/li&gt;
&lt;li&gt;Password update with bcrypt&lt;/li&gt;
&lt;li&gt;Single-use reset links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these features create a secure and user-friendly password recovery workflow commonly used in production applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;The authentication system is now functionally complete.&lt;/p&gt;

&lt;p&gt;In the next article, we'll implement &lt;strong&gt;Logout&lt;/strong&gt;, understand why Refresh Tokens should be invalidated, and finally deploy the complete application to &lt;strong&gt;Render&lt;/strong&gt; and &lt;strong&gt;Vercel&lt;/strong&gt;, along with the production issues I encountered and how I resolved them.&lt;/p&gt;

&lt;p&gt;Continue Reading Part 5 - &lt;a href="https://dev.to/t_sriya_2af6abc7e8d4e87da/part-5-building-an-authentication-system-from-scratch-5h7"&gt;https://dev.to/t_sriya_2af6abc7e8d4e87da/part-5-building-an-authentication-system-from-scratch-5h7&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live App: &lt;a href="https://auth-flow-five-iota.vercel.app/auth/" rel="noopener noreferrer"&gt;https://auth-flow-five-iota.vercel.app/auth/&lt;/a&gt;&lt;br&gt;
Backend API: &lt;a href="https://auth-flow-backend-1v2h.onrender.com/" rel="noopener noreferrer"&gt;https://auth-flow-backend-1v2h.onrender.com/&lt;/a&gt;&lt;br&gt;
github url: &lt;a href="https://github.com/sriyaT/Auth-Flow" rel="noopener noreferrer"&gt;https://github.com/sriyaT/Auth-Flow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect : LinkedIn : &lt;a href="https://www.linkedin.com/in/t-sriya-b4234510a/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/t-sriya-b4234510a/&lt;/a&gt;, github : &lt;a href="https://github.com/sriyaT" rel="noopener noreferrer"&gt;https://github.com/sriyaT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Author: Sriya T.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Part 3 : Building an Authentication System from Scratch</title>
      <dc:creator>t sriya</dc:creator>
      <pubDate>Thu, 02 Jul 2026 16:35:42 +0000</pubDate>
      <link>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-3-building-an-authentication-system-from-scratch-backend-setup-4119</link>
      <guid>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-3-building-an-authentication-system-from-scratch-backend-setup-4119</guid>
      <description>&lt;h2&gt;
  
  
  Secure User Authentication with JWT, Access Tokens &amp;amp; Refresh Tokens
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;When a user registers, the application securely stores their credentials in the database. However, storing user information alone is not enough. The application also needs a secure mechanism to recognize authenticated users every time they interact with protected resources.&lt;/p&gt;

&lt;p&gt;A login system should answer one simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How does the server know that this request is coming from an authenticated user?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The traditional solution was server-side sessions, where the server stored session data in memory or a database. Modern applications, however, often rely on JSON Web Tokens (JWT) because they are lightweight, stateless, and highly scalable.&lt;/p&gt;

&lt;p&gt;In this article, we'll build the complete login workflow using JWT, understand why Access Tokens and Refresh Tokens are required, and see how protected APIs verify user identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Login Flow
&lt;/h3&gt;

&lt;p&gt;The login request follows the same layered architecture used throughout the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   │
   ▼
Routes
   │
   ▼
Controller
   │
   ▼
Service
   │
   ▼
Repository
   │
   ▼
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow is:&lt;/p&gt;

&lt;p&gt;User submits email and password.&lt;br&gt;
Controller receives the request.&lt;br&gt;
Service validates the credentials.&lt;br&gt;
Repository retrieves the user from PostgreSQL.&lt;br&gt;
bcrypt verifies the password.&lt;br&gt;
JWT generates an Access Token.&lt;br&gt;
JWT generates a Refresh Token.&lt;br&gt;
Refresh Token is stored in PostgreSQL.&lt;br&gt;
Both tokens are returned to the client.&lt;/p&gt;
&lt;h3&gt;
  
  
  Controller
&lt;/h3&gt;

&lt;p&gt;The controller is responsible only for handling the incoming HTTP request.&lt;/p&gt;

&lt;p&gt;Its responsibilities are:&lt;/p&gt;

&lt;p&gt;Extract email and password.&lt;br&gt;
Call the authentication service.&lt;br&gt;
Return the generated tokens to the client.&lt;br&gt;
Handle errors gracefully.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn96cqple45yx1tujiq1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn96cqple45yx1tujiq1y.png" alt=" " width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Service
&lt;/h3&gt;

&lt;p&gt;The authentication service contains the complete login workflow.&lt;/p&gt;

&lt;p&gt;It performs the following steps:&lt;/p&gt;

&lt;p&gt;Find the user using the email address.&lt;br&gt;
Verify the password using bcrypt.&lt;br&gt;
Generate the JWT Access Token.&lt;br&gt;
Generate the Refresh Token.&lt;br&gt;
Save the Refresh Token in PostgreSQL.&lt;br&gt;
Return user information along with both tokens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fstgty3wi4zvzrb8mim79.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fstgty3wi4zvzrb8mim79.png" alt=" " width="800" height="799"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Repository
&lt;/h3&gt;

&lt;p&gt;The repository interacts directly with PostgreSQL.&lt;/p&gt;

&lt;p&gt;For login, it performs operations such as:&lt;/p&gt;

&lt;p&gt;Finding the user by email.&lt;br&gt;
Updating the Refresh Token after successful authentication.&lt;/p&gt;

&lt;p&gt;Keeping database logic isolated from business logic makes the application easier to maintain and test.&lt;/p&gt;
&lt;h3&gt;
  
  
  Verifying the Password
&lt;/h3&gt;

&lt;p&gt;Although the password entered during login is plain text, the password stored in the database is already hashed.&lt;/p&gt;

&lt;p&gt;Instead of converting the stored hash back into the original password (which is impossible), bcrypt hashes the entered password again using the salt embedded inside the stored hash.&lt;/p&gt;

&lt;p&gt;const isMatch = await bcrypt.compare(&lt;br&gt;
    enteredPassword,&lt;br&gt;
    storedHash&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;Internally, bcrypt performs:&lt;br&gt;
&lt;/p&gt;

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

↓

Extract Salt from Stored Hash

↓

Hash Entered Password

↓

Compare Hashes

↓

true / false

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

&lt;/div&gt;



&lt;p&gt;If both hashes match, authentication succeeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why JWT?
&lt;/h3&gt;

&lt;p&gt;Once the user's identity has been verified, the server needs a way to recognize that user during future requests.&lt;/p&gt;

&lt;p&gt;Instead of asking the user to submit their email and password for every API call, the server issues a JSON Web Token (JWT).&lt;/p&gt;

&lt;p&gt;The client stores this token and includes it in every protected request.&lt;/p&gt;

&lt;p&gt;Authorization: Bearer &lt;/p&gt;

&lt;p&gt;This allows the server to authenticate the user without maintaining session data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomy of a JWT
&lt;/h3&gt;

&lt;p&gt;A JWT consists of three sections.&lt;/p&gt;

&lt;p&gt;`Header&lt;/p&gt;

&lt;p&gt;.&lt;/p&gt;

&lt;p&gt;Payload&lt;/p&gt;

&lt;p&gt;.&lt;/p&gt;

&lt;p&gt;Signature`&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;xxxxx.yyyyy.zzzzz&lt;br&gt;
Header&lt;/p&gt;

&lt;p&gt;Contains metadata.&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "alg": "HS256",&lt;br&gt;
  "typ": "JWT"&lt;br&gt;
}&lt;br&gt;
Payload&lt;/p&gt;

&lt;p&gt;Contains user claims.&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "id": 1,&lt;br&gt;
  "email": "&lt;a href="mailto:john@example.com"&gt;john@example.com&lt;/a&gt;"&lt;br&gt;
}&lt;br&gt;
Signature&lt;/p&gt;

&lt;p&gt;Protects the token from tampering.&lt;/p&gt;

&lt;p&gt;If someone modifies the payload, the signature immediately becomes invalid.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Access Token?
&lt;/h3&gt;

&lt;p&gt;The Access Token is used to authenticate protected API requests.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;p&gt;GET /profile&lt;/p&gt;

&lt;p&gt;GET /dashboard&lt;/p&gt;

&lt;p&gt;POST /logout&lt;/p&gt;

&lt;p&gt;Instead of sending credentials repeatedly, the client simply sends:&lt;/p&gt;

&lt;p&gt;Authorization: Bearer &lt;/p&gt;

&lt;p&gt;Access Tokens are intentionally short-lived (typically 15 minutes to 1 hour).&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Refresh Token?
&lt;/h3&gt;

&lt;p&gt;If only Access Tokens existed, users would need to log in every time the token expired.&lt;/p&gt;

&lt;p&gt;To improve user experience, a Refresh Token is also generated.&lt;/p&gt;

&lt;p&gt;When the Access Token expires:&lt;br&gt;
&lt;/p&gt;

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

↓

Client sends Refresh Token

↓

Server verifies Refresh Token

↓

Generate New Access Token

↓

Continue Using Application

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

&lt;/div&gt;



&lt;p&gt;Refresh Tokens usually remain valid for several days.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication Flow
&lt;/h3&gt;



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

↓

Verify Email &amp;amp; Password

↓

Generate Access Token

↓

Generate Refresh Token

↓

Store Refresh Token

↓

Return Tokens

↓

Client Stores Tokens

↓

Access Protected APIs

↓

Access Token Expires

↓

Use Refresh Token

↓

Generate New Access Token

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  JWT Middleware
&lt;/h3&gt;

&lt;p&gt;Protected routes require authentication.&lt;/p&gt;

&lt;p&gt;Instead of validating the JWT inside every controller, the application uses middleware.&lt;/p&gt;

&lt;p&gt;The middleware:&lt;/p&gt;

&lt;p&gt;Reads the Authorization header.&lt;br&gt;
Extracts the JWT.&lt;br&gt;
Verifies its signature.&lt;br&gt;
Decodes the payload.&lt;br&gt;
Attaches the authenticated user to the request.&lt;br&gt;
Passes control to the controller.&lt;/p&gt;

&lt;p&gt;If verification fails, the request is rejected.&lt;br&gt;
&lt;/p&gt;

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

↓

JWT Middleware

↓

Verify Token

↓

Valid?

↓

Yes → Controller

No → 401 Unauthorized

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

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Testing Login
&lt;/h3&gt;

&lt;p&gt;Request:&lt;/p&gt;

&lt;p&gt;POST /api/auth/login&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sriya@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Password123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sriya"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sriya@gmail.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accessToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refreshToken"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;In this article we:&lt;/p&gt;

&lt;p&gt;Implemented secure user login.&lt;br&gt;
Verified passwords using bcrypt.&lt;br&gt;
Generated JWT Access Tokens.&lt;br&gt;
Generated Refresh Tokens.&lt;br&gt;
Stored Refresh Tokens in PostgreSQL.&lt;br&gt;
Protected APIs using JWT middleware.&lt;br&gt;
Built a stateless authentication system.&lt;br&gt;
What's Next?&lt;/p&gt;

&lt;p&gt;Now that users can authenticate successfully, the next step is handling one of the most common real-world scenarios:&lt;/p&gt;

&lt;p&gt;Forgot Password&lt;/p&gt;

&lt;p&gt;In the next article, we'll build a complete password recovery workflow using Resend, secure reset tokens, email delivery, and password reset validation.&lt;/p&gt;

&lt;p&gt;Continue Reading Part 4 -                                            &lt;a href="https://dev.to/t_sriya_2af6abc7e8d4e87da/part-4-building-an-authentication-system-from-scratch-backend-setup-k2c"&gt;https://dev.to/t_sriya_2af6abc7e8d4e87da/part-4-building-an-authentication-system-from-scratch-backend-setup-k2c&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live App: &lt;a href="https://auth-flow-five-iota.vercel.app/auth/" rel="noopener noreferrer"&gt;https://auth-flow-five-iota.vercel.app/auth/&lt;/a&gt;&lt;br&gt;
Backend API: &lt;a href="https://auth-flow-backend-1v2h.onrender.com/" rel="noopener noreferrer"&gt;https://auth-flow-backend-1v2h.onrender.com/&lt;/a&gt;&lt;br&gt;
github url: &lt;a href="https://github.com/sriyaT/Auth-Flow" rel="noopener noreferrer"&gt;https://github.com/sriyaT/Auth-Flow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect : LinkedIn : &lt;a href="https://www.linkedin.com/in/t-sriya-b4234510a/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/t-sriya-b4234510a/&lt;/a&gt;, github : &lt;a href="https://github.com/sriyaT" rel="noopener noreferrer"&gt;https://github.com/sriyaT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Author: Sriya T.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>security</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Part 2 : Building an Authentication System from Scratch</title>
      <dc:creator>t sriya</dc:creator>
      <pubDate>Thu, 02 Jul 2026 16:15:19 +0000</pubDate>
      <link>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-2building-an-authentication-system-from-scratch-backend-setup-59gg</link>
      <guid>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-2building-an-authentication-system-from-scratch-backend-setup-59gg</guid>
      <description>&lt;h2&gt;
  
  
  User Registration &amp;amp; Secure Password Hashing with bcrypt
&lt;/h2&gt;

&lt;p&gt;In the previous article, we built the backend foundation by setting up Express.js, PostgreSQL, environment variables, and a clean layered architecture.&lt;/p&gt;

&lt;p&gt;With the backend ready, it's time to implement the first authentication feature—&lt;strong&gt;User Registration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although registration appears straightforward, it involves much more than simply storing user details in a database. A secure registration system must validate user input, prevent duplicate accounts, protect passwords, and ensure that sensitive information is never exposed.&lt;/p&gt;

&lt;p&gt;In this article, we'll build the complete registration workflow while following security best practices.&lt;/p&gt;




&lt;h1&gt;
  
  
  Registration Flow
&lt;/h1&gt;

&lt;p&gt;The registration process follows a layered architecture, where each layer has a single responsibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   │
   ▼
Routes
   │
   ▼
Controller
   │
   ▼
Service
   │
   ▼
Repository
   │
   ▼
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overall workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client submits the registration form.&lt;/li&gt;
&lt;li&gt;The controller receives the request.&lt;/li&gt;
&lt;li&gt;The service validates the data.&lt;/li&gt;
&lt;li&gt;The repository checks whether the email already exists.&lt;/li&gt;
&lt;li&gt;The password is securely hashed using bcrypt.&lt;/li&gt;
&lt;li&gt;The user is stored in PostgreSQL.&lt;/li&gt;
&lt;li&gt;A success response is returned to the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Why a Layered Architecture?
&lt;/h1&gt;

&lt;p&gt;Instead of placing all the registration logic inside the controller, I divided the implementation into three layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controller
&lt;/h3&gt;

&lt;p&gt;Responsible only for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receiving the HTTP request&lt;/li&gt;
&lt;li&gt;Calling the service layer&lt;/li&gt;
&lt;li&gt;Returning the HTTP response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The controller should never contain business logic or database queries.&lt;/p&gt;




&lt;h3&gt;
  
  
  Service
&lt;/h3&gt;

&lt;p&gt;The service contains the application's business logic.&lt;/p&gt;

&lt;p&gt;For registration, it is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validating the request&lt;/li&gt;
&lt;li&gt;Checking whether the email already exists&lt;/li&gt;
&lt;li&gt;Hashing the password&lt;/li&gt;
&lt;li&gt;Calling the repository to save the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layer acts as the brain of the application.&lt;/p&gt;




&lt;h3&gt;
  
  
  Repository
&lt;/h3&gt;

&lt;p&gt;The repository communicates directly with PostgreSQL.&lt;/p&gt;

&lt;p&gt;Its responsibilities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checking if a user already exists&lt;/li&gt;
&lt;li&gt;Creating a new user&lt;/li&gt;
&lt;li&gt;Executing SQL queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping SQL isolated inside repositories makes the application easier to maintain and test.&lt;/p&gt;




&lt;h1&gt;
  
  
  Controller
&lt;/h1&gt;

&lt;p&gt;The controller receives the registration request and forwards the data to the service layer.&lt;/p&gt;

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

&lt;p&gt;The controller itself performs very little work.&lt;/p&gt;

&lt;p&gt;Its responsibility is simply to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract the request body&lt;/li&gt;
&lt;li&gt;Call the service&lt;/li&gt;
&lt;li&gt;Return either a success or an error response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps controllers lightweight and easy to understand.&lt;/p&gt;




&lt;h1&gt;
  
  
  Service
&lt;/h1&gt;

&lt;p&gt;The service contains the actual registration workflow.&lt;/p&gt;

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

&lt;p&gt;The registration service performs the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check whether the email already exists.&lt;/li&gt;
&lt;li&gt;Generate a secure password hash.&lt;/li&gt;
&lt;li&gt;Create the user in PostgreSQL.&lt;/li&gt;
&lt;li&gt;Return the newly created user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because all business rules live inside the service layer, future changes become much easier.&lt;/p&gt;

&lt;p&gt;For example, adding email verification later would require changes only inside the service, without affecting controllers or repositories.&lt;/p&gt;




&lt;h1&gt;
  
  
  Repository
&lt;/h1&gt;

&lt;p&gt;The repository is responsible only for database communication.&lt;/p&gt;

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

&lt;p&gt;Typical repository functions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;findByEmail()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createUser()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping SQL queries isolated improves readability and keeps the service layer database-agnostic.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Password Hashing is Necessary
&lt;/h1&gt;

&lt;p&gt;One of the biggest mistakes an application can make is storing passwords in plain text.&lt;/p&gt;

&lt;p&gt;Imagine a database leak.&lt;/p&gt;

&lt;p&gt;If passwords are stored as plain text, every user's credentials become immediately visible.&lt;/p&gt;

&lt;p&gt;Instead, passwords should always be transformed into a secure one-way hash before being stored.&lt;/p&gt;

&lt;p&gt;This is exactly why we use &lt;strong&gt;bcrypt&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why bcrypt?
&lt;/h1&gt;

&lt;p&gt;bcrypt is one of the most trusted password hashing libraries available for Node.js.&lt;/p&gt;

&lt;p&gt;Unlike encryption, hashing is a &lt;strong&gt;one-way operation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The original password cannot be recovered.&lt;/li&gt;
&lt;li&gt;Even the application itself cannot view the user's password.&lt;/li&gt;
&lt;li&gt;Only password verification is possible.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  How bcrypt Works
&lt;/h1&gt;

&lt;p&gt;When a user registers, bcrypt performs several operations internally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Password
    │
    ▼
Generate Random Salt
    │
    ▼
Password + Salt
    │
    ▼
Multiple Hashing Rounds
    │
    ▼
Store Hash in Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each password receives its own randomly generated salt before hashing.&lt;/p&gt;

&lt;p&gt;Because of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Two users with the same password will have completely different hashes.&lt;/li&gt;
&lt;li&gt;Rainbow table attacks become ineffective.&lt;/li&gt;
&lt;li&gt;Brute-force attacks become significantly slower due to bcrypt's configurable cost factor.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Password Verification During Login
&lt;/h1&gt;

&lt;p&gt;During login, the user enters their password as plain text.&lt;/p&gt;

&lt;p&gt;bcrypt then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads the stored hash.&lt;/li&gt;
&lt;li&gt;Extracts the embedded salt.&lt;/li&gt;
&lt;li&gt;Hashes the entered password using the same salt.&lt;/li&gt;
&lt;li&gt;Compares the generated hash with the stored hash.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If both hashes match, the user is successfully authenticated.&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;isMatch&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="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;enteredPassword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;storedHash&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of bcrypt's biggest advantages is that developers never need to manually manage salts or compare hashes—the library handles the entire verification process securely.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security Benefits of bcrypt
&lt;/h1&gt;

&lt;p&gt;Using bcrypt provides several important security advantages.&lt;/p&gt;

&lt;p&gt;✅ Passwords are never stored in plain text.&lt;/p&gt;

&lt;p&gt;✅ Every password uses a unique random salt.&lt;/p&gt;

&lt;p&gt;✅ Identical passwords generate different hashes.&lt;/p&gt;

&lt;p&gt;✅ Brute-force attacks become significantly slower.&lt;/p&gt;

&lt;p&gt;✅ Rainbow table attacks are mitigated.&lt;/p&gt;

&lt;p&gt;These features make bcrypt one of the industry standards for password protection.&lt;/p&gt;




&lt;h1&gt;
  
  
  Testing the Registration Flow
&lt;/h1&gt;

&lt;p&gt;Once the backend implementation was complete, I verified the registration API using Postman.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /api/auth/register
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sriya"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sriya@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Password123"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sriya"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sriya@gmail.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the response never includes the password or its hash.&lt;/p&gt;

&lt;p&gt;Only non-sensitive user information is returned to the client.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's Next?
&lt;/h1&gt;

&lt;p&gt;Now that users can securely register and their passwords are safely stored, the next step is allowing them to authenticate.&lt;/p&gt;

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

&lt;p&gt;In the next article, we'll build the &lt;strong&gt;Login Flow&lt;/strong&gt;, where we'll:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify user credentials&lt;/li&gt;
&lt;li&gt;Compare passwords using bcrypt&lt;/li&gt;
&lt;li&gt;Generate JWT Access Tokens&lt;/li&gt;
&lt;li&gt;Generate Refresh Tokens&lt;/li&gt;
&lt;li&gt;Understand how JWT authentication works internally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Continue Reading Part 3- &lt;a href="https://dev.to/t_sriya_2af6abc7e8d4e87da/part-3-building-an-authentication-system-from-scratch-backend-setup-4119"&gt;https://dev.to/t_sriya_2af6abc7e8d4e87da/part-3-building-an-authentication-system-from-scratch-backend-setup-4119&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live App: &lt;a href="https://auth-flow-five-iota.vercel.app/auth/" rel="noopener noreferrer"&gt;https://auth-flow-five-iota.vercel.app/auth/&lt;/a&gt;&lt;br&gt;
Backend API: &lt;a href="https://auth-flow-backend-1v2h.onrender.com/" rel="noopener noreferrer"&gt;https://auth-flow-backend-1v2h.onrender.com/&lt;/a&gt;&lt;br&gt;
github url: &lt;a href="https://github.com/sriyaT/Auth-Flow" rel="noopener noreferrer"&gt;https://github.com/sriyaT/Auth-Flow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect : LinkedIn : &lt;a href="https://www.linkedin.com/in/t-sriya-b4234510a/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/t-sriya-b4234510a/&lt;/a&gt;, github : &lt;a href="https://github.com/sriyaT" rel="noopener noreferrer"&gt;https://github.com/sriyaT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Author: Sriya T.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>node</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Part 1 : Building an Authentication System from Scratch – Backend Setup</title>
      <dc:creator>t sriya</dc:creator>
      <pubDate>Thu, 02 Jul 2026 14:39:37 +0000</pubDate>
      <link>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-1building-an-authentication-system-from-scratch-backend-setup-4f4l</link>
      <guid>https://dev.to/t_sriya_2af6abc7e8d4e87da/part-1building-an-authentication-system-from-scratch-backend-setup-4f4l</guid>
      <description>&lt;p&gt;&lt;strong&gt;Setting up a scalable authentication backend using Node.js, Express.js, PostgreSQL, and a layered architecture.&lt;br&gt;&lt;br&gt;
Introduction&lt;/strong&gt;__&lt;/p&gt;

&lt;p&gt;Authentication is one of the most essential features of any modern web application. While many projects rely on third-party providers such as Firebase Auth, Auth0, or Clerk, I wanted to understand what actually happens behind the scenes when a user registers, logs in, resets their password, or logs out.&lt;/p&gt;

&lt;p&gt;To achieve that, I decided to build a complete authentication system from scratch.&lt;/p&gt;

&lt;p&gt;In this project, I implemented the entire authentication flow using:&lt;/p&gt;

&lt;p&gt;React&lt;br&gt;
Node.js&lt;br&gt;
Express.js&lt;br&gt;
PostgreSQL&lt;br&gt;
JWT&lt;br&gt;
Redux Toolkit&lt;br&gt;
Tailwind CSS&lt;br&gt;
Resend&lt;/p&gt;

&lt;p&gt;The goal wasn't just to make authentication work—it was to understand the architecture, security considerations, and communication between the frontend, backend, and database.&lt;/p&gt;

&lt;p&gt;This article is the first part of the series, where we'll build the backend foundation that powers the entire authentication system.&lt;/p&gt;

&lt;p&gt;Project Structure&lt;/p&gt;

&lt;p&gt;I prefer keeping both the frontend and backend inside a single project repository. This makes development, version control, and deployment much easier to manage.&lt;/p&gt;

&lt;p&gt;Auth-Flow&lt;br&gt;
│&lt;br&gt;
├── auth-backend&lt;br&gt;
│&lt;br&gt;
└── auth-frontend&lt;/p&gt;

&lt;p&gt;Inside the backend, I initialized a new Node.js application.&lt;/p&gt;

&lt;p&gt;mkdir auth-project&lt;br&gt;
cd auth-project&lt;/p&gt;

&lt;p&gt;mkdir auth-backend&lt;br&gt;
mkdir auth-frontend&lt;/p&gt;

&lt;p&gt;cd auth-backend&lt;/p&gt;

&lt;p&gt;mkdir server&lt;br&gt;
cd server&lt;/p&gt;

&lt;p&gt;npm init -y&lt;br&gt;
Installing Dependencies&lt;/p&gt;

&lt;p&gt;Next, I installed the packages required for building the authentication APIs.&lt;/p&gt;

&lt;p&gt;npm install express pg bcrypt dotenv cors&lt;br&gt;
npm install -D nodemon&lt;/p&gt;

&lt;p&gt;Each package serves a specific purpose.&lt;/p&gt;

&lt;p&gt;Package Purpose&lt;br&gt;
Express Build REST APIs&lt;br&gt;
pg  Connect Node.js with PostgreSQL&lt;br&gt;
bcrypt  Securely hash passwords&lt;br&gt;
dotenv  Manage environment variables&lt;br&gt;
cors    Enable frontend-backend communication&lt;br&gt;
nodemon Automatically restart the server during development&lt;/p&gt;

&lt;p&gt;Instead of installing unnecessary libraries upfront, I only installed the packages required for the current stage of development. Additional libraries such as JWT and Resend will be introduced later as the project evolves.&lt;/p&gt;

&lt;p&gt;Organizing the Backend&lt;/p&gt;

&lt;p&gt;As applications grow, keeping everything inside a single file quickly becomes difficult to maintain.&lt;/p&gt;

&lt;p&gt;To keep the project modular and scalable, I organized the backend into multiple layers.&lt;/p&gt;

&lt;p&gt;server&lt;br&gt;
│&lt;br&gt;
├── src&lt;br&gt;
│&lt;br&gt;
│── controllers&lt;br&gt;
│── services&lt;br&gt;
│── repositories&lt;br&gt;
│── middleware&lt;br&gt;
│── routes&lt;br&gt;
│── db&lt;br&gt;
│── utils&lt;br&gt;
│&lt;br&gt;
├── server.js&lt;br&gt;
└── package.json&lt;/p&gt;

&lt;p&gt;Each folder has a clearly defined responsibility.&lt;/p&gt;

&lt;p&gt;Controllers handle incoming HTTP requests and responses.&lt;br&gt;
Services contain business logic.&lt;br&gt;
Repositories interact with PostgreSQL.&lt;br&gt;
Routes define API endpoints.&lt;br&gt;
Middleware contains reusable request-processing logic.&lt;br&gt;
DB manages database connections.&lt;br&gt;
Utils stores helper functions.&lt;/p&gt;

&lt;p&gt;This layered architecture keeps responsibilities separated and makes the codebase much easier to maintain and extend.&lt;/p&gt;

&lt;p&gt;Setting Up PostgreSQL&lt;/p&gt;

&lt;p&gt;Since user information needs to persist across sessions, I chose PostgreSQL as the database.&lt;/p&gt;

&lt;p&gt;First, create the database.&lt;/p&gt;

&lt;p&gt;CREATE DATABASE auth_project;&lt;/p&gt;

&lt;p&gt;Then create the users table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&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="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;password_hash&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&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;Notice that the table stores password_hash instead of the user's original password. This is one of the most important security practices when building authentication systems.&lt;/p&gt;

&lt;p&gt;Environment Variables&lt;/p&gt;

&lt;p&gt;Sensitive information such as database credentials should never be hardcoded inside the application.&lt;/p&gt;

&lt;p&gt;Instead, I created a .env file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5000&lt;/span&gt;

&lt;span class="py"&gt;DB_HOST&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="py"&gt;DB_PORT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5432&lt;/span&gt;
&lt;span class="py"&gt;DB_USER&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="py"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;your_password&lt;/span&gt;
&lt;span class="py"&gt;DB_NAME&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;auth_project&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The .env file is excluded from version control.&lt;/p&gt;

&lt;p&gt;node_modules&lt;br&gt;
.env&lt;/p&gt;

&lt;p&gt;This prevents sensitive credentials from being pushed to GitHub.&lt;/p&gt;

&lt;p&gt;Creating the Database Connection&lt;/p&gt;

&lt;p&gt;Instead of creating a new PostgreSQL connection inside every repository, I created a reusable database connection using pg.Pool.&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pg&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;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&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="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;DB_HOST&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="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;DB_PORT&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="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;DB_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&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;DB_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;database&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;DB_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single connection pool is shared throughout the application, improving performance and reducing resource usage.&lt;/p&gt;

&lt;p&gt;Creating the Express Application&lt;/p&gt;

&lt;p&gt;Next, I configured the Express server.&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&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;cors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cors&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5173&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&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="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, the server is capable of:&lt;/p&gt;

&lt;p&gt;Parsing JSON request bodies&lt;br&gt;
Accepting requests from the React frontend&lt;br&gt;
Preparing for future authentication middleware&lt;br&gt;
Server Entry Point&lt;/p&gt;

&lt;p&gt;The application starts from server.js.&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="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/app&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;PORT&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;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="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="s2"&gt;`Server running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;npm run dev&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;starts the backend server.&lt;/p&gt;

&lt;p&gt;Server running on port 5000&lt;br&gt;
Verifying the Backend&lt;/p&gt;

&lt;p&gt;Before implementing authentication, I always verify that the server is functioning correctly.&lt;/p&gt;

&lt;p&gt;I added a simple health-check endpoint.&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;router&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;/&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;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="o"&gt;=&amp;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="nf"&gt;json&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ok&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;Request:&lt;/p&gt;

&lt;p&gt;GET /health&lt;/p&gt;

&lt;p&gt;Response:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "status": "ok"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This confirms that the backend is configured correctly before moving on to authentication.&lt;/p&gt;

&lt;p&gt;Why I Chose a Layered Architecture&lt;/p&gt;

&lt;p&gt;One mistake I often see in beginner projects is placing SQL queries directly inside controllers.&lt;/p&gt;

&lt;p&gt;Instead, I separated responsibilities into three layers.&lt;/p&gt;

&lt;p&gt;Controller&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Service&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
Repository&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
PostgreSQL&lt;br&gt;
Controller&lt;/p&gt;

&lt;p&gt;Receives the HTTP request and returns the HTTP response.&lt;/p&gt;

&lt;p&gt;Service&lt;/p&gt;

&lt;p&gt;Contains all business logic, including:&lt;/p&gt;

&lt;p&gt;Email validation&lt;br&gt;
Password hashing&lt;br&gt;
JWT generation&lt;br&gt;
Authentication rules&lt;br&gt;
Repository&lt;/p&gt;

&lt;p&gt;Handles database operations only.&lt;/p&gt;

&lt;p&gt;This separation makes the project easier to:&lt;/p&gt;

&lt;p&gt;Debug&lt;br&gt;
Test&lt;br&gt;
Scale&lt;br&gt;
Maintain&lt;/p&gt;

&lt;p&gt;As more authentication features are added, each layer continues to have a single responsibility.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;With the backend infrastructure now in place, the application is ready to implement authentication features.&lt;/p&gt;

&lt;p&gt;In the next article, we'll build the User Registration Flow, where we'll:&lt;/p&gt;

&lt;p&gt;Validate user input&lt;br&gt;
Prevent duplicate accounts&lt;br&gt;
Securely hash passwords using bcrypt&lt;br&gt;
Store users in PostgreSQL&lt;br&gt;
Return a safe API response&lt;/p&gt;

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

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

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

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

&lt;p&gt;Continue Reading Part 2: &lt;a href="https://dev.to/t_sriya_2af6abc7e8d4e87da/part-2building-an-authentication-system-from-scratch-backend-setup-59gg"&gt;https://dev.to/t_sriya_2af6abc7e8d4e87da/part-2building-an-authentication-system-from-scratch-backend-setup-59gg&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live App: &lt;a href="https://auth-flow-five-iota.vercel.app/auth/" rel="noopener noreferrer"&gt;https://auth-flow-five-iota.vercel.app/auth/&lt;/a&gt;&lt;br&gt;
Backend API: &lt;a href="https://auth-flow-backend-1v2h.onrender.com/" rel="noopener noreferrer"&gt;https://auth-flow-backend-1v2h.onrender.com/&lt;/a&gt;&lt;br&gt;
github url: &lt;a href="https://github.com/sriyaT/Auth-Flow" rel="noopener noreferrer"&gt;https://github.com/sriyaT/Auth-Flow&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Connect : LinkedIn : &lt;a href="https://www.linkedin.com/in/t-sriya-b4234510a/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/t-sriya-b4234510a/&lt;/a&gt;, github : &lt;a href="https://github.com/sriyaT" rel="noopener noreferrer"&gt;https://github.com/sriyaT&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Author: Sriya T.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>node</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
