Add JWT request authentication to your charm¶
This guide explains how to add JWT request authentication to your charm using the istio-request-auth interface library. This relation lets your charm tell the Istio ingress gateway which JWT issuers to trust and how to map JWT claims onto request headers, so that authenticated requests reach your workload with identity information it can use.
What is the istio-request-auth relation for?¶
The istio-ingress-k8s charm wraps an Istio Kubernetes Gateway. Istio can natively validate a JSON Web Token (JWT) carried on a request against a trusted issuer using a RequestAuthentication resource. When a request carries a valid JWT, Istio validates it at the gateway and can copy claims from the token (such as email or sub) into request headers before forwarding the request to your workload.
This is distinct from, but complementary to, the forward-auth relation used with oauth2-proxy. forward-auth handles the interactive browser login flow (external authorization), while istio-request-auth handles native JWT validation and claim-to-header mapping. The two work together: a browser request is authenticated by oauth2-proxy, which injects a JWT, and Istio then validates that JWT and maps its claims to headers.
The motivating use case is an application that needs a user identity in a specific header. For example, the Kubeflow dashboard expects the authenticated user’s email in a kubeflow-userid header. Only your charm knows which claims it needs and which headers to map them to, so your charm publishes those mappings to the gateway over the istio-request-auth relation. The gateway then creates the RequestAuthentication resource on your behalf.
For more on how authorization works in a charmed service mesh, see Traffic authorization.
Add the required relation to charmcraft.yaml¶
Add the istio-request-auth relation to your charm’s charmcraft.yaml:
requires:
istio-request-auth:
interface: istio_request_auth
limit: 1
description: |
Publish JWT authentication rules to the Istio ingress gateway. The gateway
creates a RequestAuthentication resource from these rules to validate JWTs
and map token claims to request headers.
Add the library dependency¶
The istio-request-auth interface library is distributed as a Python package. Add it to your charm’s pyproject.toml:
charmlibs-interfaces-istio-request-auth
Use IstioRequestAuthRequirer in your charm¶
Instantiate the requirer in your charm’s __init__ and publish your JWT rules whenever your configuration or relations change.
Instantiate the requirer¶
from charmlibs.interfaces.istio_request_auth import (
ClaimToHeader,
FromHeader,
IstioRequestAuthRequirer,
JWTRule,
)
class MyCharm(CharmBase):
def __init__(self, *args):
super().__init__(*args)
self.request_auth = IstioRequestAuthRequirer(self, relation_name="istio-request-auth")
self.framework.observe(
self.on["istio-request-auth"].relation_changed, self._publish_jwt_rules
)
self.framework.observe(self.on.config_changed, self._publish_jwt_rules)
Define and publish JWT rules¶
Each JWTRule describes one issuer to trust and how to handle tokens from it. Call publish_data with the rules you want the gateway to enforce:
def _publish_jwt_rules(self, _):
self.request_auth.publish_data(
[
JWTRule(
issuer="https://accounts.example.com",
jwks_uri="https://accounts.example.com/jwks",
forward_original_token=True,
claim_to_headers=[
ClaimToHeader(header="kubeflow-userid", claim="email"),
],
from_headers=[
FromHeader(name="Authorization", prefix="Bearer "),
],
)
]
)
The fields of a JWTRule mirror the Istio JWTRule entry:
issuer: the issuer URL that the JWT must be issued by (required).jwks_uri: the JSON Web Key Set endpoint used to validate the token signature.audiences: an optional list of audiences the token must be intended for.forward_original_token: whether to keep the original token on the request forwarded to your workload.claim_to_headers: a list ofClaimToHeadermappings, each copying a JWTclaiminto the namedheader.from_headers: a list ofFromHeaderlocations describing where to extract the JWT from, each with a headernameand optionalprefix.
Note
publish_data only writes to the relation databag when the unit is the leader. You can call it unconditionally; the library skips the write on non-leader units.
Integrate your charm with the ingress¶
Once your charm supports the relation, deploy it alongside istio-ingress-k8s and integrate the two:
juju integrate my-charm:istio-request-auth ingress:istio-request-auth
When the relation is established and your charm has published valid JWT rules, the ingress charm creates two Kubernetes resources in its own namespace, both targeting its gateway:
a
RequestAuthenticationresource (namedrequest-auth-<your-app>-<ingress-app>) built from your published rules, anda
DENYAuthorizationPolicy(nameddeny-without-jwt-<ingress-app>) that rejects any request without a validated JWT principal, ensuring fail-closed behavior.
When forward-auth is also active on the same gateway, the DENY policy is scoped to requests carrying a Bearer token so that non-Bearer requests continue to flow through the external authorization stack.
Understand the gateway-wide scope¶
Both resources target the entire gateway, not just the routes belonging to your charm. The RequestAuthentication and DENY AuthorizationPolicy both use a targetRefs entry of kind: Gateway, so JWT validation, claim-to-header mapping, and the fail-closed enforcement apply to every route exposed by that gateway. This has important consequences when more than one application shares the same istio-ingress-k8s instance.
Important
Enabling istio-request-auth affects all traffic through the gateway, including applications that never integrated over the relation. Plan your gateway topology accordingly: if some applications must not require a JWT, give them a separate gateway.
Multiple charms on the same gateway¶
The istio-request-auth relation does not limit the number of related applications, so several charms can integrate with the same gateway. When they do:
The ingress charm creates one
RequestAuthenticationresource per related application (each namedrequest-auth-<their-app>-<ingress-app>), built from that application’s published rules.Istio merges the
jwtRulesfrom everyRequestAuthenticationthat selects the same gateway. The gateway therefore trusts the union of all related applications’ issuers. Because validation is gateway-scoped rather than per-route, a token issued for one application is also considered valid on requests destined for another application on the same gateway.The ingress charm creates a single
DENYAuthorizationPolicyfor the whole gateway. As soon as any application enablesistio-request-auth, every request through the gateway must carry a validated JWT principal (or, whenforward-authis active, everyBearer-token request). Applications sharing the gateway are subject to this even if they did not opt in.If a related application publishes malformed or empty rules, no
RequestAuthenticationis created for it, but the gateway-wideDENYpolicy is still applied. This is the fail-closed behavior: a misconfigured application cannot leave the gateway open, but it can cause the gateway to reject traffic until valid rules are published or the relation is removed.
If you need applications to trust different issuers in isolation, or you do not want one application’s authentication requirements imposed on others, deploy them behind separate gateways.
Verify the resources¶
After the charms settle to active/idle, you can inspect the resources the gateway created in its model’s namespace:
kubectl get requestauthentication -n <ingress-model>
kubectl get authorizationpolicy -n <ingress-model>
You should see the request-auth-<your-app>-<ingress-app> and deny-without-jwt-<ingress-app> resources. Inspect the RequestAuthentication to confirm your issuer and claim-to-header mappings were applied:
kubectl get requestauthentication request-auth-<your-app>-<ingress-app> -n <ingress-model> -o yaml