Imagine this: you want a third-party application — say, a service that arranges your photos nicely — to peek into your photo album in the cloud. The most direct way is to give that application the login and password to your cloud. But that is a bad idea: the application will see your password, which means it can do absolutely everything, not just look at photos. And if the password leaks somewhere, you will have to change it everywhere at once. You need a way to grant limited access that is easy to revoke, without ever showing your password. That is exactly what OAuth2 solves. And OIDC adds an answer to a separate question — "so who exactly is logged in right now."
Next we will go through everything in order and without rushing: why you must not hand over the password, who is actually involved in this scheme (there are only four of them), and what the difference is between "grant access" and "find out who you are." If you never confuse these two things, everything else in Keycloak becomes clear.
Why handing over the password is a bad idea
Let's start with the problem all of this was invented to solve. Suppose you did give the application your cloud password. What's wrong with that?
- The application sees your password. And a password is "everything at once": with it you can not only view photos but also delete them, change settings, read your messages if there are any. There is no boundary whatsoever.
- Access cannot be narrowed. You wanted to allow "just view the photos," but a password cannot do that. A password is either full access or nothing.
- Access cannot be taken away from a single application. Suppose you changed your mind and no longer trust this service. The only way to "turn it off" is to change the password. But then all the other applications and devices you gave the same password to will break as well.
You can see that the problem is not a specific application but the tool itself: the password is too "coarse." It can neither limit rights nor be revoked individually.
OAuth2 is a protocol for delegating access. "To delegate" here means: to allow someone to act on your behalf, but strictly within agreed limits. Instead of a password, the application receives a token — a temporary "pass" with limited rights. This pass can be issued narrow (read photos only), it expires on its own after a short time, and it can be revoked at any moment without touching the password.
Remember the main idea from the very start: OAuth2 is about access. It answers the question "what is this application allowed to do," not the question "who is this person."
The four OAuth2 roles through a building-pass analogy
Every OAuth2 scenario always involves four parties. They are easy to remember through an everyday analogy: picture an office business center where people are let in by passes.
- The resource owner is you, the visitor. The resource is, for example, a meeting room whose access you and only you decide on. No one but the owner has the right to permit entry.
- The authorization server is the pass desk at the entrance. You show your ID, the desk verifies who you are, and issues a pass card. It is the desk that decides what you are allowed to do, and it is the desk that prints the passes. In a real system this role is played by Keycloak.
- The client is the application that needs access. In the analogy — a courier you invited to carry a box into the meeting room. The courier is not the owner and on their own has no entry anywhere; they act only because you allowed it, and only within the limits you set.
- The resource server is the turnstile (or door lock) at the entrance to the room itself. The turnstile does not know you personally and does not need to. Its job is simpler: a pass is presented — it checks whether it is genuine and whether it grants the right to pass through exactly here. Fits — let through; doesn't — no. In a real system this is your API with the data.
Now let's tie it together in one sentence. The resource owner (you) allows the pass desk (the authorization server) to issue the courier (the client) a pass (a token), and the turnstile (the resource server) lets the courier through with that pass. The password (your ID) is seen only by the pass desk — the courier never gets it.
Here's the diagram: four roles and who passes what to whom. The arrows are "who is contacted" and "what is handed over."
Why it matters to keep the roles separate: they are responsible for different things and must not be mixed. The pass desk issues access, the turnstile verifies it — these are two different machines, and they don't even have to know each other personally. The courier uses access but has no right to draw one up for themselves. And only the owner decides who is allowed at all. When we get to tokens later, keep this picture in mind — each token is addressed to a specific role.
Access token: the pass you carry to the API
Let's return to the turnstile. For it to let you through, you need a pass. In OAuth2 this pass is called an access token — it is that very "key card."
The problem it solves: the API cannot ask for a password on every request — that is both insecure and inconvenient. Instead, the client gets an access token once from the authorization server and then attaches it to every call to the API. Usually — in the HTTP Authorization header with the Bearer prefix:
GET /albums/42/photos
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
The word Bearer literally means "the one who carries": whoever brought this token is who the API serves (which is why you must not scatter the token around). A few properties of the access token that a beginner should understand:
- It is short-lived. Usually a few minutes (often 5–15). This is by design: if the token leaks, the window for abuse is small — it expires on its own quickly.
- It carries rights (scope). Encoded inside the token is which actions are allowed (more on scope below). The turnstile looks exactly at this.
- The resource server verifies it itself. If the token is a signed JWT, the turnstile checks the signature locally, without pinging the authorization server on every request. (There is another format too — more on that at the very end.)
And the most important thing about the recipient: the access token is intended for the resource server — that is, the API. It is a pass "to do something," not an identity document. You cannot reliably learn from it who is in front of you; you can only decide from it whether to let the request through.
Refresh token: so you don't have to log in again every 10 minutes
Here an inconvenience arises. If the access token lives only 10 minutes, then in theory the user would have to re-enter their login and password every 10 minutes. It is impossible to use an application like that.
The solution is the refresh token. This is a second pass, and it has a different role. It lives long (hours, days, sometimes longer) and is kept carefully on the client side. When the short access token expires, the client quietly, without the user's involvement, goes to the authorization server and exchanges the refresh token for a new access token:
POST /token
grant_type=refresh_token
refresh_token=<long-token>
Why have such a "short + long" pair at all, rather than a single token:
- the access token constantly "travels" the network — it flies in every request to the API. That's why it is short: even if it is intercepted, it quickly becomes worthless.
- the refresh token barely moves — it is needed only at the moment of renewal and is stored more securely. That's why it is allowed to live long.
- the refresh token can be revoked at the authorization server. After revocation the client can no longer obtain new access tokens — and access gradually closes on its own.
The recipient is different too: the refresh token is intended for the authorization server. Only the pass desk knows how to accept it and issue a new pass in exchange. You don't go to the API (the turnstile) with a refresh token — it wouldn't be understood there.
ID token and OIDC: the answer to "who are you"
And now — the important fork in the road, the very reason OIDC exists. Until now we have talked only about access. But notice: neither the access token nor the refresh token answers the question "who is this person." The turnstile doesn't need to know you personally — a valid pass is enough for it.
Yet the application often needs precisely to know the user: to show "Hello, Anna," insert an avatar, tie data to an account. From pure OAuth2 you cannot reliably extract this — the access token is formally even opaque to the client; it is not meant to be "cracked open" and read like a form.
This is exactly the gap that OIDC (OpenID Connect) fills — a thin layer on top of OAuth2 that adds authentication: a way to learn and prove who exactly logged in. It does this through a third token.
The ID token is always a JWT (signed JSON) that describes the user. Inside is a set of fields called claims:
{
"iss": "https://keycloak.example.com/realms/myrealm",
"sub": "a1b2c3d4-...",
"aud": "my-web-app",
"exp": 1735690000,
"iat": 1735689700,
"name": "Anna Ivanova",
"email": "anna@example.com"
}
What the key fields mean:
iss(issuer) — who issued the token, that is, the address of the authorization server. It is used to verify that the token is really from your Keycloak and not from an outsider.sub(subject) — a stable unique identifier of the user. It, and not the email, is the real "key" for a person: an email can be changed, butsubstays the same.aud(audience) — which application this token is intended for. If the token was issued not to you, you must not accept it.exp/iat— when the token expires and when it was issued.
The most common beginner misconception is confusing which token goes where. Remember the rule in one sentence: the id_token is intended for the client (the application — to find out who logged in), and the access_token — for the resource server (the API — to decide whether to let the request through). From this follow two "don'ts":
- do not send the id_token to the API instead of the access_token — these are different passes for different doors;
- do not try to read the contents of the access_token as a user profile — for identity there is the id_token.
OAuth2 and OIDC: so what is the difference
These two words are constantly confused because they work together. Let's separate them once and for all.
- OAuth2 is about authorization, that is, about access. "This application is allowed to read my photos." The tool is
access_token. The question it answers: what you are allowed to do. - OIDC is about authentication, that is, about identity. "Anna is logged in right now, and this is confirmed by the authorization server." The tool is
id_token. The question it answers: who you are.
OIDC does not replace OAuth2 — it is built on top of it. The token-getting flow is the same; it's just that in the response, besides the access token, an id_token with information about the user is also added. When you see a "Sign in with..." button on some site — behind it almost always stands OIDC, not "bare" OAuth2.
Here's the diagram: the same tokens, but you can see which token is about what and addressed to whom.
Simple mnemonic: access → access_token → API → OAuth2; identity → id_token → application → OIDC.
Scopes: how to narrow down "what exactly is allowed"
One important detail remains. "Grant access" is too coarse: the photo service needs only to read albums, not delete them and certainly not poke into payment data. How do you express such narrowness?
For this there is scope — the requested extent of rights, written as short labels. On login the client says what it needs, and the user confirms it (often on a consent screen):
scope=openid profile email albums:read
Let's break it down piece by piece:
openid— a special scope. Its very presence turns on OIDC, that is, it asks the authorization server to "also issue an id_token." Noopenidmeans it is just OAuth2 without identity information, and you will not get an id_token.profile,email— standard OIDC field sets: name, email and the like will land in the id_token.albums:read— an example of an application-level right for your API: "I allow reading albums."
The resulting access token carries the agreed scopes, and the resource server decides from them what to allow. The main principle here is least privilege: request exactly the scopes that are actually needed, and not one more. The narrower the pass, the less damage it can do if it leaks.
How it all comes together in a single login
In reality all four roles and three tokens meet in a single login scenario — it is called Authorization Code Flow and is used in most web and mobile applications. Here we give only the general picture so the roles fall into place:
- The application (client) sends the user to the authorization server, to the login page.
- The user enters their login and password right there, on the authorization server — the application does not see them.
- The authorization server returns to the application a one-time authorization code.
- The application on its own backend exchanges this code via
/tokenfor tokens: access_token, refresh_token and (if there was scopeopenid) id_token.
The key idea: the password is entered only on the authorization server, and the application receives not the password but a short-lived code, which it immediately swaps for tokens. For public clients (mobile applications, single-page applications) this exchange is additionally protected by the PKCE mechanism, so that an intercepted code cannot be used. Details — in a separate article about Authorization Code Flow, link below.
A small note about the access token format
Sometimes beginners hear "opaque token" and decide it is some kind of fourth token. That is not so. Opaque and JWT are two formats of the same access token, not different tokens:
- JWT format (by-value) — the token itself carries signed data inside, and the resource server verifies it locally against a public key (via JWKS). The authorization server is not pinged in the process.
- Opaque format (by-reference) — the token looks from the outside like a meaningless reference string, and to find out whether it is valid and what rights it has, the resource server asks the authorization server (introspection).
This is a separate axis — about how the access token is verified — not "one more kind of token." There are still four roles, and by meaning three tokens: access_token, refresh_token, id_token.
In short
- OAuth2 gives an application limited and revocable access without handing over the password — it is about access (delegated authorization).
- Four roles: resource owner (the user), client (the application), authorization server (issues tokens, e.g. Keycloak), resource server (the API, verifies tokens).
- access_token — a short-lived pass to the API, flies in the
Authorization: Bearerheader, addressed to the resource server. - refresh_token — long-lived, swapped for a new access_token without the user's involvement via
/token, addressed to the authorization server. - id_token — always a JWT about the user's identity, addressed to the client; appears only with OIDC and scope
openid. It is not sent to the API. - OIDC — a layer on top of OAuth2 about authentication ("who you are"); OAuth2 itself is about authorization ("what is allowed").
- scope limits the extent of rights;
openidturns on OIDC,profile/emailadd fields about the user. - opaque and JWT are two formats of the access_token (verification by reference or locally via JWKS), not a separate token.
What to read next
- Authorization Code Flow in detail — why the code is needed, the steps of the flow, and PKCE.
- Tokens and common mistakes — what to send where, lifetimes, and frequent token slip-ups.
- What Keycloak is — what authorization server role it plays in this scheme.
- Integration with Spring Security — how the API verifies the access_token on the resource server side.