Tutorials / Build a REST API with C# and .NET · Lesson 35 of 38
Concept
On this page
What a JWT Is
The shop has two kinds of people who use the screens. Staff add, edit and delete books. The manager does that too, and also reads the stock report, which shows what the stock is worth. Today anyone who knows the address can open both pages, and the owner wants the report kept for managers.
That raises a puzzle from HTTP and REST concepts: the server does not remember you between requests. So after you log in, how does the API know it is still you on the next request, and the one after that?
Our goal: understand how a logged-in user proves who they are on every request, with a small signed note called a JWT.
Here is the route:
- The two questions every protected request answers.
- A story first: the visitor badge in an office building.
- The same story on the web: a token, sent with every request.
- A real token, taken apart into its three pieces.
- Signed is not the same as secret: what never goes in a token.
- Why a token at all, and the other common way to stay logged in.
This is a read-along lesson. There is no code to write and nothing to run. The next three lessons build it: the login endpoint makes tokens, protect endpoints and roles checks them, and the login screen keeps one in the browser. Left out on purpose: logging in with a Google or Microsoft account, tokens that renew themselves, and encrypted tokens.
Two questions
Meet the two people we use for the rest of the course. alice is the manager. bob works on the shop floor, as staff. (The lower-case names are their user names.)
Every time bob or alice asks the API for something that is not public, the API needs two answers, in this order:
| Question | Its name | Example |
|---|---|---|
| Who are you? | Authentication | bob types his user name and password, and the API agrees that this is bob. |
| What are you allowed to do? | Authorization | bob asks for the stock report. The API knows it is bob, and says no: the report is for managers. |
The two words look alike, and people mix them up all the time. Keep the order in mind: authentication comes first, because you cannot decide what someone may do before you know who they are.
What bob may do comes from his role: a label such as Staff or Manager that groups people who may do the same things. The API does not list what bob may do; it lists what each role may do, and bob has one role.
The visitor badge
Before any web words, a story you have probably lived.
You visit an office building. At reception, you show your identity card once. The receptionist prints a badge for you. It says your name, your role and the time it stops being valid, and it carries a stamp that only reception can make. After that, nobody asks for your identity card again. Each door in the building has a guard who reads your badge, checks the stamp and the time, and decides.
Follow bob through four doors, one at a time:
- bob at the stockroom. The badge says Staff, the stamp is right, it is 10:00. The guard lets him in.
- bob at the accounts office. The stamp is right, so the guard knows this is bob. But the badge says Staff, and this door is for managers. The guard says: "I know who you are, and you may not come in."
- bob changes his badge. He crosses out Staff and writes Manager with a pen. The stamp was made for the badge as it was printed, so it no longer matches. The guard does not trust a single word on this badge, and turns him away.
- bob at 19:00. The badge has run out. Even the stockroom guard sends him back to reception for a new one.
Three things make this work. The guards never phone reception: everything they need is on the badge. Anyone in the corridor can read the badge, but nobody except reception can make a stamp that matches. And the badge has an end time, so a lost badge stops working by itself.
The same story on the web
Every part of the story has a twin in our system:
| In the building | In the bookshop |
|---|---|
| Reception | The API's login endpoint, which checks a user name and password |
| Identity card | The user name and password |
| Badge | A token: a short piece of text the API hands back after login |
| The stamp | The token's signature (explained below) |
| A door guard | The API, checking the token on every request |
| The end time | The token's expiry time |
| A door for managers only | An endpoint such as GET /reports/stock |
The token our API uses is a JWT, short for JSON Web Token. People say it as "jot". It is a standard format, so every language and framework can make and check one.
Here is bob's day as requests and responses. Time runs down the picture:
bob sends his password once. After that, every request carries the token instead, in a header (one of the extra lines of a request, from the HTTP lesson). The header has a fixed name and form:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQi...
The name of the header is Authorization (a slightly confusing name, since it carries the answer to "who are you?"). Its value is the word Bearer, a space, and the token. Bearer means "whoever carries this": the API does not check who sends the token, only that the token is good. So a stolen token works for the thief until it runs out, exactly like a lost badge. Treat a token like a password.
This answers the puzzle from the start. The server still remembers nothing between requests. It does not need to: the token travels with every request, and it holds everything the API needs to know about bob.
A real token, taken apart
This is a real token. Our API made it for bob when we ran the login endpoint of the next lesson, and the text is one long line:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJCb29rU2hvcCIsImlzcyI6IkJvb2tTaG9wLkFwaSIsImV4cCI6MTc5MzQ0Mzk0MywibmFtZSI6ImJvYiIsInJvbGUiOiJTdGFmZiIsImlhdCI6MTc5MzQ0MDM0MywibmJmIjoxNzkzNDQwMzQzfQ._dlpMAmhQU7RATNTOXovXe4H_gXos0snljjUYR9pyuw
It looks like noise, but look for the two dots. They cut it into three pieces:
The first two pieces are JSON, written in Base64url: a way to write any text using only letters, digits, - and _, so it travels safely inside a header. It is not a code or a secret. Anyone can turn it back into the JSON. Your browser can do it: in the browser's developer tools (the Console tab from the first web page), atob turns Base64 back into text. We gave it the first piece:
atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9')
'{"alg":"HS256","typ":"JWT"}'
And then the second piece:
atob('eyJhdWQiOiJCb29rU2hvcCIsImlzcyI6IkJvb2tTaG9wLkFwaSIsImV4cCI6MTc5MzQ0Mzk0MywibmFtZSI6ImJvYiIsInJvbGUiOiJTdGFmZiIsImlhdCI6MTc5MzQ0MDM0MywibmJmIjoxNzkzNDQwMzQzfQ')
'{"aud":"BookShop","iss":"BookShop.Api","exp":1793443943,"name":"bob","role":"Staff","iat":1793440343,"nbf":1793440343}'
(atob only understands plain Base64. It worked on these two pieces because they happen to contain no - or _. Websites such as jwt.io decode any token and show the pieces side by side. That is fine for a practice token like this one; never paste a real user's token into a website.)
The header
{"alg":"HS256","typ":"JWT"} says two things: this is a JWT, and the stamp was made with HS256, a common way of signing with one secret key. We come back to it under the signature.
The payload
The payload holds the facts. Each fact is called a claim: something the token claims about its holder, which the API believes because of the stamp. Here is each one in bob's token:
| Claim | Value | Meaning |
|---|---|---|
name |
bob |
Who this is. We chose this claim. |
role |
Staff |
What bob is. We chose this claim too. |
iss |
BookShop.Api |
The issuer: who made the token. |
aud |
BookShop |
The audience: who the token is for. A token made for another system is refused. |
iat |
1793440343 |
Issued at: when it was made. |
nbf |
1793440343 |
Not before: it is not valid before this moment. |
exp |
1793443943 |
Expires: it is not valid after this moment. |
The short names (iss, aud, exp and the others) come from the JWT standard, so that every library reads them the same way. name and role are ours.
The three times are counted in seconds since the start of 1 January 1970, a common way for computers to write a moment. They are in UTC, the world's reference time, so they mean the same moment in every country. 1793440343 is 31 October 2026 at 09:52:23 UTC, when we ran the login. The expiry is 3600 seconds later: 1793443943 − 1793440343 = 3600, which is one hour. After 10:52:23, the token is the badge at 19:00.
The signature
The third piece is the stamp. The API made it from three things: the header, the payload, and a secret key, a long password that only the API knows and that never leaves the server. HS256 mixes them into a short code. The same three inputs always give the same code. Change one letter of the payload, and the code comes out completely different.
When a request arrives, the API does the work again: it takes the header and the payload that came with the request, mixes them with its own key, and compares the result with the signature that came along. Same code: nobody has touched the token since the API made it, so the API believes every claim. A different code: someone changed something, like bob with his pen, and the API refuses the whole token.
bob cannot make a matching stamp for a payload that says Manager, because he does not have the key. That is why the key must stay secret, on the server, and must never be written into a web page.
Signed is not secret
The signature stops anyone from changing a token. It does not stop anyone from reading it. You decoded bob's token above without any key. A signed token is like the badge on bob's shirt: anyone in the corridor can read it.
So the rule: put in a token only what the holder may see, and what the API needs on every request. Encrypted tokens exist (they hide the payload too). We do not need one: our token carries only a name and a role.
Why a token, and the other way
The honest question: why hand out a token at all? The server could remember who has logged in.
That is the other common way, and it is older. At login, the server makes a random number, called a session id, and keeps a list: "session 4821 is bob". It sends the number to the browser in a cookie: a small value the browser stores and sends back by itself with every request to the same site. On each request, the server looks the number up in its list.
Both ways work, and both are used everywhere. What differs:
| Session | Token (JWT) | |
|---|---|---|
| Who knows that it is bob | The server, in its list | The token itself |
| What the server keeps | A list of everyone logged in | Only its secret key |
| Log someone out right now | Easy: delete the line from the list | Hard: the token stays good until it expires |
| Who can use it | Mostly browsers, which handle cookies by themselves | Any program that can send a header |
A realistic future. Today our only client is the web pages. A shop like this soon gets more: a phone app for the shop floor, a nightly job that updates stock, a partner's system that reads the catalogue. Each of them can send an Authorization header with a token, and the API checks it the same way for all of them, with no list to share between servers. That is why tokens are a common choice for APIs.
The cost, said honestly. A token cannot be taken back. If bob leaves the shop at 10:00, his token still works until 10:52. Short expiry times keep that window small; ours is one hour. Bigger systems add tokens that renew themselves, which this course leaves out.
Not over-engineering. Our pages and our API share one address, and the only client is a browser. For exactly that case, a session cookie is a fine choice, and often a simpler one: ASP.NET supports both. We use a JWT because it is widely used by APIs, it works for every kind of client, and you can see everything it holds. That is a choice, not the only right answer.
Recap
- Authentication answers "who are you?". Authorization answers "what may you do?", and it comes second. What a user may do comes from their role.
- A JWT is a token the API hands out at login. Like a visitor badge, it says who you are, what you are, and until when, and it carries a stamp.
- The browser sends it with every request, in the
Authorization: Bearer ...header. So the server can stay stateless: the token carries what it needs. - A token has three pieces, joined by dots: the header (how it is signed), the payload (the claims, such as
name,roleandexp) and the signature. - The signature is made from the header, the payload and a secret key that only the API has. Change one letter and the API refuses the whole token.
- Signed is not secret. Anyone can read a token, so it never holds a password or private data. And whoever holds it can use it, so keep it safe.
- A session cookie is the other common way. A token suits APIs with many kinds of clients, and it cannot be taken back before it expires, so keep its life short.
Questions to think about
Each question has its answer folded away underneath. Guess first, then open it to check.
- What is the difference between authentication and authorization?
Show answer
Authentication finds out who you are, for example from a user name and password. Authorization decides what you may do, for example whether you may see the report. Authorization needs the answer to authentication first.
- The server remembers nothing between requests. How does it know that a request comes from bob?
Show answer
Every request carries bob's token in the Authorization header. The token says who he is, and the signature proves the API made it.
- bob decodes his token, changes
StafftoManagerand sends it. What happens, and why?
Show answer
The API refuses the token. The signature was made for the original payload with a key bob does not have, so it no longer matches the changed one.
- Why must a token never hold a password?
Show answer
A token is signed, not encrypted. Anyone who has it can decode the payload and read every claim.
- Where does a token come from in the first place?
Show answer
From a login endpoint: the user sends a user name and password once, and the API answers with a token. The next lesson builds it.
Next lesson: the login endpoint, where the API checks a password and hands out a token.