CodeGym /Kursy /Spring Security /JWT in a stateless API

JWT in a stateless API

Spring Security
Poziom 22, Lekcja 0
Dostępny

1. JWT in one sentence: what it is and what it is not

If you have seen JWT for the first time, it looks roughly like a Wi‑Fi password that someone rewrote from memory: lots of characters, dots, and incomprehensible magic. But the shortest and most honest view of JWT sounds like this: JWT is a compact string that carries a small set of data (claims) between requests and is protected by a signature against unnoticed tampering. That is all. No more, no less.

As soon as the server stops storing session and remembering the user between requests, a new engineering question appears: what will the client bring instead of server-side memory so that on every request we can rebuild Authentication from scratch. JWT appears exactly here.

Problems begin when JWT starts being treated as a “new security model” or a “replacement for the whole server.” JWT is not Spring Security and not even “authorization.” It does not decide for you whether the user may publish a draft. It does not replace your @PreAuthorize rules, does not know about ownership, and cannot “on its own” distinguish 401 from 403. It only helps carry what the server needs to know in order to make those decisions.

It is very useful to keep a simple comparison table in mind — not as an exam crib sheet, but as an antidote to two common illusions.

What it is What it is about What it gives What it does not give
JWT as a format Token structure and a set of fields A convenient portable “data card” Automatic security “just because a token exists”
JWT signature Data integrity Protection against unnoticed payload tampering Payload secrecy (it is readable)
Application security model Access rules and life cycle Who has access and where, how errors are handled Cannot be “replaced” by a single token string

It is tempting to joke here, but the joke is already in how often JWT gets appointed the “king of all problems.” JWT is like a passport. A passport helps identify a person and carries some context. But a passport by itself does not guard a museum, close doors, or decide whether you may enter the vault with paintings. The museum decides that with rules, security guards, and turnstiles. JWT in our application is such a “document,” not a “guard.”

2. Stateless API and “passing memory” between requests

In a session-based world, everything was fairly comfortable. The user logged in once, the server stored the needed security context in the session, the client carries a cookie, and after that requests look like “a continuation of the conversation.” A stateless world is more like “document check at every entrance.” The server does not store “your card” between requests, so the client must bring proof of who they are every time.

And here it is easy to confuse two things: credentials and token. Credentials are what the user proves their identity with at login (for example, a password). A token is what the system issues after successful authentication so the client can then confirm their identity without sending the password on every request. It is like a gym: you show your documents at the reception (credentials), they give you a wristband (token), and then you enter with the wristband instead of waving your passport over the turnstile.

JWT usually plays the role of such a token. It carries the “minimally sufficient” information about the user (and sometimes about their basic access context) so the server can restore Authentication for the duration of a specific request.

At the same time, it is important to state right away: JWT appears not because we wanted a trendy technology, but because a stateless architecture requires carrying auth context between requests without a server-side session. JWT is one of the formats that makes this convenient because it is compact, text-based, standardized, and fits well into HTTP.

From the perspective of our project, Secure Content Platform API, this means a simple thing: when the client starts calling, for example, /api/me or /api/drafts/{id}, the server can no longer “remember” the user by session. It has to restore who you are based on what you bring in the request. JWT is one way to bring such context.

3. Signed token: integrity versus secrecy

The most harmful misconception about JWT sounds roughly like this: “If I have a JWT, then the data inside is hidden.” No. A JWT in the course’s typical scenario is signed, not encrypted. And signing and encryption solve different problems.

A signature answers the question: “Could someone have changed the data inside the token without anyone noticing?” This is about integrity. If you received a token, checked the signature, and it is correct, you can trust that the payload was not changed after issuance.

But a signature does not answer the question: “Can someone read the contents?” In a normal JWT, the payload is visible. It can be decoded. And that is fine. JWT was designed as a format that is convenient to transport, not as a safe.

That is why it is useful to think of JWT as a transparent container with a seal. The container is transparent — the payload is readable. The seal is there — tampering becomes visible after signature verification. From this comes a simple engineering rule: you must not put sensitive data into a JWT, because anyone who gets the token will be able to see it.

And here another adult thought appears: the security of JWT is built not on “token secrecy,” but on the fact that the token cannot be forged unnoticed if the signature is checked correctly. This is a fundamentally different style of thinking compared with “let’s just hide the data and nobody will guess.”

4. Base64URL: token readability

At first, JWT is annoying because it looks like a chaotic string. In reality, the first two parts of the token are just ordinary JSON, packed into Base64URL so the string is convenient to send over HTTP and URL. Base64URL is encoding, not an invisibility cloak.

For this point, one fact is enough: the payload is readable. If you put email, displayName, or something else sensitive there, it did not “stay somewhere on the backend” — it traveled together with the token to the client and into any intermediate layer where that token ended up.

That already is enough to avoid confusing convenient packaging with real protection.

5. JWT as a data contract: a minimum set of fields

JWT is convenient because it can carry data. JWT is dangerous because it can carry too much data. A beginner usually falls into the trap: “Since this is a token, let’s put everything we might need into it. So we do not have to go to the DB later.” That sounds like an optimization, but more often it turns into fragility, leaks, and stale snapshots of reality.

To keep yourself in check, it is useful to think of JWT as a data contract, not as “a bag where you can throw everything.” A contract means you make a decision in advance: which fields are actually needed by the application to restore the current user and the basic access context, and which ones are merely convenient but harmful.

In our project, the minimal contract usually comes down to three things:

— who the user is (sub and/or a stable userId);
— what compact access context they have, for example roles;
— until when this token lives (exp).

That is enough to restore Authentication and make a basic access decision. Anything that is not needed on every request right now is usually better read from the server.

This is exactly how it is easier not to confuse a token with a user profile: a JWT should carry the minimum without which a stateless request cannot be processed, not the entire biography, settings, and sensitive fields.

6. JWT in the security model: the overall flow

To make JWT stop being just a format and become a clear piece of architecture, it helps to see the overall request picture. But here it is enough to stay at the “who does what” level, without classes and filters. Then the role of the token itself becomes visible, not just the eventual implementation.

sequenceDiagram
    participant C as Client
    participant A as "API (Secure Content Platform)"

    C->>A: "(1) Login with credentials (username/password)"
    A-->>C: "(2) Response: access token (JWT)"
    C->>A: "(3) Request to /api/me with the token"
    A->>A: "(4) Checks the token signature and time"
    A->>A: "(5) Extracts subject/roles and makes an access decision"
    A-->>C: "(6) JSON response or security error (401/403)"

At this level, several things are already visible that fit well with what we have learned so far.

First, a token is not credentials. Credentials are needed at step 1, and the token appears after 2. Second, JWT helps exactly in the stateless part between requests 3: each time, the server gets not a cookie session, but a token. Third, the payload can always be read, but the data in it can be trusted only after signature and time validation.

And finally, JWT does not cancel your business constraints. If a user has the USER role, it does not turn into “can publish.” If a user has userId=42, that does not mean a draft with authorId=99 suddenly became “theirs.” Owner-based rules remain owner-based rules; only instead of “pulling the id from the session” we “take the id from the token” (or from the restored Authentication, which will be built based on the token).

If you keep this picture in mind, it will be easier to discuss any specific details later. Conversely, if you start learning JWT as a set of magical library calls, you will quickly end up in a state of “it seems to work, but I do not know why.”

7. Typical mistakes when first getting acquainted with JWT

There is almost no “complicated math” in JWT at the developer level, but there are many mental-model traps. And that is normal: the brain likes simplification. It is just that in security, simplifications sometimes work like a self-destruct button, only without the pretty animation.

Mistake #1: thinking that Base64URL “hides” the payload.
When you see a long string, the hand wants to say, “Well, it is encoded, so it must be hidden.” Base64URL is just a way to pack JSON into a safe string. If you can decode the payload with one line of code, then anyone who has the token can decode it too.

Mistake #2: confusing “signature” and “encryption.”
A signature makes forgery hard, but reading easy. Because of this, the token is often compared to a “seal,” and that is a good analogy. If you put extra data into the payload, the signature will not save you: the extra data will be visible to everyone who sees the token.

Mistake #3: putting everything into the token “for later.”
People quickly turn JWT into a mini database: profile, settings, biography, the favorite color of the “Save” button. Then it starts living its own life: tokens get bloated, structures change, stale fields appear, and the client carries megabytes in headers. JWT wins precisely through minimalism: the less data there is, the easier the maintenance and the lower the leak risk.

Mistake #4: putting sensitive fields there (even “not quite passwords”).
passwordHash, internal security flags, values that are painful to lose or dangerous to show — none of this should be in a readable payload. “But it is a hash!” is not an argument. It is a sensitive value, and you do not want to see it in someone else’s logs, in a proxy, in DevTools, or in a screenshot that someone attached to a ticket.

Mistake #5: thinking that JWT automatically solves authorization.
JWT is a format. It does not replace your URL rules and @PreAuthorize. It does not do the ownership check for you. If your project has the rule “only the author may edit the draft,” it will remain the rule “only the author may edit the draft,” even if the token contains the word USER in large letters and in a pretty font.

1
Zadanie
Spring Security, poziom 22, lekcja 0
Niedostępne
Dekodowanie ladunku Base64URL
Dekodowanie ladunku Base64URL
1
Zadanie
Spring Security, poziom 22, lekcja 0
Niedostępne
Przegląd zestawu pól dla JWT payload
Przegląd zestawu pól dla JWT payload
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION