# hmac-auth

The `hmac-auth` plugin supports HMAC (Hash-based Message Authentication Code) authentication as a mechanism to ensure the integrity of requests, preventing them from being modified during transmissions. To use the plugin, you would configure HMAC secret keys on [consumers](https://docs.api7.ai/apisix/key-concepts/consumers.md) and enable the plugin on routes or services.

When a consumer is successfully authenticated, APISIX adds additional headers, such as `X-Consumer-Username`, `X-Credential-Identifier`, and other consumer custom headers if configured, to the request, before proxying it to the upstream service. The upstream service will be able to differentiate between consumers and implement additional logic as needed. If any of these values is not available, the corresponding header will not be added.

About X-Consumer-Username

When consumers are configured using the Ingress Controller, the consumer name is generated in the format `namespace_consumername`. As a result, the `X-Consumer-Username` header will also follow this format instead of just `consumername`.

## Implementation[​](#implementation "Direct link to Implementation")

Once enabled, the plugin verifies the HMAC signature in the request's `Authorization` header and checks that incoming requests are from trusted sources. Specifically, when APISIX receives an HMAC-signed request, the key ID is extracted from the `Authorization` header. APISIX then retrieves the corresponding consumer configuration, including the secret key. If the key ID is valid and exists, APISIX generates an HMAC signature using the request's `Date` header and the secret key. If this generated signature matches the signature provided in the `Authorization` header, the request is authenticated and forwarded to upstream services.

The plugin implementation is based on [draft-cavage-http-signatures](https://www.ietf.org/archive/id/draft-cavage-http-signatures-12.txt).

## Examples[​](#examples "Direct link to Examples")

The examples below demonstrate how you can work with the `hmac-auth` plugin for different scenarios.

### Implement HMAC Authentication on a Route[​](#implement-hmac-authentication-on-a-route "Direct link to Implement HMAC Authentication on a Route")

The following example demonstrates how to implement HMAC authentication on a route. You will also attach a consumer custom ID to authenticated requests in the `X-Consumer-Custom-Id` header, which can be used to implement additional logics as needed.

* Admin API
* ADC
* Ingress Controller

Create a consumer `john` with a custom ID label:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "username": "john",

    "labels": {

      "custom_id": "495aec6a"

    }

  }'
```

Create `hmac-auth` credential for the consumer:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "id": "cred-john-hmac-auth",

    "plugins": {

      "hmac-auth": {

        "key_id": "john-key",

        "secret_key": "john-secret-key"

      }

    }

  }'
```

Create a route with the `hmac-auth` plugin using its default configurations:

```
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "id": "hmac-auth-route",

    "uri": "/get",

    "methods": ["GET"],

    "plugins": {

      "hmac-auth": {}

    },

    "upstream": {

      "type": "roundrobin",

      "nodes": {

        "httpbin.org:80": 1

      }

    }

  }'
```

Create a consumer with `hmac-auth` credential and a route with `hmac-auth` plugin configured as such:

adc.yaml

```
consumers:

  - username: john

    labels:

      custom_id: "495aec6a"

    credentials:

      - name: hmac-auth

        type: hmac-auth

        config:

          key_id: john-key

          secret_key: john-secret-key

services:

  - name: hmac-auth-service

    routes:

      - name: hmac-auth-route

        uris:

          - /get

        methods:

          - GET

        plugins:

          hmac-auth: {}

    upstream:

      type: roundrobin

      nodes:

        - host: httpbin.org

          port: 80

          weight: 1
```

Preview the changes and confirm that they only affect resources for this guide:

```
adc diff -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

Synchronize the configuration to the gateway:

```
adc sync -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

Create a consumer with `hmac-auth` credential and a route with `hmac-auth` plugin configured as such:

* Gateway API
* APISIX CRD

hmac-auth-ic.yaml

```
apiVersion: apisix.apache.org/v1alpha1

kind: Consumer

metadata:

  namespace: aic

  name: john

  labels:

    custom_id: "495aec6a"

spec:

  gatewayRef:

    name: apisix

  credentials:

    - type: hmac-auth

      name: primary-cred

      config:

        key_id: john-key

        secret_key: john-secret-key

---

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: hmac-auth-plugin-config

spec:

  plugins:

    - name: hmac-auth

      config:

        _meta:

          disable: false

---

apiVersion: gateway.networking.k8s.io/v1

kind: HTTPRoute

metadata:

  namespace: aic

  name: hmac-auth-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: hmac-auth-plugin-config

      backendRefs:

        - name: httpbin-external-domain

          port: 80
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

hmac-auth-ic.yaml

```
apiVersion: apisix.apache.org/v2

kind: ApisixConsumer

metadata:

  namespace: aic

  name: john

  labels:

    custom_id: "495aec6a"

spec:

  ingressClassName: apisix

  authParameter:

    hmacAuth:

      value:

        key_id: john-key

        secret_key: john-secret-key

---

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: hmac-auth-route

spec:

  ingressClassName: apisix

  http:

    - name: hmac-auth-route

      match:

        paths:

          - /get

        methods:

          - GET

      upstreams:

      - name: httpbin-external-domain

      plugins:

      - name: hmac-auth

        enable: true
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

Generate a signature. You can use the below Python snippet or other stack of your choice:

hmac-sig-header-gen.py

```
import hmac

import hashlib

import base64

from datetime import datetime, timezone



key_id = "john-key"                # key id

secret_key = b"john-secret-key"    # secret key

request_method = "GET"             # HTTP method

request_path = "/get"              # route URI

algorithm= "hmac-sha256"           # can use other algorithms in allowed_algorithms



# get current datetime in GMT

# note: the signature will become invalid after the clock skew (default 300s)

# you can regenerate the signature after it becomes invalid, or increase the clock

# skew to prolong the validity within the advised security boundary

gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')



# construct the signing string (ordered)

# the date and any subsequent custom headers should be lowercased and separated by a

# single space character, i.e. `<key>:<space><value>`

# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6

signing_string = (

  f"{key_id}\n"

  f"{request_method} {request_path}\n"

  f"date: {gmt_time}\n"

)



# create signature

signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()

signature_base64 = base64.b64encode(signature).decode('utf-8')



# construct the Authorization header

authorization = (

  f'Signature keyId="{key_id}",algorithm="{algorithm}",'

  f'headers="@request-target date",'

  f'signature="{signature_base64}"'

)



# print one value per line for the verification command to read

print(gmt_time)

print(authorization)
```

Generate fresh values and pass them directly to the request. The `read` commands consume the date and authorization value in the order printed by the script:

```
python3 hmac-sig-header-gen.py | {

  IFS= read -r HMAC_DATE

  IFS= read -r HMAC_AUTHORIZATION

  curl "http://127.0.0.1:9080/get" \

    -H "Date: ${HMAC_DATE}" \

    -H "Authorization: ${HMAC_AUTHORIZATION}"

}
```

You should see an `HTTP/1.1 200 OK` response similar to the following:

```
{

  "args": {}, 

  "headers": {

    "Accept": "*/*", 

    "Authorization": "<generated Authorization header>",

    "Date": "<generated Date header>",

    "Host": "127.0.0.1", 

    "User-Agent": "curl/8.6.0", 

    "X-Amzn-Trace-Id": "Root=1-66d96513-2e52d4f35c9b6a2772d667ea",

    "X-Consumer-Username": "john",

    "X-Credential-Identifier": "cred-john-hmac-auth",

    "X-Consumer-Custom-Id": "495aec6a",

    "X-Forwarded-Host": "127.0.0.1"

  }, 

  "origin": "192.168.65.1, 34.0.34.160", 

  "url": "http://127.0.0.1/get"

}
```

If you would like to attach more consumer custom headers to authenticated requests, see the [`attach-consumer-label`](https://docs.api7.ai/hub/attach-consumer-label.md) plugin.

### Hide Authorization Information From Upstream[​](#hide-authorization-information-from-upstream "Direct link to Hide Authorization Information From Upstream")

As seen in the previous example, the `Authorization` header passed to the upstream includes the signature and all other details. This could potentially introduce security risks.

This example continues from the [previous example](#implement-hmac-authentication-on-a-route) to demonstrate how to prevent this information from being sent to the upstream service.

* Admin API
* ADC
* Ingress Controller

Update the plugin configuration to set `hide_credentials` to `true`:

```
curl "http://127.0.0.1:9180/apisix/admin/routes/hmac-auth-route" -X PATCH \

-H "X-API-KEY: ${ADMIN_API_KEY}" \

-d '{

  "plugins": {

    "hmac-auth": {

      "hide_credentials": true

    }

  }

}'
```

Update the plugin configuration as such:

adc.yaml

```
consumers:

  - username: john

    labels:

      custom_id: "495aec6a"

    credentials:

      - name: hmac-auth

        type: hmac-auth

        config:

          key_id: john-key

          secret_key: john-secret-key

services:

  - name: hmac-auth-service

    routes:

      - name: hmac-auth-route

        uris:

          - /get

        methods:

          - GET

        plugins:

          hmac-auth:

            hide_credentials: true

    upstream:

      type: roundrobin

      nodes:

        - host: httpbin.org

          port: 80

          weight: 1
```

Preview the changes and confirm that they only affect resources for this guide:

```
adc diff -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

Synchronize the configuration to the gateway:

```
adc sync -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

* Gateway API
* APISIX CRD

Update the PluginConfig to set `hide_credentials` to `true`:

hmac-auth-ic.yaml

```
# other configs

# ---

apiVersion: apisix.apache.org/v1alpha1

kind: PluginConfig

metadata:

  namespace: aic

  name: hmac-auth-plugin-config

spec:

  plugins:

    - name: hmac-auth

      config:

        _meta:

          disable: false

        hide_credentials: true
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

Update the ApisixRoute to set `hide_credentials` to `true`:

hmac-auth-ic.yaml

```
# other configs

# ---

apiVersion: apisix.apache.org/v2

kind: ApisixRoute

metadata:

  namespace: aic

  name: hmac-auth-route

spec:

  ingressClassName: apisix

  http:

    - name: hmac-auth-route

      match:

        paths:

          - /get

        methods:

          - GET

      upstreams:

      - name: httpbin-external-domain

      plugins:

      - name: hmac-auth

        enable: true

        config:

          hide_credentials: true
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

Regenerate the short-lived signature and send a request to the route:

```
python3 hmac-sig-header-gen.py | {

  IFS= read -r HMAC_DATE

  IFS= read -r HMAC_AUTHORIZATION

  curl "http://127.0.0.1:9080/get" \

    -H "Date: ${HMAC_DATE}" \

    -H "Authorization: ${HMAC_AUTHORIZATION}"

}
```

You should see an `HTTP/1.1 200 OK` response and notice the `Authorization` header is entirely removed:

```
{

  "args": {}, 

  "headers": {

    "Accept": "*/*", 

    "Host": "127.0.0.1", 

    "User-Agent": "curl/8.6.0", 

    "X-Amzn-Trace-Id": "Root=1-66d96513-2e52d4f35c9b6a2772d667ea",

    "X-Consumer-Username": "john",

    "X-Credential-Identifier": "cred-john-hmac-auth",

    "X-Forwarded-Host": "127.0.0.1"

  }, 

  "origin": "192.168.65.1, 34.0.34.160", 

  "url": "http://127.0.0.1/get"

}
```

### Enable Body Validation[​](#enable-body-validation "Direct link to Enable Body Validation")

The following example demonstrates how to validate the request body and bind that digest to the HMAC signature.

When `validate_request_body` is true, APISIX compares the `Digest` header with a SHA-256 digest of the request body. A missing or mismatched `Digest` header is rejected. That comparison does not bind the digest to the HMAC signature, so a client can still replace both the body and the `Digest` header together. Sign `digest` as well, and list it in `signed_headers`, so the digest cannot change without invalidating the signature.

Compressed request bodies

If clients send gzip- or deflate-compressed bodies, the [`request-decompress` plugin](https://docs.api7.ai/hub/request-decompress.md) runs before `hmac-auth` at their default priorities. Body validation therefore hashes the decompressed bytes. To validate a digest calculated from the compressed bytes instead, set the `hmac-auth` `_meta.priority` above `2850` so it runs before decompression.

* Admin API
* ADC
* Ingress Controller

Create a consumer `john`:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "username": "john"

  }'
```

Create `hmac-auth` credential for the consumer:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "id": "cred-john-hmac-auth",

    "plugins": {

      "hmac-auth": {

        "key_id": "john-key",

        "secret_key": "john-secret-key"

      }

    }

  }'
```

Create a route with the `hmac-auth` plugin as such:

```
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "id": "hmac-auth-route",

    "uri": "/post",

    "methods": ["POST"],

    "plugins": {

      "hmac-auth": {

        "signed_headers": ["date", "digest"],

        "validate_request_body": true

      }

    },

    "upstream": {

      "type": "roundrobin",

      "nodes": {

        "httpbin.org:80": 1

      }

    }

  }'
```

Create a consumer with `hmac-auth` credential and a route with `hmac-auth` plugin configured as such:

adc.yaml

```
consumers:

  - username: john

    credentials:

      - name: hmac-auth

        type: hmac-auth

        config:

          key_id: john-key

          secret_key: john-secret-key

services:

  - name: hmac-auth-service

    routes:

      - name: hmac-auth-route

        uris:

          - /post

        methods:

          - POST

        plugins:

          hmac-auth:

            signed_headers:

              - date

              - digest

            validate_request_body: true

    upstream:

      type: roundrobin

      nodes:

        - host: httpbin.org

          port: 80

          weight: 1
```

Preview the changes and confirm that they only affect resources for this guide:

```
adc diff -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

Synchronize the configuration to the gateway:

```
adc sync -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

Create a consumer with `hmac-auth` credential and a route with `hmac-auth` plugin configured as such:

* Gateway API
* APISIX CRD

hmac-auth-ic.yaml

```
apiVersion: apisix.apache.org/v1alpha1

kind: Consumer

metadata:

  namespace: aic

  name: john

spec:

  gatewayRef:

    name: apisix

  credentials:

    - type: hmac-auth

      name: primary-cred

      config:

        key_id: john-key

        secret_key: john-secret-key

---

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: hmac-auth-plugin-config

spec:

  plugins:

    - name: hmac-auth

      config:

        _meta:

          disable: false

        signed_headers:

          - date

          - digest

        validate_request_body: true

---

apiVersion: gateway.networking.k8s.io/v1

kind: HTTPRoute

metadata:

  namespace: aic

  name: hmac-auth-route

spec:

  parentRefs:

    - name: apisix

  rules:

    - matches:

        - path:

            type: Exact

            value: /post

          method: POST

      filters:

        - type: ExtensionRef

          extensionRef:

            group: apisix.apache.org

            kind: PluginConfig

            name: hmac-auth-plugin-config

      backendRefs:

        - name: httpbin-external-domain

          port: 80
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

hmac-auth-ic.yaml

```
apiVersion: apisix.apache.org/v2

kind: ApisixConsumer

metadata:

  namespace: aic

  name: john

spec:

  ingressClassName: apisix

  authParameter:

    hmacAuth:

      value:

        key_id: john-key

        secret_key: john-secret-key

---

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: hmac-auth-route

spec:

  ingressClassName: apisix

  http:

    - name: hmac-auth-route

      match:

        paths:

          - /post

        methods:

          - POST

      upstreams:

      - name: httpbin-external-domain

      plugins:

      - name: hmac-auth

        enable: true

        config:

          signed_headers:

            - date

            - digest

          validate_request_body: true
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

Generate a signature. You can use the below Python snippet or other stack of your choice:

hmac-sig-digest-header-gen.py

```
import hmac

import hashlib

import base64

from datetime import datetime, timezone



key_id = "john-key"                # key id

secret_key = b"john-secret-key"    # secret key

request_method = "POST"             # HTTP method

request_path = "/post"              # route URI

algorithm= "hmac-sha256"            # can use other algorithms in allowed_algorithms

body = '{"name": "world"}'          # example request body



# get current datetime in GMT

# note: the signature will become invalid after the clock skew (default 300s).

# you can regenerate the signature after it becomes invalid, or increase the clock

# skew to prolong the validity within the advised security boundary

gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')



# create the SHA-256 digest of the request body and include it in the signing string

body_digest = hashlib.sha256(body.encode('utf-8')).digest()

digest_header = "SHA-256=" + base64.b64encode(body_digest).decode('utf-8')



# construct the signing string (ordered)

# the date and any subsequent custom headers should be lowercased and separated by a

# single space character, i.e. `<key>:<space><value>`

# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6

signing_string = (

    f"{key_id}\n"

    f"{request_method} {request_path}\n"

    f"date: {gmt_time}\n"

    f"digest: {digest_header}\n"

)



# create signature

signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()

signature_base64 = base64.b64encode(signature).decode('utf-8')



# construct the Authorization header

authorization = (

    f'Signature keyId="{key_id}",algorithm="hmac-sha256",'

    f'headers="@request-target date digest",'

    f'signature="{signature_base64}"'

)



# print one value per line for the verification command to read

print(gmt_time)

print(digest_header)

print(authorization)
```

Generate fresh values and pass them directly to the request:

```
python3 hmac-sig-digest-header-gen.py | {

  IFS= read -r HMAC_DATE

  IFS= read -r HMAC_DIGEST

  IFS= read -r HMAC_AUTHORIZATION

  curl "http://127.0.0.1:9080/post" -X POST \

    -H "Date: ${HMAC_DATE}" \

    -H "Digest: ${HMAC_DIGEST}" \

    -H "Authorization: ${HMAC_AUTHORIZATION}" \

    -d '{"name": "world"}'

}
```

You should see an `HTTP/1.1 200 OK` response similar to the following:

```
{

  "args": {},

  "data": "",

  "files": {},

  "form": {

    "{\"name\": \"world\"}": ""

  },

  "headers": {

    "Accept": "*/*",

    "Authorization": "<generated Authorization header>",

    "Content-Length": "17",

    "Content-Type": "application/x-www-form-urlencoded",

    "Date": "<generated Date header>",

    "Digest": "<generated Digest header>",

    "Host": "127.0.0.1:9080",

    "User-Agent": "curl/8.7.1",

    "X-Consumer-Username": "john",

    "X-Credential-Identifier": "cred-john-hmac-auth",

    "X-Forwarded-For": "192.168.117.1",

    "X-Forwarded-Host": "127.0.0.1:9080",

    "X-Forwarded-Port": "9080",

    "X-Forwarded-Proto": "http",

    "X-Real-IP": "192.168.117.1"

  },

  "json": null,

  "origin": "192.168.117.3",

  "url": "http://127.0.0.1/post"

}
```

If you send the same signed headers with a different body, the `Digest` header no longer matches:

```
python3 hmac-sig-digest-header-gen.py | {

  IFS= read -r HMAC_DATE

  IFS= read -r HMAC_DIGEST

  IFS= read -r HMAC_AUTHORIZATION

  curl "http://127.0.0.1:9080/post" -X POST \

    -H "Date: ${HMAC_DATE}" \

    -H "Digest: ${HMAC_DIGEST}" \

    -H "Authorization: ${HMAC_AUTHORIZATION}" \

    -d '{"name": "tampered"}'

}
```

You should see an `HTTP/1.1 401 Unauthorized` response with the following message:

```
{"message":"client request can't be validated"}
```

### Mandate Signed Headers[​](#mandate-signed-headers "Direct link to Mandate Signed Headers")

The following example demonstrates how you can mandate certain headers to be signed in the request's HMAC signature.

* Admin API
* ADC
* Ingress Controller

Create a consumer `john`:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "username": "john"

  }'
```

Create `hmac-auth` credential for the consumer:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "id": "cred-john-hmac-auth",

    "plugins": {

      "hmac-auth": {

        "key_id": "john-key",

        "secret_key": "john-secret-key"

      }

    }

  }'
```

Create a route with the `hmac-auth` plugin which requires three headers to be present in the HMAC signature:

```
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "id": "hmac-auth-route",

    "uri": "/get",

    "methods": ["GET"],

    "plugins": {

      "hmac-auth": {

        "signed_headers": ["date","x-custom-header-a", "x-custom-header-b"]

      }

    },

    "upstream": {

      "type": "roundrobin",

      "nodes": {

        "httpbin.org:80": 1

      }

    }

  }'
```

Create a consumer with `hmac-auth` credential and a route with `hmac-auth` plugin configured as such:

adc.yaml

```
consumers:

  - username: john

    credentials:

      - name: hmac-auth

        type: hmac-auth

        config:

          key_id: john-key

          secret_key: john-secret-key

services:

  - name: hmac-auth-service

    routes:

      - name: hmac-auth-route

        uris:

          - /get

        methods:

          - GET

        plugins:

          hmac-auth:

            signed_headers:

              - date

              - x-custom-header-a

              - x-custom-header-b

    upstream:

      type: roundrobin

      nodes:

        - host: httpbin.org

          port: 80

          weight: 1
```

Preview the changes and confirm that they only affect resources for this guide:

```
adc diff -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

Synchronize the configuration to the gateway:

```
adc sync -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

Create a consumer with `hmac-auth` credential and a route with `hmac-auth` plugin configured as such:

* Gateway API
* APISIX CRD

hmac-auth-ic.yaml

```
apiVersion: apisix.apache.org/v1alpha1

kind: Consumer

metadata:

  namespace: aic

  name: john

spec:

  gatewayRef:

    name: apisix

  credentials:

    - type: hmac-auth

      name: primary-cred

      config:

        key_id: john-key

        secret_key: john-secret-key

---

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: hmac-auth-plugin-config

spec:

  plugins:

    - name: hmac-auth

      config:

        _meta:

          disable: false

        signed_headers:

          - date

          - x-custom-header-a

          - x-custom-header-b

---

apiVersion: gateway.networking.k8s.io/v1

kind: HTTPRoute

metadata:

  namespace: aic

  name: hmac-auth-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: hmac-auth-plugin-config

      backendRefs:

        - name: httpbin-external-domain

          port: 80
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

hmac-auth-ic.yaml

```
apiVersion: apisix.apache.org/v2

kind: ApisixConsumer

metadata:

  namespace: aic

  name: john

spec:

  ingressClassName: apisix

  authParameter:

    hmacAuth:

      value:

        key_id: john-key

        secret_key: john-secret-key

---

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: hmac-auth-route

spec:

  ingressClassName: apisix

  http:

    - name: hmac-auth-route

      match:

        paths:

          - /get

        methods:

          - GET

      upstreams:

      - name: httpbin-external-domain

      plugins:

      - name: hmac-auth

        enable: true

        config:

          signed_headers:

            - date

            - x-custom-header-a

            - x-custom-header-b
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

Generate a signature. You can use the below Python snippet or other stack of your choice:

hmac-sig-req-header-gen.py

```
import hmac

import hashlib

import base64

from datetime import datetime, timezone



key_id = "john-key"                # key id

secret_key = b"john-secret-key"    # secret key

request_method = "GET"             # HTTP method

request_path = "/get"              # route URI

algorithm= "hmac-sha256"           # can use other algorithms in allowed_algorithms

custom_header_a = "hello123"       # required custom header

custom_header_b = "world456"       # required custom header



# get current datetime in GMT

# note: the signature will become invalid after the clock skew (default 300s)

# you can regenerate the signature after it becomes invalid, or increase the clock

# skew to prolong the validity within the advised security boundary

gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')



# construct the signing string (ordered)

# the date and any subsequent custom headers should be lowercased and separated by a

# single space character, i.e. `<key>:<space><value>`

# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6

signing_string = (

    f"{key_id}\n"

    f"{request_method} {request_path}\n"

    f"date: {gmt_time}\n"

    f"x-custom-header-a: {custom_header_a}\n"

    f"x-custom-header-b: {custom_header_b}\n"

)



# create signature

signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()

signature_base64 = base64.b64encode(signature).decode('utf-8')



# construct the Authorization header

authorization = (

    f'Signature keyId="{key_id}",algorithm="hmac-sha256",'

    f'headers="@request-target date x-custom-header-a x-custom-header-b",'

    f'signature="{signature_base64}"'

)



# print one value per line for the verification command to read

print(gmt_time)

print(authorization)
```

Generate fresh values and pass them directly to the request:

```
python3 hmac-sig-req-header-gen.py | {

  IFS= read -r HMAC_DATE

  IFS= read -r HMAC_AUTHORIZATION

  curl "http://127.0.0.1:9080/get" \

    -H "Date: ${HMAC_DATE}" \

    -H "Authorization: ${HMAC_AUTHORIZATION}" \

    -H "x-custom-header-a: hello123" \

    -H "x-custom-header-b: world456"

}
```

You should see an `HTTP/1.1 200 OK` response similar to the following:

```
{

  "args": {}, 

  "headers": {

    "Accept": "*/*", 

    "Authorization": "<generated Authorization header>",

    "Date": "<generated Date header>",

    "Host": "127.0.0.1", 

    "User-Agent": "curl/8.6.0", 

    "X-Amzn-Trace-Id": "Root=1-66d98196-64a58db25ece71c077999ecd",

    "X-Consumer-Username": "john",

    "X-Credential-Identifier": "cred-john-hmac-auth",

    "X-Custom-Header-A": "hello123", 

    "X-Custom-Header-B": "world456", 

    "X-Forwarded-Host": "127.0.0.1"

  }, 

  "origin": "192.168.65.1, 103.97.2.206", 

  "url": "http://127.0.0.1/get"

}
```

### Rate Limit with Anonymous Consumer[​](#rate-limit-with-anonymous-consumer "Direct link to Rate Limit with Anonymous Consumer")

The following example demonstrates how you can configure different rate limiting policies by regular and anonymous consumers, where the anonymous consumer does not need to authenticate and has less quota.

* Admin API
* ADC
* Ingress Controller

Create a regular consumer `john` and configure the `limit-count` plugin to allow for a quota of 3 within a 30-second window:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "username": "john",

    "plugins": {

      "limit-count": {

        "count": 3,

        "time_window": 30,

        "rejected_code": 429,

        "policy": "local"

      }

    }

  }'
```

Create the `hmac-auth` credential for the consumer `john`:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "id": "cred-john-hmac-auth",

    "plugins": {

      "hmac-auth": {

        "key_id": "john-key",

        "secret_key": "john-secret-key"

      }

    }

  }'
```

Create an anonymous user `anonymous` and configure the `limit-count` plugin to allow for a quota of 1 within a 30-second window:

```
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "username": "anonymous",

    "plugins": {

      "limit-count": {

        "count": 1,

        "time_window": 30,

        "rejected_code": 429,

        "policy": "local"

      }

    }

  }'
```

Create a route and configure the `hmac-auth` plugin to accept anonymous consumer `anonymous` from bypassing the authentication:

```
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \

  -H "X-API-KEY: ${ADMIN_API_KEY}" \

  -d '{

    "id": "hmac-auth-route",

    "uri": "/get",

    "methods": ["GET"],

    "plugins": {

      "hmac-auth": {

        "anonymous_consumer": "anonymous"

      }

    },

    "upstream": {

      "type": "roundrobin",

      "nodes": {

        "httpbin.org:80": 1

      }

    }

  }'
```

Configure consumers with different rate limits and a route that accepts anonymous users:

adc.yaml

```
consumers:

  - username: john

    plugins:

      limit-count:

        count: 3

        time_window: 30

        rejected_code: 429

        policy: local

    credentials:

      - name: hmac-auth

        type: hmac-auth

        config:

          key_id: john-key

          secret_key: john-secret-key

  - username: anonymous

    plugins:

      limit-count:

        count: 1

        time_window: 30

        rejected_code: 429

        policy: local

services:

  - name: anonymous-rate-limit-service

    routes:

      - name: hmac-auth-route

        uris:

          - /get

        methods:

          - GET

        plugins:

          hmac-auth:

            anonymous_consumer: anonymous

    upstream:

      type: roundrobin

      nodes:

        - host: httpbin.org

          port: 80

          weight: 1
```

Preview the changes and confirm that they only affect resources for this guide:

```
adc diff -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

Synchronize the configuration to the gateway:

```
adc sync -f adc.yaml \

  --include-resource-type consumer \

  --include-resource-type service \

  --label-selector docs-example=hmac-auth
```

* Gateway API
* APISIX CRD

Configure consumers with different rate limits and a route that accepts anonymous users:

hmac-auth-ic.yaml

```
apiVersion: apisix.apache.org/v1alpha1

kind: Consumer

metadata:

  namespace: aic

  name: john

spec:

  gatewayRef:

    name: apisix

  credentials:

    - type: hmac-auth

      name: primary-cred

      config:

        key_id: john-key

        secret_key: john-secret-key

  plugins:

    - name: limit-count

      config:

        count: 3

        time_window: 30

        rejected_code: 429

        policy: local

---

apiVersion: apisix.apache.org/v1alpha1

kind: Consumer

metadata:

  namespace: aic

  name: anonymous

spec:

  gatewayRef:

    name: apisix

  plugins:

    - name: limit-count

      config:

        count: 1

        time_window: 30

        rejected_code: 429

        policy: local

---

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: hmac-auth-plugin-config

spec:

  plugins:

    - name: hmac-auth

      config:

        anonymous_consumer: aic_anonymous  # namespace_consumername

---

apiVersion: gateway.networking.k8s.io/v1

kind: HTTPRoute

metadata:

  namespace: aic

  name: hmac-auth-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: hmac-auth-plugin-config

      backendRefs:

        - name: httpbin-external-domain

          port: 80
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-ic.yaml
```

hmac-auth-apisix-crd.yaml

```
apiVersion: apisix.apache.org/v2

kind: ApisixConsumer

metadata:

  namespace: aic

  name: john

spec:

  ingressClassName: apisix

  authParameter:

    hmacAuth:

      value:

        key_id: john-key

        secret_key: john-secret-key

  plugins:

    - name: limit-count

      config:

        count: 3

        time_window: 30

        rejected_code: 429

        policy: local

---

apiVersion: apisix.apache.org/v2

kind: ApisixConsumer

metadata:

  namespace: aic

  name: anonymous

spec:

  ingressClassName: apisix

  plugins:

    - name: limit-count

      config:

        count: 1

        time_window: 30

        rejected_code: 429

        policy: local

---

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: hmac-auth-route

spec:

  ingressClassName: apisix

  http:

    - name: hmac-auth-route

      match:

        paths:

          - /get

        methods:

          - GET

      upstreams:

        - name: httpbin-external-domain

      plugins:

        - name: hmac-auth

          config:

            anonymous_consumer: aic_anonymous
```

Apply the configuration to your cluster:

```
kubectl apply -f hmac-auth-apisix-crd.yaml
```

Generate a signature. You can use the below Python snippet or other stack of your choice:

hmac-sig-header-gen.py

```
import hmac

import hashlib

import base64

from datetime import datetime, timezone



key_id = "john-key"                # key id

secret_key = b"john-secret-key"    # secret key

request_method = "GET"             # HTTP method

request_path = "/get"              # route URI

algorithm= "hmac-sha256"           # can use other algorithms in allowed_algorithms



# get current datetime in GMT

# note: the signature will become invalid after the clock skew (default 300s)

# you can regenerate the signature after it becomes invalid, or increase the clock

# skew to prolong the validity within the advised security boundary

gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')



# construct the signing string (ordered)

# the date and any subsequent custom headers should be lowercased and separated by a

# single space character, i.e. `<key>:<space><value>`

# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6

signing_string = (

  f"{key_id}\n"

  f"{request_method} {request_path}\n"

  f"date: {gmt_time}\n"

)



# create signature

signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()

signature_base64 = base64.b64encode(signature).decode('utf-8')



# construct the Authorization header

authorization = (

  f'Signature keyId="{key_id}",algorithm="{algorithm}",'

  f'headers="@request-target date",'

  f'signature="{signature_base64}"'

)



# print one value per line for the verification command to read

print(gmt_time)

print(authorization)
```

Generate fresh values and pass them directly to five consecutive requests:

```
python3 hmac-sig-header-gen.py | {

  IFS= read -r HMAC_DATE

  IFS= read -r HMAC_AUTHORIZATION

  resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/get" \

    -H "Date: ${HMAC_DATE}" \

    -H "Authorization: ${HMAC_AUTHORIZATION}" \

    -o /dev/null -s -w "%{http_code}\n") && \

    count_200=$(echo "$resp" | grep "200" | wc -l) && \

    count_429=$(echo "$resp" | grep "429" | wc -l) && \

    echo "200": $count_200, "429": $count_429

}
```

You should see the following response, showing that out of the 5 requests, 3 requests were successful (status code 200) while the others were rejected (status code 429).

```
200:    3, 429:    2
```

Send five anonymous requests:

```
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/get" -o /dev/null -s -w "%{http_code}\n") && \

  count_200=$(echo "$resp" | grep "200" | wc -l) && \

  count_429=$(echo "$resp" | grep "429" | wc -l) && \

  echo "200": $count_200, "429": $count_429
```

You should see the following response, showing that only one request was successful:

```
200:    1, 429:    4
```
