DEV Community

Cover image for ASP.NET Core Authentication with JWT (Easy, Real-World Explanation)
Mehedi Hasan
Mehedi Hasan

Posted on

ASP.NET Core Authentication with JWT (Easy, Real-World Explanation)

Why Authentication Feels Hard

When I first started building web apps in ASP.NET Core, authentication confused me. On paper, it looked like:

  1. User logs in.
  2. Server checks username/password.
  3. Server gives a token.
  4. User can access protected pages.

Easy, right?

But in real projects, things get tricky. You suddenly need to think about:

  • Where should the token live on the client?
  • How long should it last?
  • How do you keep the user logged in without being insecure?
  • What happens if someone steals the token?
  • How do roles and permissions work?

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


What JWT Really Is (Like a Letter)

What JWT Really Is

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

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

Important points I learned the hard way:

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

Example of a payload:

{
  "sub": "123",       // User ID
  "role": "Admin",    // User role
  "iat": 1670000000,  // Issued at time
  "exp": 1670003600   // Expiry time
}
Enter fullscreen mode Exit fullscreen mode

How JWT Works Step by Step

How JWT Works

Here’s the process I use in real projects:

1. User Logs In

  • The user types username and password on a login form.
  • Server checks the database.
  • If correct, server creates a JWT and optionally a refresh token.
  • Server sends these tokens back to the user.

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


2. Client Uses the Token

The client stores the JWT somewhere safe:

  • Web apps: HttpOnly cookies (cannot be accessed by JavaScript, safer).
  • Mobile apps: secure storage.

Every time the client calls a protected API, it sends the JWT in the header:

Authorization: Bearer <token>
Enter fullscreen mode Exit fullscreen mode

Server steps:

  1. Extract the token.
  2. Check the signature (is it sealed correctly?).
  3. Check expiry (is it still valid?).
  4. Check role or permissions.
  5. Allow request if everything is valid, otherwise return 401 Unauthorized.

Tip from experience: If your JWT is big, it slows down every API call. Keep it small.


3. Refreshing Tokens

Short-lived tokens are safe (15–30 mins), but logging in every 15 minutes is annoying.

Solution: refresh tokens:

  • Refresh tokens live longer (days or weeks).
  • When the access token expires, the client sends the refresh token to /refresh.
  • Server checks the refresh token and issues a new access token.
  • Optional: rotate the refresh token (replace it with a new one).

Story: 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.


How JWT Works in ASP.NET Core (Conceptually)

  1. Configure JWT Middleware
  • Add a secret key, issuer, and audience.
  • Enable validation for signature and expiry.
  1. Login Endpoint
  • Receive username/password.
  • Check the database.
  • Issue access token + refresh token.
  • Send them to the client.
  1. Protect APIs
  • Use [Authorize] on controllers or actions.
  • Use [Authorize(Roles="Admin")] for role-based access.
  1. Refresh Token Endpoint
  • Validate refresh token.
  • Issue new access token.
  • Rotate refresh token if needed.

Common Mistakes Beginners Make

  1. Storing JWT in localStorage
  • Easy, but vulnerable to XSS attacks.
  • Safer: HttpOnly cookies.
  1. Tokens that live too long
  • Access tokens should be short-lived (15–30 mins).
  • Long tokens are risky if stolen.
  1. Skipping refresh tokens
  • Either log users out too often or make access tokens too long. Both are bad.
  1. Hardcoding secrets in code
  • Never commit keys to GitHub. Use environment variables or secret managers.
  1. Ignoring revocation
  • Once issued, JWT is valid until it expires. Use short-lived tokens + refresh tokens to control sessions.

Security Tips I Always Follow

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

When JWT Is Right and When It’s Not

Good for:

  • Single-page apps (React, Angular) calling APIs.
  • Mobile apps.
  • Microservices (stateless auth).

Not ideal for:

  • Apps that need server-side logout control.
  • Highly sensitive data in tokens.
  • Frequent permission changes during a session.

Real-World Advice

JWT is easy to understand, but tricky in production. Most mistakes come from:

  • Bad storage (localStorage vs cookies)
  • Long expiry (tokens live too long)
  • Poor secret management (hardcoded keys)

Think of JWT as a key:

  • Short-lived
  • Stored safely
  • Checked on every request

If you do this, authentication works reliably. If not, you’ll spend hours debugging 401 errors and security holes.

Story: 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.


Visual Flow of JWT (How It Works)

  1. Login → Server validates → Returns access + refresh tokens
  2. Client stores tokens → Sends access token on every API request
  3. Server validates token → Grants or denies access
  4. Access token expires → Client sends refresh token → Server issues new access token

Top comments (0)