Skip to content

JWT

Istio and JWT authentication

I’ve been looking at a workflow which allow the JWT authentication in Istio to be used for both UI and API requests. The basic idea is to make use of Istio’s JWT authentication mechanism to control initial access and then use an authorization policy to control access to specific services.

The diagram below shows the basic workflow of how this will work. It has been simplified by removing things like refresh tokens or authorization policies.

Flow chart showing JWT flow

The basic idea is that once the basic auth is done to a 3rd party service such as Azure AD/Entra ID or Google Workspace, a signed token is generated and stored in a cookie. This cookie value is then used for further auth requests. For APIs, the cookie value can be used directly and passed to any API calls. For UIs, because Istio can’t currently extract tokens from, an Envoy Filter is used. This looks for a specific cookie and copies the value to the authentication header before forwarding the request to Istio’s authentication handler.

If the token is valid (signed by a known private key and hasn’t expired), traffic is allowed to pass (subject to authorization policies) or rejected. On unauthorised requests, APIs just respond as unauthorised where as the UI redirects to the /auth page.

Create a Signed JWT and validate it in Istio using a JWK

This guide shows how to create a public/private key pair and how to use these to create a JWK and a signed JWT and then validate a request to a service running in Kubernetes where Istio is running.

Generate a Public/Private Key Pair

For this you’ll need openssl installed. If you have Chocolatey installed, choco install openssl -y will do the job.

Run the following commands:

ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
# Don't add passphrase
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub

This produces a .key file containing the private key and a .key.pub file containing the public key.

Create JWK from Public Key

Go to the JWK Creator site and paste in the contents of your public key. For purpose, choose Signing and for the algorithm, choose RS256.

Manifests

The following will secure all workloads where they have a label of type and a value of api so they must have a JWT and it must be valid.

apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: "validate-jwt"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      type: api
  jwtRules:
  - issuer: "testing@secure.istio.io"
    jwks: |
      {
        "keys": [
          <YOUR_JWK_GOES_HERE>
        ]
      }

---

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: "deny-requests-without-jwt"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      type: api
  action: DENY
  rules:
  - from:
    - source:
        notRequestPrincipals: ["*"]

Replace <YOUR_JWK_GOES_HERE> with your JWK created in the previous step. Make sure your indentation doesn’t go to less than the existing { below the jwks: | line.

Create a Test JWT

The following PowerShell script will create a JWT that will last 3 hours and print it to the screen.

Clear-Host

# Install-Module jwtPS -force

Import-Module -Name jwtPS

$encryption = [jwtTypes+encryption]::SHA256
$algorithm = [jwtTypes+algorithm]::HMAC
$alg = [jwtTypes+cryptographyType]::new($algorithm, $encryption)

$key = "jwtRS256.key"
# The content must be joined otherwise you would have a string array.
$keyContent = (Get-Content -Path $key) -join ""
$payload = @{
    aud = "jwtPS"        
    iss = "testing@secure.istio.io"
    sub = "testing@secure.istio.io"
    nbf = 0
    groups = @(
      "group1",
      "group2"    
    )
    exp = ([System.DateTimeOffset]::Now.AddHours(3)).ToUnixTimeSeconds()
    iat = ([System.DateTimeOffset]::Now).ToUnixTimeSeconds()
    jti = [guid]::NewGuid()
}
$encryption = [jwtTypes+encryption]::SHA256
$algorithm = [jwtTypes+algorithm]::RSA
$alg = [jwtTypes+cryptographyType]::new($algorithm, $encryption)
$jwt = New-JWT -Payload $payload -Algorithm $alg -Secret $keyContent

Write-Host $jwt

Note that you will likely need to run the line Install-Module jwtPS -force in an elevated prompt.

Testing

To test this, you will need a service you can point at in your cluster with a label of type set to api. Don’t forget to update URLs as needed.

$TOKEN = jwt_from_previous_script
curl https://example.com/api/v1/test
curl --header "Authorization: Bearer $TOKEN" https://example.com/api/v1/test

In the above examples, the first request should return 401 and the second request should return 200.