.NET Academy

Authentication: sessions, tokens and passwords

Prove who a user is, remember them across requests, and store passwords safely.

14 min read

You will learn

  • Distinguish authentication from authorization
  • Compare cookie sessions with bearer tokens (JWT)
  • Read the parts of a JWT and explain what the signature guarantees
  • Store passwords with a slow, salted hash

Two different questions

  • Authentication (AuthN) — who are you? Proving identity: a password, a passkey, a login with Google.
  • Authorization (AuthZ) — what may you do? Deciding whether this identity can perform this action: only admins may delete products, only the owner may see an order.

Authentication happens first; authorization uses its result. The status codes reflect this: 401 "I don't know who you are", 403 "I know who you are, and no".

The problem: HTTP forgets

After you log in once, every following request must say who you are again — HTTP is stateless. There are two main approaches.

1. Server-side sessions (cookies)

POST /login  (username + password)
← 200 OK   Set-Cookie: session=8f2c…; HttpOnly; Secure; SameSite=Lax
             (server stores: 8f2c… → user 42, expires in 2 h)
 
GET /orders  Cookie: session=8f2c…
             (server looks up 8f2c… → user 42)
  • ✅ Easy to revoke (delete the session server-side); the cookie holds no data.
  • ✅ Browsers handle cookies automatically; HttpOnly keeps scripts away from them.
  • ❌ The server must store sessions somewhere every instance can read (a database or Redis).
  • Classic choice for server-rendered websites.

2. Bearer tokens (JWT)

POST /login  (username + password)
← 200 OK   { "access_token": "eyJhbGciOi…", "expires_in": 900 }
 
GET /orders  Authorization: Bearer eyJhbGciOi…
             (server verifies the signature — no lookup needed)

A JSON Web Token carries the user's identity and claims inside it, signed by the server so it can't be modified:

header.payload.signature
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiI0MiIsInJvbGUiOiJ1c2VyIn0 . 4kD2…
{"alg":"HS256"}        {"sub":"42","role":"user","exp":…}    HMAC(header.payload, secret)
  • ✅ Stateless: any server with the key can verify it — great for APIs, mobile apps and microservices.
  • ❌ Hard to revoke before expiry → keep access tokens short-lived (5–15 minutes) and use refresh tokens to get new ones.
  • ❌ Readable by anyone who holds it — no secrets inside.

Explore a real token — decode it, look at the expiry, and see what tampering does to the signature:

Interactive

JWT decoder

Decode a JSON Web Token and verify its signature (HS256)

Generating a sample token…

A JWT is signed, not encrypted: anyone can decode the header and payload — never put secrets in them. The signature only proves the token was issued by someone who knows the secret and hasn't been modified. Try Tamper: the payload now says admin, but the signature no longer matches. Without the secret, an attacker can't produce a valid one — that's what an API checks on every request.

Storing passwords

If your database leaks (assume it will, eventually), attackers must not get usable passwords. Store neither plain text nor encrypted passwords — store a password hash:

  • A hash is one-way: you can check a password against it, but not reverse it.
  • A unique random salt per user means identical passwords produce different hashes, defeating precomputed tables.
  • A deliberately slow algorithm — PBKDF2, bcrypt, scrypt or Argon2 — makes each guess expensive. Fast hashes like plain SHA-256 are unsuitable: attackers can try billions per second.
// ASP.NET Core Identity does this for you (PBKDF2 with a random salt and many iterations):
var hasher = new PasswordHasher<User>();
string hash = hasher.HashPassword(user, "correct horse battery staple");
var result = hasher.VerifyHashedPassword(user, hash, attempt);   // Success / Failed

Don't build it all yourself

Authentication is easy to get subtly wrong. Use ASP.NET Core Identity for local accounts, or delegate login entirely to an identity provider (Microsoft Entra ID, Auth0, Keycloak, Google) via OpenID Connect — the next lesson. Add multi-factor authentication and consider passkeys for strong, phishing-resistant login.

Authorization models

Once you know who someone is:

  • Role-based — [Authorize(Roles = "Admin")]: simple, coarse.
  • Claims/policy-based — "must have claim subscription=premium" or "must be over 18".
  • Resource-based — "may edit this document only if they own it" — checked in code with the resource at hand.

Key takeaways

  • Authentication proves identity (401 when missing); authorization grants permissions (403 when denied).
  • Sessions keep state on the server behind a cookie; JWTs carry signed claims and are verified without lookups.
  • JWT payloads are readable — the signature only guarantees integrity; keep tokens short-lived.
  • Store passwords with a slow, salted hash (PBKDF2, bcrypt, Argon2) — or use Identity / an identity provider.

Quiz

4 questions · 3 correct to pass
  1. 1.What's the difference between authentication and authorization?

  2. 2.Is the payload of a JWT secret?

  3. 3.How should passwords be stored?

  4. 4.What's a main drawback of long-lived JWT access tokens?

To complete this lesson

  • ○ Pass the quiz (3/4 correct)