← Home
.NETWeb APIReported by candidates

How does JWT authentication work end to end in a .NET API: issuing the token, validating it, roles, expiry and refresh?

19 reports in the question index (7 linked below): Infosys (3), NeoSOFT (2), Sagar Informatics (2), TCS (2), Capgemini (2), Deloitte (2), Synechron and others, in the first technical round. This card is the .NET side; the Angular side (interceptor, storing the token) is its own card.

Say this first · about 30 seconds
Make it yours · Navistar Financial dealer portal API (invented example)
The code, simplified

Invented to show the shape, in C#. Tap Next: the reset, then the old phone.

Navistar.sln · where this file sits
  • Services/
TokenService.cs · line 1 · 1 of 9_db is the database object, handed in by DI through the constructor.

Fix later in your repo Example written for this card: invented, not from a real codebase.

Say it out loud, then check

Show the 30-second answer
  • A JWT is a signed token with three parts: a header, a payload of claims like the user id, the role and an expiry time, and a signature. The payload is only encoded, not encrypted, so anyone can read it; the signature is what stops anyone from changing it.
  • At login the API checks the password once, then signs a short-lived access token, about fifteen minutes, and hands out a long random refresh token, which it stores hashed in the database. The client sends the access token on every call as a bearer token in the Authorization header.
  • On each request the middleware checks the token is genuine, not expired and meant for this API, then turns it into the user. Then authorization, with a role, decides if that user may call the endpoint. The server keeps no session, so any instance can check the token.
  • When the access token expires the API answers 401, and the client calls the refresh endpoint. The API looks up the refresh token, revokes it and issues a new pair, which is called rotation. Because a JWT can't be recalled once issued, we keep access tokens short and do logout and revocation on the refresh tokens.
Check your answer: tick what you said

You said 0 of 7

Then add your example: Navistar Financial dealer portal API (invented example)
  • Situation: a dealership told Navistar that a salesperson had left, and their admin reset his password. Two days later he was still looking at the dealer's contracts on his phone: the portal kept getting new access tokens with his old refresh token, which was valid for 7 days.
  • What I did: a password reset or a disabled account now calls , which marks every one of that user's refresh tokens as revoked. Access tokens can't be recalled, but they die within 15 minutes, and the next refresh is refused.
  • Result: after a reset, every device is signed out within 15 minutes. A test resets a password and checks that the old refresh token gets 401.
  • What I'd say: a JWT can't be taken back, so logout and revocation happen on the refresh tokens, and the access token is short enough that its last minutes don't matter.

See it

1/7Maria signs in. The portal POSTs her email and password to /api/auth/login.

In plain words

The senior answer · about 2 minutes
What the token is
  • A JWT is three Base64URL parts: a header with the algorithm, a payload with the claims, and a signature over both. The signature gives integrity, not secrecy: anyone can decode the payload, so it carries ids and roles, never a password, a card number or an income.
  • The API signs it at login with a key only it has. On every request it recomputes the signature, so a dealer who edits their role in the payload gets a 401. Because the token carries everything needed, the API keeps no session, and any of our instances can check any call.
Issue and validate
  • Login checks the password hash once, then issues a fifteen-minute access token with sub, role and dealer_id, plus a random refresh token whose SHA-256 hash goes in the database. The client sends the access token as a bearer token on every call.
  • validates the signature, issuer, audience and expiry. I always set explicitly: its default is five minutes, so an expired token keeps working for five more minutes, which surprises people in a security test. Then [Authorize(Roles = "DealerAdmin")] or a policy decides what the user may do.
Expiry, refresh and logout
  • When the access token expires, the API answers 401 with invalid_token, and the client calls the refresh endpoint. The API finds the refresh token by its hash, checks it isn't expired or revoked, revokes it and issues a new pair. That rotation means a stolen refresh token works once at most, and if a revoked one comes back we treat it as theft and revoke the user's tokens.
  • Rotation has a trap: a browser that sends two refreshes at once looks like theft. On the Navistar portal that logged dealers out several times a day; the incident in Deep is that, and the fix was a short grace window. Logout and password resets revoke all of a user's refresh tokens; the access token simply runs out within fifteen minutes.
What I check in production
  • The signing key is in and identical on every instance; for tokens from we set (the address of the identity provider that issued the tokens) and let the handler download Entra's public keys, so key rotation just works. HTTPS only, tokens never logged, and a test that an expired, a tampered and a wrong-audience token all get 401.

Two tokens, one morning

Maria's access tokens and refresh tokens over 30 minutes: issue, use, expiry, refresh, and what reuse looks like.

1/6Issue. 09:00 login: access token 1 (15 min) and refresh token 1 (7 days).

The story

  1. Before

    Navistar Financial's dealer portal is an Angular app that truck dealers use to look up their contracts and request . Its first API used server sessions: at login the server kept a record of who was signed in, in its own memory, and the browser got a cookie (a small value the browser sends back with every request) holding that record's number. Then the API was scaled out to two servers (two copies of the app behind a load balancer, which sends each request to either one).

    1/4Before: Maria signs in. Server A remembers her session in its own memory.

  2. Why it had to change

    A session lives in one server's memory, so every server must share it (a shared cache, or "sticky" routing that always sends a user to the same server). A token that carries the user's identity and is signed by the API can be checked by any server with the key, with nothing to look up.

  3. How it works now

    Words you will meet. A JWT (JSON Web Token, said "jot") is that signed token. Claims are the facts inside it, like the user id and the role. Signing means the API computes a short code, the signature, from the token's contents and a secret key; change one letter of the contents and the signature no longer matches. Bearer means whoever holds the token is treated as that user, so it must stay secret and short-lived. A JWT is three parts joined with dots:

    1/4Header: which signing method. Encoded as text, not hidden.

  4. What we gained

    No session store, any server can check any call, and the user's role travels with every request. The cost: a signed token can't be taken back, which is why the token we send with every call, the access token, lives 15 minutes, and a second, long-lived refresh token, used only to ask for a new access token, has a hash (a one-way fingerprint of it) kept in our database. The rest of the card follows Maria's tokens through one morning.

In the lending API

The token service: these lines make the access token; the diagram above shows it being used.

Only the key lines are shown; Deep walks the whole file.

Lending.sln · where this file sits
  • src/Navistar.DealerPortal.Api/Services/
⋯ 5 lines in Deep⋯ 1 line in Deep
TokenService.cs · line 22 · 1 of 4Inside the method that creates the access token. spec describes the token to make: who issued it, how long it lives, how it is signed. Filled in below.

The code, file by file

The Navistar dealer portal API: start-up, the login and refresh endpoints, the token service, and a protected controller. Tap Next to walk Maria's morning.

Lending.sln · where these 4 files sit
  • src/Navistar.DealerPortal.Api/
    • Controllers/
    • Services/
Diagram · step 1Maria signs in. The portal POSTs her email and password to /api/auth/login.
Program.cs · line 3 · 1 of 17config reads settings: appsettings.json for the issuer and audience, for the signing key.

What they ask next

The follow-ups interviews really ask. Answer out loud, then tap to check.

Follow-up tree

Answer, then open the next level. That is the order an interviewer digs in.

Reporteda candidate was asked Intervieweran interviewer asks it

Production bugs, and how to tell them

Each one: what you see, why, the fix in code, and the line to say.

Production incident: Dealers were thrown back to the login page several times a day

P3 · minorNavistar Financialcame with a deployDealer portal API (ASP.NET Core on App Service)

Composed, not a real event at Navistar Financial. The mechanism is real, checked by a run on .NET 10 with EF Core: with refresh-token rotation and reuse detection, a second refresh sent 300 ms after the first with the same token is treated as reuse and revokes the user's tokens; a 30-second grace window lets both succeed. Times, counts, names, ticket and PR numbers are composed. OAuth 2.0 Security Best Current Practice (RFC 9700): refresh token rotation

The story in 5 lines
  1. From Thu 1 Oct, Navistar dealers were sent back to the portal's login page two or three times a day, often in the middle of a .
  2. Release 4.3 on 30 Sep had added refresh-token rotation with reuse detection, after a security test asked for it: a revoked refresh token coming back means theft, so all the user's tokens are revoked.
  3. But the portal sometimes sends two refreshes at once when several calls expire together. The second one arrives a moment after the first has rotated the token, looks like reuse, and signs the dealer out.
  4. switched reuse detection to log-only with a . The fix: a 30-second grace window for a token that was just rotated, and a portal change so only one refresh runs at a time.
  5. Now a test sends two refreshes 300 ms apart, and a dashboard shows reuse events per hour.
Who's who (5 people)
You
.NET developer on the vendor team (L3). Owns the fix.
Tech lead
Vendor senior. Reviews every PR, decides on the flag.
L2 pod
Vendor support. Runs queries, flips feature flags with approval.
Portal developer
Angular developer on the same vendor team. Owns the portal side.
Navistar dealer support lead
Collects dealer complaints, confirms the fix.

Tap a step. The map lights the place where it happens.

1/8 Detect

Dealers keep landing on the login page, mid-application.

Mon 5 Oct, 09:00 CT (19:30 IST)Navistar dealer support, then the L2 pod

  • What: dealer support has 23 complaints since Thursday: "the portal logged me out in the middle of a and I lost what I typed".
  • How we saw it: finds a new warning in since release 4.3: Refresh token reuse: {UserId}, about 1,900 a day, from 600 different users.
  • Why it hid: every logout looked like a normal 401 and a normal login. No error was raised, and the warning was new, so no alert watched it.
App Insights: reuse warnings per day
traces
| where timestamp > ago(7d)
| where message has "Refresh token reuse"
| summarize events = count(),
    users = dcount(tostring(
      customDimensions.UserId))
    by bin(timestamp, 1d)
// 0 a day before 30 Sep,
// ~1,900 a day after
In theory

A new security warning gets a dashboard and a baseline before release.

In practice

It shipped with no alert and no expected rate. Dealers found it first.

Try it yourself · 15 minutes, on your Mac
  1. Build this card's login and refresh endpoints (or dotnet new webapi and copy them), with a SQLite database.
  2. Log in, then send two refreshes with the same refresh token: the second in a sleep 0.3 after the first.
  3. With the Before code, the second gets 401 and the first one's new token is dead too.
  4. Add the grace window and repeat: both get 200. Wait 31 seconds and reuse the token: 401.
You should see

Rotation with reuse detection can't tell a thief from a browser that refreshes twice. A short grace window keeps the protection without signing out normal users.

The senior move

"Refresh tokens rotate and a reused one is treated as theft, but with a short grace window, because a browser with several tabs or parallel calls will legitimately refresh twice."

Sources Sourced: refresh token rotation and reuse detection (RFC 9700, OAuth 2.0 Security Best Current Practice); identity providers offer a reuse interval for the same reason. Checked with a real run on .NET 10 and EF Core with SQLite: two refreshes 300 ms apart gave 200 then 401 and revoked the user's tokens; with the grace window 200 and 200; reuse after 31 seconds gave 401. Composed: Navistar's systems, dates, counts, names, ticket and numbers and the .

Traps

Traps

What should you say instead? Tap to check.

Live coding

Easy first, then harder. Set a timer and type it before you look.

Task 1

Question index: Musteasy · 10 minutes

Configure JWT bearer validation in Program.cs: issuer, audience, signing key from configuration, 30-second clock skew. Then protect so every action needs a token and needs the role .

Sagar Informatics: explain the JWT authentication flow
Stuck? Hints
  1. Which method adds the JWT handler, and which object holds the rules?
  2. Which checks are on by default, and which values must you supply?
  3. Where must go relative to ?
Show a solution
Lending.sln · where these 2 files sit
  • Controllers/
Program.cs · line 8 · 1 of 1Set it; the default is 5 minutes.
What the interviewer checks
  • AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(...) with , , .
  • set explicitly.
  • Key read from configuration (), not hard-coded.
  • [Authorize] on the controller, [Authorize(Roles = "DealerAdmin")] on the action; before .

Task 2

Reportedmedium · 30 minutes

Implement JWT login: POST /api/auth/login checks the password and returns an access token (15 minutes, with the user's role) and a refresh token; POST /api/auth/refresh exchanges a valid refresh token for a new pair and revokes the old one. Protect an endpoint with [Authorize] and a role.

Corporate Infotech: how would you implement JWT?
You are given
Lending.sln · where these 2 files sit
  • Data/
RefreshToken.cs · line 3 · 1 of 2Each { get; set; } line is a column of the table.
Stuck? Hints
  1. What goes in the access token, and what must never go in it?
  2. Why is the refresh token random instead of a second JWT?
  3. What do you store in the database: the refresh token, or something else?
  4. What makes a used refresh token unusable?
Show a solution
Lending.sln · where this file sits
  • Controllers/
AuthController.cs · line 1 · 1 of 5Runs for POST /api/auth/login.
What the interviewer checks
  • Password checked against a hash; wrong credentials give 401 without saying which part was wrong.
  • Access token: sub, role, an expiry of minutes, signed with a key from configuration.
  • Refresh token: random bytes, stored as a hash with an expiry, revoked on use (rotation).
  • Refresh refuses unknown, expired or revoked tokens with 401.
  • Knows the trade-off: access tokens can't be revoked early, so they stay short.
With AI tools

Interviewers now ask how AI changes this. Assistants produce a working JWT login in a minute, and the generated code is a good checklist of what to fix: the signing key hard-coded in appsettings.json, ValidateLifetime = false or ValidateIssuer = false to get a test passing, a 7-day access token with no refresh, the refresh token stored in plain text, and the user's email or more in the payload. I review generated auth code against the must-say list on this card, and I never let an assistant turn a validation flag off to fix a 401.

Glossary: every name, one line each →

My notes

Paste the NOTES block from your doubt chat here. The grader never reads notes; the coach reads "Still unclear".

Read moreWhere it was asked