An Istio RequestAuthentication resource is applied to a workload with a jwtRules entry for your identity provider, yet requests carrying no token at all still reach the application. Why, and what makes the endpoint actually require a valid token?
answer
- it validates, it does not require
- absence of a credential is not a failure
- two resources, two different codes
- the requirement lives in the other resource
- a wildcard means any authenticated user
basics
~20 sRequestAuthentication only defines how to validate a token if one is present; a request with no token is not rejected. To require one, add an AuthorizationPolicy whose rule matches source.requestPrincipals with a wildcard, so unauthenticated requests fail to match any grant.
solid answer
~40 s`RequestAuthentication` is a validation rule, not a gate. It says: if a request carries a token from this issuer, verify its signature against the configured key set and its issuer and audience, and reject it with 401 if that fails. A request with **no** token satisfies the rule vacuously and is passed through with no end-user identity attached. Requiring authentication is the authorization layer's job: you add an `AuthorizationPolicy` with `action: ALLOW` and a rule whose `from.source.requestPrincipals` is `["*"]`, meaning "any successfully authenticated end user". Because the first ALLOW policy makes the workload default-deny, a tokenless request now matches nothing and is rejected with 403. The split is deliberate — validation is stated once per issuer, while the decision about which endpoints demand a user, and which claims they demand, is expressed per route.
code
yaml · 28 linesapiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
name: idp
namespace: payments
spec:
selector:
matchLabels:
app: api
jwtRules:
- issuer: "https://idp.example.com"
jwksUri: "https://idp.example.com/.well-known/jwks.json"
audiences: ["payments-api"]
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: require-user
namespace: payments
spec:
selector:
matchLabels:
app: api
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["*"]go deeper
Remember the split: one resource says how to validate a token, a separate one says a token is required. Applying only the first leaves anonymous requests flowing through.
Explain the three outcomes of validation — valid, invalid giving 401, absent giving pass-through — and write the ALLOW policy with a wildcard request principal that makes a token mandatory.
Distinguish peer identity from end-user identity as separate rule axes, combine them in one rule, and diagnose from the status code which of the two layers rejected a request.
Decide how much authorization belongs at the mesh boundary versus in application code, and weigh the dependency you take on the identity provider's key set being reachable from the control plane.
## Two resources, two jobs Istio separates *authentication* — deciding whether a presented credential is genuine — from *authorization* — deciding whether the request may proceed. `RequestAuthentication` does only the first, and only for end-user tokens: ```yaml apiVersion: security.istio.io/v1 kind: RequestAuthentication metadata: name: idp namespace: payments spec: selector: matchLabels: app: api jwtRules: - issuer: "https://idp.example.com" jwksUri: "https://idp.example.com/.well-known/jwks.json" audiences: ["payments-api"] ``` This produces exactly three outcomes for an inbound request: - **A valid token** — the proxy records an end-user identity and, where configured, the token's claims become available to policy. - **A token that fails validation** — bad signature, wrong issuer, expired, wrong audience — rejected with **401**. - **No token at all** — allowed through with no identity. This is the case people do not expect, and it is intentional: it lets one resource cover an endpoint that serves both anonymous and authenticated traffic. ## Making a token mandatory ```yaml apiVersion: security.istio.io/v1 kind: AuthorizationPolicy metadata: name: require-user namespace: payments spec: selector: matchLabels: app: api action: ALLOW rules: - from: - source: requestPrincipals: ["*"] ``` `requestPrincipals` matches the end-user identity derived from a validated token, formed by joining the issuer and the subject claim. The wildcard means "any authenticated user", and naming a specific value pins it to one issuer and subject. Because this is the first ALLOW policy selecting the workload, the workload flips to default-deny, and a tokenless request now matches no rule and gets 403. Note which code appears when: **401 comes from failed validation, 403 from failed authorization.** A tokenless request therefore fails with 403, not 401 — surprising the first time, and a useful diagnostic once you know it. ## Peer identity and end-user identity are different axes This is the distinction the leaf is really about. `source.principals` matches the *calling workload*, taken from its mesh certificate. `source.requestPrincipals` matches the *end user*, taken from a validated token. They answer different questions — which service is calling, and on whose behalf — and a single rule can require both: ```yaml rules: - from: - source: principals: ["cluster.local/ns/web/sa/frontend"] requestPrincipals: ["https://idp.example.com/*"] to: - operation: paths: ["/v1/accounts/*"] ``` That reads: the request must arrive over mutual TLS from the frontend's identity *and* carry a valid token from that issuer. Neither alone is sufficient. Expressing this is the practical reason the two mechanisms exist side by side rather than being collapsed. ## Claim-based rules Beyond presence, individual claims can gate access through `when` conditions keyed on the request's authenticated claims — a group, a role, a tenant. That lets coarse decisions live at the mesh boundary while the application keeps the fine-grained ones, and it means a claim used for access control is validated at a layer the application cannot accidentally skip. ## Operational edges - **Key set availability.** Validation needs the issuer's public keys. Pointing at a remote key set makes the control plane's ability to fetch and refresh it part of your request path's dependency graph; inlining the keys removes that dependency but makes rotation your problem. - **Multiple issuers.** Several `jwtRules` entries can coexist, which is how a migration between identity providers is run — but during that window "any authenticated user" spans both, so pin the issuer in the rule if that matters. - **The token is still forwarded.** By default the original token continues to the application unless configured otherwise, so an application that also validates it is not broken by adding the mesh layer — which makes rolling this out ahead of removing in-app validation safe. - **Selector mismatch is the silent failure.** A `RequestAuthentication` whose selector matches nothing produces no error and no effect. If tokens seem never to be validated, confirm the selector matches the workload's labels before suspecting the issuer configuration.
- A request with an expired token and a request with no token both fail. Do they fail the same way?No. The expired token fails validation in RequestAuthentication and is rejected with 401. The tokenless request passes validation vacuously, then fails to match any ALLOW rule requiring a request principal, and is rejected with 403 by the authorization layer. Two layers, two status codes — useful when diagnosing which resource is misconfigured.
- How do source.principals and source.requestPrincipals differ, and when would a rule use both?`principals` is the calling workload's identity from its mesh certificate; `requestPrincipals` is the end-user identity from a validated token. Using both expresses 'this specific service, acting for an authenticated user' — for instance letting only the frontend reach an account endpoint, and only when it carries a real user token. Neither alone conveys that.
- You apply a RequestAuthentication and tokens are still never validated, with no errors anywhere. What do you check first?The selector. A RequestAuthentication whose selector matches no workload is accepted by the API server and silently does nothing. Compare its matchLabels against the pod labels, and confirm the resource is in the workload's own namespace — before spending time on the issuer or key-set configuration.
saying these in an interview costs you the question
- Believes RequestAuthentication rejects requests lacking a token
- Expects a tokenless request to be rejected with 401
- Confuses requestPrincipals with the calling workload's identity
- Thinks validated claims cannot be used in authorization rules
- Assumes the mesh strips the token before the application sees it