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
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.
Make it yours · 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.
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 repoExample 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
PortalAngular
Auth APIlogin, renew
JWT checkstep on every request
Contractsthe API
POST login: email, password
1/7Maria signs in. The portal POSTs her email and password to /api/auth/login.Password OK. The API signs an access token (15 min) and makes a refresh token (7 days).Each API call carries the access token: Authorization: Bearer eyJ….The JWT middleware (a step each request passes through) checks signature, issuer, audience, expiry.16 minutes later, the same call. The token has expired: 401invalid_token.The portal sends the refresh token. The API revokes it (unusable now) and issues a new pair.The call is repeated with the new access token: 200. Maria never saw a login page.
In plain words
JWT (JSON Web Token): a token in three parts joined by dots: header (how it's signed), payload (the claims) and signature. Each part is written in Base64URL, a way to turn bytes into plain letters. That's encoding, not encryption: anyone can read the payload.
Claims: the facts in the payload. Standard ones have short names: sub (who), exp (expires at), iss (issuer: who made it), aud (audience: who it's for). We add our own: role,dealer_id.
Signature: a code computed from the header, the payload and a secret key. Our API can recompute it; if one letter of the payload changed, it won't match and the token is refused. That's why nobody can give themselves a higher role.
Access token: the JWT sent with every call, in the header Authorization: Bearer <token>. Short-lived (15 minutes, plus a small leeway for clocks, the clock skew explained below), because once signed it can't be recalled.
Refresh token: a long random string, not a JWT, valid for days. It's used only to get a new access token. The database keeps a hash of it (a one-way scramble: you can check a token against it but cannot get the token back), so we can revoke it.
Rotation: each time a refresh token is used, it's revoked and a new one is issued. A stolen one works at most once.
Validation: on every request the JWT bearer (a step every request passes through before the controller) checks the signature, the issuer, the audience and the expiry (with a small allowed clock skew: leeway for two computers' clocks being a little apart), then turns the claims into ().
Stateless: the server stores no session. Any copy of the API with the key can check any token, which is what makes scaling out easy.
Roles in the token: the role claim becomes a role in .NET, so [Authorize(Roles = "DealerAdmin")] works. A changed role only shows up in the next token, at most 15 minutes later.
HS256 vs RS256: HS256 signs and checks with one shared secret (fine when one API makes and checks its own tokens). RS256 signs with a private key and checks with a public one (a pair of keys: what one locks, only the other unlocks), so many APIs can check tokens without being able to make them. uses RS256.
Where the key lives: in Azure , read at start-up. Never in appsettings.json or the repo: whoever has the key can sign tokens as anyone.
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.
access tokens
refresh tokens
API calls
access 1
refresh 1
0 ms
1/6Issue. 09:00 login: access token 1 (15 min) and refresh token 1 (7 days).Validate. Every call carries access 1. Each one is checked and gets 200.Expire. At 09:16 access 1 is past its expiry and the 30-second skew: 401.Refresh and rotate. Refresh 1 buys access 2 and refresh 2. Refresh 1 is revoked.Retry. The same call with access 2: 200. Maria noticed nothing.Reuse. 09:25, long after rotation: revoked refresh 1 comes again. Theft: all her tokens revoked.
The story
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).
Dealer portalbrowser
Server Ahas session
Server Bno session
login
session 51 = Maria
cookie: session 51
1/4Before: Maria signs in. Server A remembers her session in its own memory.Her next call is sent to server B. B has never heard of session 51.Fix: the server signs a token that carries who she is. It remembers nothing.Any server can check the signature and read the token. B says yes.
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.
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:
Headeralg: HS256 (one shared secret)
Payloadsub, role, exp
Signaturefrom key + both
1/4Header: which signing method. Encoded as text, not hidden.Payload: the claims: who (sub 7), role DealerAdmin, expiry (exp). Anyone can read it.Signature: made from header + payload + the API's secret key.Change the role in the payload, and the signature no longer matches: rejected.
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.
PortalAngular
Auth APIlogin, renew
JWT checkstep on every request
Contractsthe API
POST login: email, password
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.
ReportedWhat is inside a JWT, and is it encrypted?
Header, payload, signature, each Base64URL-encoded and joined by dots. Decode Maria's payload and you see her id, name, role, dealer, issuer, audience and the times (iat = issued at, nbf = not before, invalid before this time; like exp, they are seconds since 1970). It is signed, not encrypted: anyone holding it can read it. So it carries ids and roles, never secrets or personal data like income. (Encrypted JWTs, JWE, exist but are rare for access tokens.)
Why a refresh token? Why not one access token for 7 days?
Because a signed access token can't be recalled. If a 7-day token leaks, it works for 7 days and nothing on the server can stop it. A 15-minute access token limits that damage to 15 minutes. The refresh token is long-lived but stored on the server (as a hash), so it can be revoked, is rotated on every use, and is only ever sent to one endpoint. How rotation and reuse detection work: .
How do you log a user out, or revoke a JWT?
You revoke what you can: the user's refresh tokens, so no new access tokens are issued. The current access token stays valid until it expires, which is why it's short. If you truly must kill an access token at once (a fired employee with admin rights), keep a small deny list of token ids (the jti claim, a unique id for one token) in a cache and check it in an event on the JWT handler (a hook where you can add your own check); that brings back a lookup on every call, so we use it only for that case.
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
ReportedWhat is inside a JWT, and is it encrypted?
Header, payload, signature, each Base64URL-encoded and joined by dots. Decode Maria's payload and you see her id, name, role, dealer, issuer, audience and the times (iat = issued at, nbf = not before, invalid before this time; like exp, they are seconds since 1970). It is signed, not encrypted: anyone holding it can read it. So it carries ids and roles, never secrets or personal data like income. (Encrypted JWTs, JWE, exist but are rare for access tokens.)
Why a refresh token? Why not one access token for 7 days?
Because a signed access token can't be recalled. If a 7-day token leaks, it works for 7 days and nothing on the server can stop it. A 15-minute access token limits that damage to 15 minutes. The refresh token is long-lived but stored on the server (as a hash), so it can be revoked, is rotated on every use, and is only ever sent to one endpoint. How rotation and reuse detection work: .
How do you log a user out, or revoke a JWT?
You revoke what you can: the user's refresh tokens, so no new access tokens are issued. The current access token stays valid until it expires, which is why it's short. If you truly must kill an access token at once (a fired employee with admin rights), keep a small deny list of token ids (the jti claim, a unique id for one token) in a cache and check it in an event on the JWT handler (a hook where you can add your own check); that brings back a lookup on every call, so we use it only for that case.
What does the API validate on every request?
The signature (with the key), the issuer (iss), the audience (aud) and the lifetime (exp,nbf, with ). These checks are on by default once you give the issuer, audience and key. The algorithm must fit the key; to pin it, set ValidAlgorithms = [SecurityAlgorithms.HmacSha256]. Turning any check off to make a test pass is a security hole, not a fix.
HS256 or RS256: which do you use?
HS256 uses one shared secret to sign and to check, so every service that checks tokens could also make them. It's fine when one API both issues and checks. RS256 signs with a private key that only the issuer has and checks with a public key that anyone may have. uses RS256 and publishes its public keys at a well-known address; in .NET you set options.Authority and the handler downloads and refreshes those keys itself. More: .
ReportedJWT or a session cookie?
A session cookie points to state on the server, which is simple and can be revoked instantly, but every server must share that state. A JWT carries the state, so any server can check it with no lookup, which suits APIs called by SPAs, mobile apps and other services. The costs of a JWT: you can't revoke it early, and it's bigger. For a server-rendered MVC site, cookies are still the simple choice.
JWT is a token format. OAuth 2.0 is a standard for an app to get an access token to call an API on a user's behalf. OpenID Connect sits on top of OAuth 2.0 and adds signing in: an ID token (a JWT) that says who the user is. speaks both; our own login on this card is a simpler, home-made issuer that only uses the format.
Where should the client keep the tokens?
That's mostly the Angular card. From the API side: we can send the refresh token as an ,,SameSite=Strict (sent only for requests from our own site) cookie, which page scripts can't read, so a cross-site scripting bug (an attacker's script running inside your page) can't steal it, and keep the access token only in memory. The .NET code then reads the refresh token from the cookie instead of the request body.
What happens if someone steals a token?
A stolen access token works until it expires: at most 15 minutes. A stolen refresh token works once; when the real user then refreshes with the same (now revoked) token, the API sees reuse and revokes all of that user's tokens, so both get signed out. HTTPS everywhere and never logging tokens keep them from leaking in the first place.
Production bugs, and how to tell them
Each one: what you see, why, the fix in code, and the line to say.
A security test found expired tokens still worked
Pen test (security test) finding NDP-PT-07 (medium):
Access token used 4 minutes after its
exp claim was accepted (HTTP 200).
Cause
A pen test (a paid security test where testers attack the system) found it. was left at its default, 5 minutes. The handler allows that much difference between servers' clocks, so a token whose exp passed 4 minutes ago still counts as valid. The 15-minute token was really a 20-minute token.
Fix
Set ClockSkew = TimeSpan.FromSeconds(30). Azure servers sync their clocks, so 30 seconds is plenty. A test now sends a token that expired 60 seconds ago and expects 401.
Diagram · step 2The default clock skew adds 5 minutes of leeway after the expiry.
access token
calls
valid 15 min
0 ms
Before · Program.cs · line 4 · 1 of 2 is never set, so it stays at 5 minutes: tokens are accepted until exp + 5 minutes.
Say it
The default clock skew is five minutes, so expired tokens were accepted for five more minutes. We set it to thirty seconds and test an expired token in .
Half the calls failed with 401 after scaling out to two instances
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
error_description="The signature key was
not found"
IDX10517: Signature validation failed.
Cause
A tutorial's start-up code made a new random signing key every time the app started. With one instance that worked. Scaled out to two, each instance had its own key: a token signed by instance A failed the signature check on instance B (the message says "key not found" because B's key does not match the one that signed the token), and the load balancer sent about half the calls to B.
Fix
One key for all instances, read from at start-up. The key is rotated on a schedule with a key id (kid, a label saying which key signed the token) so old and new keys overlap. A test starts two copies of the app and calls B with a token from A.
Diagram · step 1Before: Maria signs in on instance A. Her token is signed with A's key.
Dealer portalbrowser
Instance Akey A
Instance Bkey B
login
token signed with key A
Before · Program.cs · line 2 · 1 of 2A new random key every time an instance starts: A and B differ.
Say it
Each instance generated its own signing key, so a token from one failed on the other. The key now comes from , the same on every instance.
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
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 .
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.
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.
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.
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.
Dealer usersNavistar dealerships
Dealer portalAngular, in the browser
Dealer portal APIlogin, refresh, contracts
Azure SQLrefresh tokens
You + tech leadL3: our code
App Insightslogs, KQL
Azure DevOpsPR, tests, release
Feature flagreuse detection mode
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
Build this card's login and refresh endpoints (or dotnet new webapi and copy them), with a SQLite database.
Log in, then send two refreshes with the same refresh token: the second in a sleep 0.3 after the first.
With the Before code, the second gets 401 and the first one's new token is dead too.
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."
SourcesSourced: 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
Don't sayThe JWT payload is encrypted, so it's safe to put customer data in it.SayIt's only encoded. Anyone can read it. Put ids and roles in it, nothing secret.
Don't sayA JWT expires exactly at exp.SayThe handler allows , 5 minutes by default. Set it, for example to 30 seconds.
Don't sayLogout deletes the token on the server.SayThere's nothing on the server to delete for a JWT. Revoke the refresh tokens; the access token runs out on its own.
Don't sayStore the signing key in appsettings.json.Say, the same key on every instance, rotated with a key id. Whoever has the key can sign as anyone.
Traps
What should you say instead? Tap to check.
Don't sayThe JWT payload is encrypted, so it's safe to put customer data in it.
Say instead
It's only encoded. Anyone can read it. Put ids and roles in it, nothing secret.
Don't sayA JWT expires exactly at exp.
Say instead
The handler allows , 5 minutes by default. Set it, for example to 30 seconds.
Don't sayLogout deletes the token on the server.
Say instead
There's nothing on the server to delete for a JWT. Revoke the refresh tokens; the access token runs out on its own.
Don't sayStore the signing key in appsettings.json.
Say instead
, the same key on every instance, rotated with a key id. Whoever has the key can sign as anyone.
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 .
Which method adds the JWT handler, and which object holds the rules?
Which checks are on by default, and which values must you supply?
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.
RefreshToken.cs · line 3 · 1 of 2Each { get; set; } line is a column of the table.
Stuck? Hints
What goes in the access token, and what must never go in it?
Why is the refresh token random instead of a second JWT?
What do you store in the database: the refresh token, or something else?
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.