Security: OAuth 2.0, OIDC & Zero Trust
In Hands-on 3 we secured the store with a homemade scheme: auth-service checks the password, signs a JSON Web Token (JWT) with an HMAC secret and returns it in a cookie; on every request the gateway asks auth-service to resolve the token and forwards the caller's id in the id-account header. That works for one web app with one login form. Now the store wants "Sign in with Google", a mobile app, a nightly job that calls order-service with no user behind it, and β one day β a vulnerable library lets an attacker run code inside the product-service pod. The homemade scheme has no answer to any of these.
JWT is a transport format β it carries claims such as "this is account 42". It says nothing about how the user proved their identity, who may issue tokens, how a third-party app gets access without seeing the password, or why one service should trust a call from another. OAuth 2.0 and OpenID Connect are the standards that fill these gaps; Zero Trust is the architecture that stops trusting the network itself: distrust everything, verify always.
Learning objectives
By the end of this class you should be able to:
- Distinguish authentication from authorization and point out which component of the store performs each one.
- Explain the four OAuth 2.0 roles and choose the right grant (Authorization Code + PKCE, Client Credentials, Device Code) for a given client.
- Compare access, refresh and ID tokens, and explain what OpenID Connect adds on top of OAuth 2.0.
- Configure a gateway to validate JWTs locally with a JWKS, and compare it with the remote validation (
/auth/solve) used in Hands-on 3. - Apply Zero Trust controls β mTLS, NetworkPolicy, least privilege β to service-to-service traffic in Kubernetes.
- Choose a secrets-management approach and explain why a base64 Kubernetes Secret is not encryption.
Prerequisite
This class builds on the JWT concepts from Hands-on 3. Make sure you are comfortable with the JWT structure (header.payload.signature), the sub, exp and iss claims, and how the gateway validates tokens before reading this β see Hands-on 3 β JWT and Hands-on 3 β Authorization.
Slides
Click the slides and use β β to move through them; F goes full screen and P opens the presenter view, with the notes. Open the slides in a new tab
Roadmap
- Overview (this page) β authentication vs. authorization, what Hands-on 3 built, what JWT alone cannot do, and the map of security layers of the store.
- OAuth 2.0 & OpenID Connect β roles, tokens and scopes, grant types (Authorization Code + PKCE, Client Credentials), OIDC and the ID Token, and how the gateway validates tokens (JWKS vs. introspection).
- Zero Trust, mTLS & Secrets β perimeter vs. Zero Trust, mutual TLS and workload identity, Kubernetes NetworkPolicy, and secrets management.
Authentication vs. authorization
Two questions are asked on every request, and they are often confused:
| Authentication (authN) | Authorization (authZ) | |
|---|---|---|
| Question | Who are you? | What are you allowed to do? |
| Input | a credential: password, certificate, token | the authenticated identity + a policy (roles, scopes, ownership) |
| Failure | 401 Unauthorized (no or invalid credential) | 403 Forbidden (valid identity, not allowed) |
| In the store | auth-service checks email + password at POST /auth/login; the gateway checks the token on each request | order-service checks that order 123 belongs to the id-account that asked for it |
A few terms used throughout the class:
- Principal β the authenticated entity: a user (
ada@store.com) or a workload (order-service). - Credential β what a principal presents to prove its identity (a password, a private key, a token).
- Token β a string issued after authentication that the principal presents instead of the credential. A bearer token grants access to whoever holds it, like cash: steal it and you can spend it.
- Claim β a statement inside a token, such as
"sub": "42"or"scope": "orders:read".
Authorization is not done once at the gateway
The gateway can say "this is account 42". Only order-service knows whether order 123 belongs to account 42. Forgetting this check β GET /orders/123 returns any order to any logged-in user β is Broken Object Level Authorization, the number-one risk in the OWASP API Security Top 10 (2023)1.
What Hands-on 3 built
The flow below is the one you implemented: the token is a reference that only auth-service can read, and the gateway asks for it on every call.
sequenceDiagram
autonumber
actor B as Browser
participant GW as gateway-service
participant AU as auth-service
participant AC as account-service
B->>GW: POST /auth/login {email, password}
GW->>AU: forward (open route)
AU->>AU: check password, sign JWT (HMAC secret)
AU-->>B: Set-Cookie: __store_jwt_token
B->>GW: GET /accounts/me + cookie
GW->>AU: POST /auth/solve {token}
AU-->>GW: {idAccount}
GW->>AC: GET /accounts/me + id-account header
AC-->>B: 200 OK It is a sound design for a single application, and it has three properties worth naming:
- Symmetric signature (HMAC, e.g. HS256) β the same secret signs and verifies. Only
auth-serviceholdsJWT_SECRET_KEY, so only it can validate tokens; that is why the gateway must call it. - Remote validation β one extra network hop per request, but
auth-servicecould refuse a token immediately (for example, after logout). - Trust in the network β
account-servicebelieves anyid-accountheader, because only the gateway can reach it inside the Docker network.
What JWT alone cannot do
| Problem | Homemade JWT (Hands-on 3) | Standard answer | Where |
|---|---|---|---|
| Third-party login ("Sign in with Google") | β no way to delegate | Authorization Code + PKCE flow | OAuth |
| Service-to-service calls with no user | β no standard; services reuse a user token or a shared password | Client Credentials grant | OAuth |
| Let other services verify tokens themselves | β HMAC secret would have to be copied to every service | asymmetric keys published as a JWKS | OAuth |
| Revoke a token before it expires | only with a remote check on every call | short-lived access tokens + refresh tokens; introspection | OAuth |
| Standard user identity for the client app | β ad-hoc claims | OIDC ID Token + UserInfo endpoint | OAuth |
| A compromised pod calling other services | β the internal network is trusted | mTLS, NetworkPolicy, authorization policies | Zero Trust |
The security layers of the store
No single control protects a system. Defense in depth stacks independent layers, so that an attacker who defeats one still faces the next. The diagram follows a request from the Internet down to the data:
flowchart LR
U(["Client"]) -->|"HTTPS (TLS)"| GW["Gateway<br>validates token"]
IdP["Identity Provider<br>(auth-service / Keycloak)"] -->|"issues tokens"| U
GW -->|"identity header<br>(mTLS inside)"| SVC["order-service<br>checks ownership"]
SVC -->|"allowed by<br>NetworkPolicy"| PS["product-service"]
SVC -->|"short-lived<br>DB credential"| DB[("Postgres")] | Layer | Threat | Control | Where in the course |
|---|---|---|---|
| Edge | eavesdropping, abuse | TLS, rate limiting | Hands-on 2 β Gateway, Hands-on 7 β Nginx |
| Identity | stolen or forged credentials | OAuth 2.0 / OIDC, short-lived tokens | OAuth 2.0 & OIDC, Hands-on 3 |
| Request | user reads someone else's data | authorization in each service (ownership, roles, scopes) | this page |
| Service-to-service | lateral movement from a compromised pod | mTLS, NetworkPolicy, authorization policies | Zero Trust, Hands-on 5 |
| Data | leaked passwords and keys | secrets manager, encryption at rest | Zero Trust β Secrets |
Common mistakes
- Security only at the gateway. If any service is reachable without passing through the gateway (a
NodePort, a debug port, a misconfigured Ingress), anyone can sendid-account: <any id>and impersonate any user. - Rolling your own crypto or protocol. Hashing, signing and token flows are subtle; use a vetted library (JJWT, Spring Security) and a standard flow.
- Long-lived bearer tokens. Hands-on 3 issues 24-hour tokens; a stolen one is valid for a day. Short access tokens plus refresh tokens shrink that window.
Key takeaways
- Authentication answers who, authorization answers what; the gateway authenticates, but each service must still authorize access to its own objects.
- A JWT is only a format. OAuth 2.0 defines how tokens are obtained and delegated; OIDC adds a standard identity (ID Token).
- Hands-on 3 uses a symmetric key and remote validation; standard deployments use asymmetric keys and a JWKS so any service can validate locally.
- Zero Trust removes the assumption that the internal network is safe: every call carries a verifiable identity and is checked against a policy.
- Security is layered: TLS at the edge, tokens for users, mTLS and NetworkPolicy between services, a secrets manager for credentials.
-
OWASP. API1:2023 β Broken Object Level Authorization. OWASP API Security Top 10, 2023.Β β©