Skip to content

Latest commit

 

History

History
1385 lines (1132 loc) · 30.4 KB

File metadata and controls

1385 lines (1132 loc) · 30.4 KB
title Open Policy Agent (opa)
keywords
Apache APISIX
API Gateway
Plugin
Open Policy Agent
opa
description The opa plugin integrates with Open Policy Agent, enabling unified policy definition and enforcement for authorization in API operations.

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

Description

The opa Plugin supports the integration with Open Policy Agent (OPA), a unified policy engine and framework that helps define and enforce authorization policies. Authorization logic is defined in Rego and stored in OPA.

Once configured, the OPA engine will evaluate the client request to a protected Route to determine whether the request should have access to the Upstream resource based on the defined policies.

Attributes

Name Type Required Default Valid values Description
host string True Address of the OPA server.
policy string True Policy path to evaluate. The plugin appends this value to /v1/data/<policy>, so it must point to an OPA document/decision whose result is an object containing an allow field. The returned object can also include reason, headers, and status_code. For example, use a path such as rbac only if querying /v1/data/rbac returns an object with this structure, rather than a bare boolean result.
ssl_verify boolean False true If true, verify the OPA server's SSL certificate.
timeout integer False 3000 [1, 60000] Timeout for the HTTP call in milliseconds.
keepalive boolean False true If true, keep the connection alive for multiple requests.
keepalive_timeout integer False 60000 >= 1000 Idle time in milliseconds after which the connection is closed.
keepalive_pool integer False 5 >= 1 The number of idle connections.
with_route boolean False If true, send information of the current Route.
with_service boolean False If true, send information of the current Service.
with_consumer boolean False If true, send information of the current Consumer. Note that the Consumer information may include sensitive information such as the API key. Only set this option to true if you are sure it is safe to do so.
send_headers_upstream array[string] False >= 1 item List of header names to forward from the OPA response to the Upstream service when the request is allowed.

Data Definition

APISIX to OPA Service

The JSON below shows the data sent to the OPA service by APISIX:

{
    "type": "http",
    "request": {
        "scheme": "http",
        "path": "\/get",
        "headers": {
            "user-agent": "curl\/7.68.0",
            "accept": "*\/*",
            "host": "127.0.0.1:9080"
        },
        "query": {},
        "port": 9080,
        "method": "GET",
        "host": "127.0.0.1"
    },
    "var": {
        "timestamp": 1701234567,
        "server_addr": "127.0.0.1",
        "server_port": "9080",
        "remote_port": "port",
        "remote_addr": "ip address"
    },
    "route": {},
    "service": {},
    "consumer": {}
}

Each of these keys are explained below:

  • type indicates the request type (http or stream).
  • request is used when the type is http and contains the basic request information (URL, headers etc).
  • var contains the basic information about the requested connection (IP, port, request timestamp etc).
  • route, service and consumer contains the same data as stored in APISIX and are only sent if the opa Plugin is configured on these objects.

OPA Service to APISIX

The JSON below shows the response from the OPA service to APISIX:

{
    "result": {
        "allow": true,
        "reason": "test",
        "headers": {
            "an": "header"
        },
        "status_code": 401
    }
}

The keys in the response are explained below:

  • allow is indispensable and indicates whether the request is allowed to be forwarded through APISIX.
  • reason, headers, and status_code are optional and are only returned when you configure a custom response.

Examples

:::note

You can fetch the admin_key from conf/config.yaml and save to an environment variable with the following command:

admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

:::

Before proceeding, you should have a running OPA server. Start one using Docker or deploy it to Kubernetes:

<Tabs groupId="opa-setup" defaultValue="docker" values={[ {label: 'Docker', value: 'docker'}, {label: 'Kubernetes', value: 'kubernetes'} ]}>

docker run -d --name opa-server -p 8181:8181 openpolicyagent/opa:1.6.0 run --server --addr :8181 --log-level debug

To verify that the OPA server is installed and the port is exposed properly, run:

curl http://127.0.0.1:8181 | grep Version

You should see a response similar to the following:

Version: 1.6.0

Create a Deployment and Service for OPA in your cluster:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: aic
  name: opa
spec:
  replicas: 1
  selector:
    matchLabels:
      app: opa
  template:
    metadata:
      labels:
        app: opa
    spec:
      containers:
        - name: opa
          image: openpolicyagent/opa:1.6.0
          args:
            - run
            - --server
            - --addr=:8181
            - --log-level=debug
          ports:
            - containerPort: 8181
---
apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: opa
spec:
  selector:
    app: opa
  ports:
    - port: 8181
      targetPort: 8181

Apply the configuration to your cluster:

kubectl apply -f opa-server.yaml

Wait for the OPA pod to be ready. Once ready, the OPA server will be available within the cluster at http://opa.aic.svc.cluster.local:8181. To push policies to it from outside the cluster, set up a port-forward:

kubectl port-forward -n aic svc/opa 8181:8181 &

Implement a Basic Policy

The following example implements a basic authorization policy in OPA to allow only GET requests.

Create an OPA policy that only allows HTTP GET requests:

curl "http://127.0.0.1:8181/v1/policies/getonly" -X PUT  \
  -H "Content-Type: text/plain" \
  -d '
package getonly

default allow = false

allow if {
    input.request.method == "GET"
}'

Create a Route with the opa Plugin:

<Tabs groupId="api" defaultValue="admin-api" values={[ {label: 'Admin API', value: 'admin-api'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'aic'} ]}>

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "opa-route",
    "uri": "/anything",
    "plugins": {
      "opa": {
        "host": "http://127.0.0.1:8181",
        "policy": "getonly"
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Update host to your OPA server address. The policy is set to getonly.

services:
  - name: opa-service
    routes:
      - name: opa-route
        uris:
          - /anything
        plugins:
          opa:
            host: "http://127.0.0.1:8181"
            policy: getonly
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Update host to your OPA server address. The policy is set to getonly.

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: opa-plugin-config
spec:
  plugins:
    - name: opa
      config:
        host: "http://opa.aic.svc.cluster.local:8181"
        policy: getonly
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: opa-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /anything
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: opa-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f opa-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  ingressClassName: apisix
  externalNodes:
  - type: Domain
    name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  namespace: aic
  name: opa-route
spec:
  ingressClassName: apisix
  http:
    - name: opa-route
      match:
        paths:
          - /anything
      upstreams:
        - name: httpbin-external-domain
      plugins:
        - name: opa
          enable: true
          config:
            host: "http://opa.aic.svc.cluster.local:8181"
            policy: getonly

Apply the configuration to your cluster:

kubectl apply -f opa-ic.yaml

To verify the policy, send a GET request to the Route:

curl -i "http://127.0.0.1:9080/anything"

You should receive an HTTP/1.1 200 OK response.

Send another request to the Route using PUT:

curl -i "http://127.0.0.1:9080/anything" -X PUT

You should receive an HTTP/1.1 403 Forbidden response.

Understand Data Format

The following example helps you understand the data and the format APISIX pushes to OPA to support authorization logic writing. The example continues with the policy and the Route in the last example.

Now, update the Plugin on the previously created Route to include Route information:

<Tabs groupId="api" defaultValue="admin-api" values={[ {label: 'Admin API', value: 'admin-api'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'aic'} ]}>

curl "http://127.0.0.1:9180/apisix/admin/routes/opa-route" -X PATCH \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "plugins": {
      "opa": {
        "with_route": true
      }
    }
  }'

Update adc.yaml to add with_route: true:

services:
  - name: opa-service
    routes:
      - name: opa-route
        uris:
          - /anything
        plugins:
          opa:
            host: "http://127.0.0.1:8181"
            policy: getonly
            with_route: true
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

Update opa-ic.yaml to add with_route: true:

apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: opa-plugin-config
spec:
  plugins:
    - name: opa
      config:
        host: "http://opa.aic.svc.cluster.local:8181"
        policy: getonly
        with_route: true
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: opa-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /anything
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: opa-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the updated configuration to your cluster:

kubectl apply -f opa-ic.yaml

Update opa-ic.yaml to add with_route: true:

apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  ingressClassName: apisix
  externalNodes:
  - type: Domain
    name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  namespace: aic
  name: opa-route
spec:
  ingressClassName: apisix
  http:
    - name: opa-route
      match:
        paths:
          - /anything
      upstreams:
        - name: httpbin-external-domain
      plugins:
        - name: opa
          enable: true
          config:
            host: "http://opa.aic.svc.cluster.local:8181"
            policy: getonly
            with_route: true

Apply the updated configuration to your cluster:

kubectl apply -f opa-ic.yaml

Send a request to the Route:

curl -i "http://127.0.0.1:9080/anything"

In the OPA server log (with --log-level debug), the req_body will now include Route information in addition to the request and var fields.

Return Custom Response

The following example demonstrates how you can return a custom response code and message when the request is unauthorized.

Create an OPA policy that only allows HTTP GET requests and returns 302 with a custom message when the request is unauthorized:

curl "http://127.0.0.1:8181/v1/policies/customresp" -X PUT \
  -H "Content-Type: text/plain" \
  -d '
package customresp

default allow = false

allow if {
  input.request.method == "GET"
}

reason := "The resource has temporarily moved. Please follow the new URL." if {
  not allow
}

headers := {
  "Location": "http://example.com/auth"
} if {
  not allow
}

status_code := 302 if {
  not allow
}
'

Create a Route with the opa Plugin:

<Tabs groupId="api" defaultValue="admin-api" values={[ {label: 'Admin API', value: 'admin-api'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'aic'} ]}>

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "opa-route",
    "uri": "/anything",
    "plugins": {
      "opa": {
        "host": "http://127.0.0.1:8181",
        "policy": "customresp"
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'
services:
  - name: opa-service
    routes:
      - name: opa-route
        uris:
          - /anything
        plugins:
          opa:
            host: "http://127.0.0.1:8181"
            policy: customresp
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: opa-customresp-plugin-config
spec:
  plugins:
    - name: opa
      config:
        host: "http://opa.aic.svc.cluster.local:8181"
        policy: customresp
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: opa-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /anything
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: opa-customresp-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f opa-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  ingressClassName: apisix
  externalNodes:
  - type: Domain
    name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  namespace: aic
  name: opa-route
spec:
  ingressClassName: apisix
  http:
    - name: opa-route
      match:
        paths:
          - /anything
      upstreams:
        - name: httpbin-external-domain
      plugins:
        - name: opa
          enable: true
          config:
            host: "http://opa.aic.svc.cluster.local:8181"
            policy: customresp

Apply the configuration to your cluster:

kubectl apply -f opa-ic.yaml

Send a GET request to the Route:

curl -i "http://127.0.0.1:9080/anything"

You should receive an HTTP/1.1 200 OK response.

Send a POST request to the Route:

curl -i "http://127.0.0.1:9080/anything" -X POST

You should receive an HTTP/1.1 302 Moved Temporarily response:

HTTP/1.1 302 Moved Temporarily
...
Location: http://example.com/auth

The resource has temporarily moved. Please follow the new URL.

Implement RBAC

The following example demonstrates how to implement authentication and RBAC using the jwt-auth and opa Plugins. You will be implementing RBAC logics such that:

  • An user role can only read the Upstream resources.
  • An admin role can read and write the Upstream resources.

Create an OPA policy for RBAC of two example Consumers, where john has the user role and jane has the admin role:

curl "http://127.0.0.1:8181/v1/policies/rbac" -X PUT \
  -H "Content-Type: text/plain" \
  -d '
package rbac

# Assign roles to users
user_roles := {
  "john": ["user"],
  "jane": ["admin"]
}

# Map permissions to HTTP methods
permission_methods := {
  "read": "GET",
  "write": "POST"
}

# Assign role permissions
role_permissions := {
  "user": ["read"],
  "admin": ["read", "write"]
}

# Get JWT authorization token
bearer_token := t if {
  t := input.request.headers.authorization
}

# Decode the token to get role and permission
token := {"payload": payload} if {
  [_, payload, _] := io.jwt.decode(bearer_token)
}

# Normalize permission to a list
normalized_permissions := ps if {
  ps := token.payload.permission
  not is_string(ps)
}

normalized_permissions := [ps] if {
  ps := token.payload.permission
  is_string(ps)
}

# Implement RBAC logic
default allow = false

allow if {
  # Look up the list of roles for the user
  roles := user_roles[input.consumer.username]

  # For each role in that list
  r := roles[_]

  # Look up the permissions list for the role
  permissions := role_permissions[r]

  # For each permission
  p := permissions[_]

  # Check if the permission matches the request method
  permission_methods[p] == input.request.method

  # Check if the normalized permissions include the permission
  p in normalized_permissions
}
'

Create two Consumers john and jane in APISIX and configure their jwt-auth Credentials:

<Tabs groupId="api" defaultValue="admin-api" values={[ {label: 'Admin API', value: 'admin-api'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'aic'} ]}>

curl "http://127.0.0.1:9180/apisix/admin/consumers" \
  -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john"
}'
curl "http://127.0.0.1:9180/apisix/admin/consumers" \
  -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "jane"
}'

Configure the jwt-auth Credentials for the Consumers, using the default algorithm HS256:

curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cred-john-jwt-auth",
    "plugins": {
      "jwt-auth": {
        "key": "john-key",
        "secret": "john-hs256-secret-that-is-very-long"
      }
    }
  }'
curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cred-jane-jwt-auth",
    "plugins": {
      "jwt-auth": {
        "key": "jane-key",
        "secret": "jane-hs256-secret-that-is-very-long"
      }
    }
  }'
consumers:
  - username: john
    credentials:
      - name: cred-john-jwt-auth
        type: jwt-auth
        config:
          key: john-key
          secret: john-hs256-secret-that-is-very-long
  - username: jane
    credentials:
      - name: cred-jane-jwt-auth
        type: jwt-auth
        config:
          key: jane-key
          secret: jane-hs256-secret-that-is-very-long

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
  namespace: aic
  name: john
spec:
  gatewayRef:
    name: apisix
  credentials:
    - type: jwt-auth
      name: cred-john-jwt-auth
      config:
        key: john-key
        secret: john-hs256-secret-that-is-very-long
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
  namespace: aic
  name: jane
spec:
  gatewayRef:
    name: apisix
  credentials:
    - type: jwt-auth
      name: cred-jane-jwt-auth
      config:
        key: jane-key
        secret: jane-hs256-secret-that-is-very-long

Apply the configuration to your cluster:

kubectl apply -f opa-consumers-ic.yaml

When using the Ingress Controller, APISIX prefixes Consumer names with the Kubernetes namespace. For example, a Consumer named john in the aic namespace becomes aic_john. Update the OPA RBAC policy to use the prefixed names accordingly.

The ApisixConsumer CRD has a known issue where private_key is incorrectly required during the configuration. This issue will be addressed in a future release. At the moment, the example cannot be completed with APISIX CRDs.

Create a Route and configure the jwt-auth and opa Plugins:

<Tabs groupId="api" defaultValue="admin-api" values={[ {label: 'Admin API', value: 'admin-api'}, {label: 'ADC', value: 'adc'}, {label: 'Ingress Controller', value: 'aic'} ]}>

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "opa-route",
    "methods": ["GET", "POST"],
    "uris": ["/get","/post"],
    "plugins": {
      "jwt-auth": {},
      "opa": {
        "host": "http://127.0.0.1:8181",
        "policy": "rbac",
        "with_consumer": true
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Update adc.yaml to add the Route with jwt-auth and opa Plugins:

consumers:
  - username: john
    credentials:
      - name: cred-john-jwt-auth
        type: jwt-auth
        config:
          key: john-key
          secret: john-hs256-secret-that-is-very-long
  - username: jane
    credentials:
      - name: cred-jane-jwt-auth
        type: jwt-auth
        config:
          key: jane-key
          secret: jane-hs256-secret-that-is-very-long
services:
  - name: opa-service
    routes:
      - name: opa-route
        uris:
          - /get
          - /post
        methods:
          - GET
          - POST
        plugins:
          jwt-auth: {}
          opa:
            host: "http://127.0.0.1:8181"
            policy: rbac
            with_consumer: true
    upstream:
      type: roundrobin
      nodes:
        - host: httpbin.org
          port: 80
          weight: 1

Synchronize the configuration to the gateway:

adc sync -f adc.yaml

<Tabs groupId="k8s-api" defaultValue="gateway-api" values={[ {label: 'Gateway API', value: 'gateway-api'}, {label: 'APISIX Ingress Controller', value: 'apisix-ingress-controller'} ]}>

apiVersion: v1
kind: Service
metadata:
  namespace: aic
  name: httpbin-external-domain
spec:
  type: ExternalName
  externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
  namespace: aic
  name: opa-rbac-plugin-config
spec:
  plugins:
    - name: jwt-auth
      config:
        _meta:
          disable: false
    - name: opa
      config:
        host: "http://opa.aic.svc.cluster.local:8181"
        policy: rbac
        with_consumer: true
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: aic
  name: opa-rbac-route
spec:
  parentRefs:
    - name: apisix
  rules:
    - matches:
        - path:
            type: Exact
            value: /get
          method: GET
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: opa-rbac-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80
    - matches:
        - path:
            type: Exact
            value: /post
          method: POST
      filters:
        - type: ExtensionRef
          extensionRef:
            group: apisix.apache.org
            kind: PluginConfig
            name: opa-rbac-plugin-config
      backendRefs:
        - name: httpbin-external-domain
          port: 80

Apply the configuration to your cluster:

kubectl apply -f opa-route-ic.yaml

The ApisixConsumer CRD has a known issue where private_key is incorrectly required during the configuration. This issue will be addressed in a future release. At the moment, the example cannot be completed with APISIX CRDs.

Verify as john

To issue a JWT for john, you could use JWT.io's JWT encoder or other utilities. If you are using JWT.io's JWT encoder, do the following:

  • Fill in HS256 as the algorithm.
  • Update the secret in the Valid secret section to be john-hs256-secret-that-is-very-long.
  • Update payload with role user, permission read, and Consumer key john-key; as well as exp or nbf in UNIX timestamp.

Your payload should look similar to the following:

{
  "role": "user",
  "permission": "read",
  "key": "john-key",
  "nbf": 1729132271
}

Copy the generated JWT and save to a variable:

export john_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsInBlcm1pc3Npb24iOiJyZWFkIiwia2V5Ijoiam9obi1rZXkiLCJuYmYiOjE3MjkxMzIyNzF9.rAHMTQfnnGFnKYc3am_lpE9pZ9E8EaOT_NBQ5Ss8pk4

Send a GET request to the Route with the JWT of john:

curl -i "http://127.0.0.1:9080/get" -H "Authorization: Bearer ${john_jwt_token}"

You should receive an HTTP/1.1 200 OK response.

Send a POST request to the Route with the same JWT:

curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: Bearer ${john_jwt_token}"

You should receive an HTTP/1.1 403 Forbidden response.

Verify as jane

Similarly, to issue a JWT for jane, you could use JWT.io's JWT encoder or other utilities. If you are using JWT.io's JWT encoder, do the following:

  • Fill in HS256 as the algorithm.
  • Update the secret in the Valid secret section to be jane-hs256-secret-that-is-very-long.
  • Update payload with role admin, permission ["read","write"], and Consumer key jane-key; as well as exp or nbf in UNIX timestamp.

Your payload should look similar to the following:

{
  "role": "admin",
  "permission": ["read","write"],
  "key": "jane-key",
  "nbf": 1729132271
}

Copy the generated JWT and save to a variable:

export jane_jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW4iLCJwZXJtaXNzaW9uIjpbInJlYWQiLCJ3cml0ZSJdLCJrZXkiOiJqYW5lLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.meZ-AaGHUPwN_GvVOE3IkKuAJ1wqlCguaXf3gm3Ww8s

Send a GET request to the Route with the JWT of jane:

curl -i "http://127.0.0.1:9080/get" -H "Authorization: Bearer ${jane_jwt_token}"

You should receive an HTTP/1.1 200 OK response.

Send a POST request to the Route with the same JWT:

curl -i "http://127.0.0.1:9080/post" -X POST -H "Authorization: Bearer ${jane_jwt_token}"

You should also receive an HTTP/1.1 200 OK response.