[Question] Decode JWT and put "sub" into a request header

I’m using the OPA adapter to manage AuthN and AuthZ. Some of my backend services need to know who is making a given request; for example, to populate a created_by column when a given user creates something.

I’m trying to figure out an elegant way of decoding the JWT and putting the “sub” field into a “user” header before the request gets sent to the actual backend service. This way, a given service would simply need to look at the “user” header rather than dealing with parsing the JWT.

Any ideas or recommendations on how this could be accomplished are appreciated.

1 Like

@liminwang Hi Limin, can you take a look?

Right now, I’m trying to do the same by using some EnvoyFilter.

With some LUA code, you can alter the request / response and so, extract data from token and then inject it inside custom header.

The problem right now is, if I’m not wrong, in this EnvoyFilter, you can’t import lua library, so you have to work from “scratch” with the JWT token.

If some of you have a better idea to do this, I’ll be glad to here it, thanks :star_struck:

I had a similar problem. I forked the opa adapter and modified it to allow returning arbitrary results from the rego evaluation. Envoy’s ext_auth protocol allows the endpoint to return a list of modfied/added headers. I added the result of the evaluation to that list. Envoy then added the headers to the upstream request for me.

Currently either Istio authorization or OPA adapter returns TRUE or FALSE result. It does not modify the headers. If this becomes a popular requirement, we may consider supporting it.

In the JWT case, the original JWT token is passed to the backend. The backend just needs to base64 decode the JWT and get the claim (no need to validate the signature if Istio JWT authentication is enabled). I think this is the only supported way currently. I hope it is not too much burden for the backend.

2 Likes

I think a generic JWT to header mechanism that is exposed as a policy would likely eventually need to exist to support simplifying workloads behind the sidecar. Something not specific to any single value (flexible key/value pair for mappings?). Header patterns for are common for proprietary identity proxies, which would also make adapting legacy applications simpler.

Our eventual goals on top of https://tiny.cc/istio-oidc-policy would also look to tie in with logic similar to this based on standard use cases we shared as part of http://tiny.cc/istio-iam-usecases.

1 Like

This indeed is a good use case. We will look into supporting this.

This rule worked for me. I am not sure about the handler section but I was able to add headers from the JWT payload this way.

apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: auth-headers
  namespace: istio-system
spec:
  match: source.labels["istio"] == "ingressgateway"
  actions:
  - handler: keyval.istio-system
    instances: [ keyval ]
  request_header_operations:
  - name: X-company-userId
    values:
    - request.auth.claims["sub"]
2 Likes

request_header_operations and response_header_operations can already resolve static values in the values clause without any handler… if they could also directly reference attributes, it would solve this an other use cases.

1 Like

Thanks! This works really well for ‘sub’. We put some custom claims in our JWTs and would like to extract them in the same way and add them to headers. So far my tests have been unsuccessful. I’m doing something like the above, but replacing ‘values’ with:

request.auth.claims["abc"]

Keeping two headers, one for sub and one for abc in the rule, sub comes back fine, but abc does not. I’m wondering if the request.auth construction only pull in certain well-known JWT claims, or if vendor-specific claims get added in a different way?

@Bryan_Absher i am also using the same setup but somehow the rule was not working any sugegstions ?

I got it working, i needed to enable global.disablePolicyChecks=false in my istio release
Thank you,

@jayasai470 Were you trying to get it working for just ‘sub’ or also for vendor-specific claims?

Hi rob, i was able to retrieve custom subs as well

Hi,

Are you able to use the header in a VirtualService for a routing decision?

I find that I am unable to effectively use them if they are added by a Rule.

apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: auth-headers
spec:
  request_header_operations:
  - name: X-company
    values:
    - request.auth.claims["org_human_identifier"]
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: echo-server-svc
spec:
  hosts:
  - echo-server.poc03.svc.cluster.local
  http:
  - match:
    - name: header
      headers:
        x-company:
          exact: westeros
    route:
    - destination:
        host: echo-server.poc03.svc.cluster.local
        subset: westeros
  - route:
    - destination:
        host: echo-server.poc03.svc.cluster.local
        subset: devtesting

I could not manage to make it work, tried global.disablePolicyChecks=true|false

istio version 1.6.0

Can anyone , post a sample code snippet to extract claims from jwt and pass it as a custom headers.
I am really struggling. The kind ‘Rule’ for istio is not working for me . Any help or lead will be much appreciated. @ademaria @rob

I don’t think you can do that out-of-the-box with the current CRDs in Istio.

What you could do is, use the outputPayloadToHeader field in the RequestAuthentication resource to pass the JWT to the header of your choice. For example:

apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: httpbin
  namespace: foo
spec:
  selector:
    matchLabels:
      app: httpbin
  jwtRules:
  - issuer: "issuer-foo"
    jwksUri: https://example.com/.well-known/jwks.json
   outputPayloadToHeader: "my-jwt-header"

Then, any upstream requests will have the “my-jwt-header” and you can parse it from there. If you want to make any authz decisions based on the claims from the JWT, you’d use the AuthorizationPolicy to do that. For example:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
...
  rules:
...
   - when:
    - key: request.auth.claims[iss]  # <-- extract any claim from JWT 
      values: ["https://accounts.google.com"]

Thanks a lot @peterj !

Two more questions I have.

  1. I have to pass 2 claims from my jwt payload . How can I pass multiple claims to header ?

  2. In my claims the name is ‘email’ but I want to pass it as ‘x-claim-email’ header. How can I do that ?

Any lead or help is much appreciated :blush:

Claims are not something Istio directly handles. You can add more claims to your JWT wherever you are generating the token.

There doesn’t seem to be anything in the RequestAuthentication resource that would allow you to pull out specific claims and add them as headers. You could put the whole payload as a header with the outputPayloadToHeader.

2 Likes