<?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: Mehedi Hasan</title>
    <description>The latest articles on DEV Community by Mehedi Hasan (@mehedi_hasan_aa4e36cf9312).</description>
    <link>https://dev.to/mehedi_hasan_aa4e36cf9312</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3706430%2F83ea586e-559a-4ff9-9868-df4e9c1eeb14.png</url>
      <title>DEV Community: Mehedi Hasan</title>
      <link>https://dev.to/mehedi_hasan_aa4e36cf9312</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mehedi_hasan_aa4e36cf9312"/>
    <language>en</language>
    <item>
      <title>ASP.NET Core Authentication with JWT (Easy, Real-World Explanation)</title>
      <dc:creator>Mehedi Hasan</dc:creator>
      <pubDate>Mon, 12 Jan 2026 08:25:56 +0000</pubDate>
      <link>https://dev.to/mehedi_hasan_aa4e36cf9312/aspnet-core-authentication-with-jwt-easy-real-world-explanation-4k69</link>
      <guid>https://dev.to/mehedi_hasan_aa4e36cf9312/aspnet-core-authentication-with-jwt-easy-real-world-explanation-4k69</guid>
      <description>&lt;h3&gt;
  
  
  Why Authentication Feels Hard
&lt;/h3&gt;

&lt;p&gt;When I first started building web apps in ASP.NET Core, authentication confused me. On paper, it looked like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User logs in.&lt;/li&gt;
&lt;li&gt;Server checks username/password.&lt;/li&gt;
&lt;li&gt;Server gives a token.&lt;/li&gt;
&lt;li&gt;User can access protected pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Easy, right?&lt;/p&gt;

&lt;p&gt;But in real projects, things get tricky. You suddenly need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where should the token live on the client?&lt;/li&gt;
&lt;li&gt;How long should it last?&lt;/li&gt;
&lt;li&gt;How do you keep the user logged in without being insecure?&lt;/li&gt;
&lt;li&gt;What happens if someone steals the token?&lt;/li&gt;
&lt;li&gt;How do roles and permissions work?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I spent hours debugging &lt;strong&gt;why valid tokens weren’t working&lt;/strong&gt;, or why users got logged out suddenly. After doing this multiple times in banking and enterprise projects, I learned &lt;strong&gt;how to make JWT authentication reliable and safe&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  What JWT Really Is (Like a Letter)
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fk6it70blukrsrmr4nl4c.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.amazonaws.com%2Fuploads%2Farticles%2Fk6it70blukrsrmr4nl4c.png" alt="What JWT Really Is" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JWT stands for &lt;strong&gt;JSON Web Token&lt;/strong&gt;, but don’t worry about the name. Think of it as a &lt;strong&gt;sealed letter&lt;/strong&gt; the server gives to the user after login.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Header&lt;/strong&gt; → a small note that says “this letter is sealed with algorithm X.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload&lt;/strong&gt; → the actual content, like your user ID, role, and when the token expires.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signature&lt;/strong&gt; → the wax seal that proves no one opened or changed the letter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important points I learned the hard way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The payload is &lt;strong&gt;not secret&lt;/strong&gt;. Anyone can open the letter and see the info. So never put passwords or sensitive info there.&lt;/li&gt;
&lt;li&gt;JWT is &lt;strong&gt;stateless&lt;/strong&gt;, meaning the server doesn’t remember it. Once issued, it’s valid until it expires.&lt;/li&gt;
&lt;li&gt;JWT travels with every request, so keep it small.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example of a payload:&lt;/strong&gt;&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;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ID&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;role&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1670000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Issued&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1670003600&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Expiry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;time&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;
  
  
  How JWT Works Step by Step
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2F2xsl8plklmxq671dq3sy.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.amazonaws.com%2Fuploads%2Farticles%2F2xsl8plklmxq671dq3sy.png" alt="How JWT Works" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s the process I use in real projects:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. User Logs In
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The user types username and password on a login form.&lt;/li&gt;
&lt;li&gt;Server checks the database.&lt;/li&gt;
&lt;li&gt;If correct, server &lt;strong&gt;creates a JWT&lt;/strong&gt; and &lt;strong&gt;optionally a refresh token&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Server sends these tokens back to the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Analogy:&lt;/strong&gt; Think of the JWT as a “hall pass.” With it, the user can enter secure rooms (APIs) without asking for credentials every time.&lt;/p&gt;




&lt;h4&gt;
  
  
  2. Client Uses the Token
&lt;/h4&gt;

&lt;p&gt;The client stores the JWT somewhere safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web apps: &lt;strong&gt;HttpOnly cookies&lt;/strong&gt; (cannot be accessed by JavaScript, safer).&lt;/li&gt;
&lt;li&gt;Mobile apps: &lt;strong&gt;secure storage&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every time the client calls a protected API, it sends the JWT in the header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract the token.&lt;/li&gt;
&lt;li&gt;Check the signature (is it sealed correctly?).&lt;/li&gt;
&lt;li&gt;Check expiry (is it still valid?).&lt;/li&gt;
&lt;li&gt;Check role or permissions.&lt;/li&gt;
&lt;li&gt;Allow request if everything is valid, otherwise return 401 Unauthorized.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tip from experience:&lt;/strong&gt; If your JWT is big, it slows down every API call. Keep it small.&lt;/p&gt;




&lt;h4&gt;
  
  
  3. Refreshing Tokens
&lt;/h4&gt;

&lt;p&gt;Short-lived tokens are safe (15–30 mins), but logging in every 15 minutes is annoying.&lt;/p&gt;

&lt;p&gt;Solution: &lt;strong&gt;refresh tokens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refresh tokens live longer (days or weeks).&lt;/li&gt;
&lt;li&gt;When the access token expires, the client sends the refresh token to &lt;code&gt;/refresh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Server checks the refresh token and issues a &lt;strong&gt;new access token&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Optional: rotate the refresh token (replace it with a new one).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Story:&lt;/strong&gt; In one banking app, we skipped refresh tokens. Users kept logging out every 20 minutes. Adding refresh tokens fixed the problem and made the UX smooth.&lt;/p&gt;




&lt;h3&gt;
  
  
  How JWT Works in ASP.NET Core (Conceptually)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Configure JWT Middleware&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Add a secret key, issuer, and audience.&lt;/li&gt;
&lt;li&gt;Enable validation for signature and expiry.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Login Endpoint&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Receive username/password.&lt;/li&gt;
&lt;li&gt;Check the database.&lt;/li&gt;
&lt;li&gt;Issue access token + refresh token.&lt;/li&gt;
&lt;li&gt;Send them to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Protect APIs&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;[Authorize]&lt;/code&gt; on controllers or actions.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;[Authorize(Roles="Admin")]&lt;/code&gt; for role-based access.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Refresh Token Endpoint&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Validate refresh token.&lt;/li&gt;
&lt;li&gt;Issue new access token.&lt;/li&gt;
&lt;li&gt;Rotate refresh token if needed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Common Mistakes Beginners Make
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Storing JWT in &lt;code&gt;localStorage&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Easy, but vulnerable to XSS attacks.&lt;/li&gt;
&lt;li&gt;Safer: HttpOnly cookies.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tokens that live too long&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Access tokens should be short-lived (15–30 mins).&lt;/li&gt;
&lt;li&gt;Long tokens are risky if stolen.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Skipping refresh tokens&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Either log users out too often or make access tokens too long. Both are bad.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Hardcoding secrets in code&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Never commit keys to GitHub. Use &lt;strong&gt;environment variables&lt;/strong&gt; or secret managers.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ignoring revocation&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Once issued, JWT is valid until it expires. Use short-lived tokens + refresh tokens to control sessions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Security Tips I Always Follow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Keep access tokens short-lived.&lt;/li&gt;
&lt;li&gt;Use secure storage (HttpOnly cookies for web, secure storage for mobile).&lt;/li&gt;
&lt;li&gt;Keep JWT payload minimal.&lt;/li&gt;
&lt;li&gt;Always use HTTPS.&lt;/li&gt;
&lt;li&gt;Rotate refresh tokens regularly.&lt;/li&gt;
&lt;li&gt;Validate signature, expiry, audience, and roles.&lt;/li&gt;
&lt;li&gt;Log login attempts, token refreshes, and failures.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  When JWT Is Right and When It’s Not
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-page apps (React, Angular) calling APIs.&lt;/li&gt;
&lt;li&gt;Mobile apps.&lt;/li&gt;
&lt;li&gt;Microservices (stateless auth).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apps that need server-side logout control.&lt;/li&gt;
&lt;li&gt;Highly sensitive data in tokens.&lt;/li&gt;
&lt;li&gt;Frequent permission changes during a session.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Real-World Advice
&lt;/h3&gt;

&lt;p&gt;JWT is easy to understand, but tricky in production. Most mistakes come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bad storage&lt;/strong&gt; (localStorage vs cookies)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long expiry&lt;/strong&gt; (tokens live too long)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor secret management&lt;/strong&gt; (hardcoded keys)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of JWT as a &lt;strong&gt;key&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short-lived&lt;/li&gt;
&lt;li&gt;Stored safely&lt;/li&gt;
&lt;li&gt;Checked on every request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you do this, authentication works reliably. If not, you’ll spend hours debugging 401 errors and security holes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Story:&lt;/strong&gt; In one app, a leaked long-lived token allowed an attacker to access user data. We switched to 15-min access tokens + refresh tokens and never had that problem again.&lt;/p&gt;




&lt;h3&gt;
  
  
  Visual Flow of JWT (How It Works)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Login&lt;/strong&gt; → Server validates → Returns access + refresh tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client stores tokens&lt;/strong&gt; → Sends access token on every API request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server validates token&lt;/strong&gt; → Grants or denies access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access token expires&lt;/strong&gt; → Client sends refresh token → Server issues new access token&lt;/li&gt;
&lt;/ol&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.amazonaws.com%2Fuploads%2Farticles%2Fzzinkdwah8ml8bnaxtcr.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.amazonaws.com%2Fuploads%2Farticles%2Fzzinkdwah8ml8bnaxtcr.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>security</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
