APISIX HMAC Authentication
Feature Description
The hmac-auth-apisix
plugin is compatible with Apache APISIX’s HMAC authentication mechanism. It generates tamper-proof digital signatures for HTTP requests using the HMAC algorithm, enabling request identity authentication and permission control. This plugin is fully compatible with the configuration and signature algorithm of the Apache APISIX HMAC Authentication Plugin. For signature generation methods, please refer to the Apache APISIX HMAC Authentication Documentation.
Operational Attributes
- Plugin Execution Phase:
Authentication Phase
- Plugin Execution Priority:
330
Configuration Fields
Note:
- In a single rule, authentication configuration and authorization configuration cannot coexist.
- For requests that pass authentication and authorization, a
X-Mse-Consumer
field will be added to the request header to identify the caller’s name.
Authentication Configuration
Name | Data Type | Requirements | Default Value | Description |
---|---|---|---|---|
global_auth | bool | Optional (Instance-level configuration only) | - | Can only be configured at the instance level. If set to true , the authentication mechanism takes effect globally; if set to false , authentication only applies to domains and routes with specific configurations. If not configured, it takes effect globally only when there are no domain or route configurations (to maintain compatibility with legacy user habits). |
consumers | array of object | Required | - | Configures service callers for request authentication. |
allowed_algorithms | array of string | Optional | [“hmac-sha1”, “hmac-sha256”, “hmac-sha512”] | List of allowed HMAC algorithms. Valid values are combinations of “hmac-sha1”, “hmac-sha256”, and “hmac-sha512”. |
clock_skew | number | Optional | 300 | Maximum allowed time difference (in seconds) between the timestamp of the client request and the current time of the Higress server. This helps resolve time synchronization differences between the client and server and prevents replay attacks. The timestamp is calculated based on the time in the Date header (must be in GMT format). If set to 0 , this check is skipped. |
signed_headers | array of string | Optional | - | List of HTTP headers that should be included in the HMAC signature of the client request. |
validate_request_body | boolean | Optional | false | If set to true , the integrity of the request body is verified to ensure no tampering during transmission. Specifically, the plugin creates a SHA-256 base64-encoded digest and compares it with the Digest header. Verification fails if the Digest header is missing or the digest does not match. |
hide_credentials | boolean | Optional | false | If set to true , the authorization request header will not be passed to the upstream service. |
anonymous_consumer | string | Optional | - | Name of the anonymous consumer. If configured, anonymous users are allowed to bypass identity authentication. |
Configuration Fields for Each Item in consumers
Name | Data Type | Requirements | Default Value | Description |
---|---|---|---|---|
access_key | string | Required | - | A unique identifier for the consumer, used to reference configurations such as the secret key. |
secret_key | string | Required | - | Secret key used to generate the HMAC signature. |
name | string | Optional | access_key | Name of the consumer. |
Authorization Configuration (Non-essential)
Name | Data Type | Requirements | Default Value | Description |
---|---|---|---|---|
allow | array of string | Optional (Non-instance-level configuration only) | - | Can only be configured in fine-grained rules such as routes or domains. For requests that match the criteria, it configures the consumers allowed to access, enabling fine-grained permission control. |
Configuration Examples
Global Configuration Authentication and Route-Level Authorization
The following configuration is used to enable Hmac Auth authentication and authorization for specific routes or domains of the gateway. Note: The access_key
field must be unique.
Example 1: Basic Route and Domain Authorization Configuration
Instance-Level Plugin Configuration:
global_auth: falseconsumers:- name: consumer1 access_key: consumer1-key secret_key: 2bda943c-ba2b-11ec-ba07-00163e1250b5- name: consumer2 access_key: consumer2-key secret_key: c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
Route-Level Configuration (applicable to route-a
and route-b
):
allow:- consumer1 # Only consumer1 is allowed access
Domain-Level Configuration (applicable to *.example.com
and test.com
):
allow:- consumer2 # Only consumer2 is allowed access
Configuration Instructions:
- Route Names (e.g.,
route-a
,route-b
): Correspond to the names defined when creating gateway routes. Onlyconsumer1
is allowed access when matched. - Domain Matching (e.g.,
*.example.com
,test.com
): Used to filter request domains. Onlyconsumer2
is allowed access when matched. - Callers not in the
allow
list will be denied access.
To Generate a Signature, Use the Following Go Code Snippet or Other Tech Stacks:
package main
import ( "crypto/hmac" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "fmt" "hash" "strings" "time")
// SignedHeader defines the structure of signed headerstype SignedHeader struct { Name string Value string}
func main() { // Configuration parameters keyID := "consumer1-key" // Key ID secretKey := "2bda943c-ba2b-11ec-ba07-00163e1250b5" // Secret key requestMethod := "POST" // HTTP method requestPath := "/foo" // Route URI algorithm := "hmac-sha256" // Algorithm validateRequestBody := false // Whether to validate the request body; set to true to add the Digest header
// If signed_headers is configured, add them in order signedHeaders := []SignedHeader{ //{Name: "x-custom-header-a", Value: "test1"}, //{Name: "x-custom-header-b", Value: "test2"}, }
body := []byte("{}") // Request body
// Get current GMT time gmtTime := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
// Dynamically construct the signing string (in order) signingStringBuilder := strings.Builder{} signingStringBuilder.WriteString(fmt.Sprintf("%s\n%s %s\ndate: %s\n", keyID, requestMethod, requestPath, gmtTime))
// Add headers in the order specified in signedHeaders for _, header := range signedHeaders { signingStringBuilder.WriteString(fmt.Sprintf("%s: %s\n", header.Name, header.Value)) }
signingString := signingStringBuilder.String()
// Generate signature signature, err := generateHmacSignature(secretKey, algorithm, signingString) if err != nil { fmt.Printf("Error generating signature: %v\n", err) return }
// Dynamically build the content of the headers field headersField := "@request-target date" for _, header := range signedHeaders { headersField += " " + header.Name }
// Construct request headers headers := map[string]string{ "Date": gmtTime, "Authorization": fmt.Sprintf(`Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"`, keyID, algorithm, headersField, signature, ), }
// Add Digest header if request body validation is required if validateRequestBody { headers["Digest"] = calculateBodyDigest(body) }
// Add signed request headers for _, header := range signedHeaders { formattedHeaderName := formatHeaderName(header.Name) headers[formattedHeaderName] = header.Value }
// Print the signing string fmt.Printf("signingString: %s\n", signingString) // Print request headers fmt.Println("Headers:") for key, value := range headers { fmt.Printf("%s: %s\n", key, value) }}
// generateHmacSignature generates an HMAC signaturefunc generateHmacSignature(secretKey, algorithm, message string) (string, error) { var mac hash.Hash
switch algorithm { case "hmac-sha1": mac = hmac.New(sha1.New, []byte(secretKey)) case "hmac-sha256": mac = hmac.New(sha256.New, []byte(secretKey)) case "hmac-sha512": mac = hmac.New(sha512.New, []byte(secretKey)) default: return "", fmt.Errorf("unsupported algorithm: %s", algorithm) }
mac.Write([]byte(message)) signature := mac.Sum(nil) return base64.StdEncoding.EncodeToString(signature), nil}
// calculateBodyDigest calculates the digest of the request bodyfunc calculateBodyDigest(body []byte) string { hash := sha256.Sum256(body) encodedDigest := base64.StdEncoding.EncodeToString(hash[:]) return "SHA-256=" + encodedDigest}
// formatHeaderName converts the header name to standard HTTP header formatfunc formatHeaderName(headerName string) string { parts := strings.Split(headerName, "-") for i, part := range parts { if len(part) > 0 { parts[i] = strings.ToUpper(part[:1]) + strings.ToLower(part[1:]) } } return strings.Join(parts, "-")}
Request and Response Examples:
- Validation Passed Scenario
curl -X POST 'http://localhost:8082/foo' \-H 'Authorization:Signature keyId="consumer1-key",algorithm="hmac-sha256",headers="@request-target date",signature="746z4VISwZehUwZdzTV486ZMMbBtakmMHKPfs/A4RdU="' \-H 'Date:Fri, 12 Sep 2025 23:53:18 GMT' \-H 'Content-Type: application/json' \-d '{}'
- Response: Returns a normal response from the backend service.
- Additional Info: After successful authentication, the request header
X-Mse-Consumer: consumer1
is automatically added and passed to the backend.
- Signature Verification Failure Due to Modified Request Method
curl -X PUT 'http://localhost:8082/foo' \ # POST is modified to PUT here-H 'Authorization:Signature keyId="consumer1-key",algorithm="hmac-sha256",headers="@request-target date",signature="746z4VISwZehUwZdzTV486ZMMbBtakmMHKPfs/A4RdU="' \-H 'Date:Fri, 12 Sep 2025 23:53:18 GMT' \-H 'Content-Type: application/json' \-d '{}'
- Response:
401 Unauthorized
- Error Message:
{"message":"client request can't be validated: Invalid signature"}
- Caller Not in Allow List
curl -X POST 'http://localhost:8082/foo' \-H 'Authorization:Signature keyId="consumer2-key",algorithm="hmac-sha256",headers="@request-target date",signature="dltotPwd4iWGGz//kuehPJlHXZemR5WKwCPAJD/KPhE="' \-H 'Date:Fri, 12 Sep 2025 23:59:01 GMT' \-H 'Content-Type: application/json' \-d '{}'
- Response:
401 Unauthorized
- Error Message:
{"message":"client request can't be validated: consumer 'consumer2' is not allowed"}
- Expired Timestamp
curl -X POST 'http://localhost:8082/foo' \-H 'Authorization:Signature keyId="consumer1-key",algorithm="hmac-sha256",headers="@request-target date",signature="746z4VISwZehUwZdzTV486ZMMbBtakmMHKPfs/A4RdU="' \-H 'Date:Fri, 12 Sep 2025 23:53:18 GMT' \ # Expired timestamp-H 'Content-Type: application/json' \-d '{}'
- Response:
401 Unauthorized
- Error Message:
{"message":"client request can't be validated: Clock skew exceeded"}
Example 2: Configuration with Custom Signed Headers and Request Body Validation
Instance-Level Plugin Configuration:
global_auth: falseconsumers:- name: consumer1 access_key: consumer1-key secret_key: 2bda943c-ba2b-11ec-ba07-00163e1250b5- name: consumer2 access_key: consumer2-key secret_key: c8c8e9ca-558e-4a2d-bb62-e700dcc40e35signed_headers: # Custom request headers to be included in the signature- X-Custom-Header-A- X-Custom-Header-Bvalidate_request_body: true # Enable request body signature verification
Request and Response Examples:
- Validation Passed Scenario
curl -X POST 'http://localhost:8082/foo' \-H 'Authorization:Signature keyId="consumer1-key",algorithm="hmac-sha256",headers="@request-target date x-custom-header-a x-custom-header-b",signature="KoOlbkDIR/JzlKK47eURewnIpmhpkQU+KIyBUhqVfmo="' \-H 'Date:Sat, 13 Sep 2025 00:04:34 GMT' \-H 'Digest:SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=' \ # Request body digest-H 'X-Custom-Header-A:test1' \-H 'X-Custom-Header-B:test2' \-H 'Content-Type: application/json' \-d '{}'
- Response: Returns a normal response from the backend service.
- Missing Signed Header
curl -X POST 'http://localhost:8082/foo' \-H 'Authorization:Signature keyId="consumer1-key",algorithm="hmac-sha256",headers="@request-target date x-custom-header-b",signature="KoOlbkDIR/JzlKK47eURewnIpmhpkQU+KIyBUhqVfmo="' \-H 'Date:Sat, 13 Sep 2025 00:04:34 GMT' \-H 'Digest:SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=' \-H 'X-Custom-Header-B:test2' \ # X-Custom-Header-A is missing-H 'Content-Type: application/json' \-d '{}'
- Response:
401 Unauthorized
- Error Message:
{"message":"client request can't be validated: expected header \"X-Custom-Header-A\" missing in signing"}
- Tampered Request Body
curl -X POST 'http://localhost:8082/foo' \-H 'Authorization:Signature keyId="consumer1-key",algorithm="hmac-sha256",headers="@request-target date x-custom-header-a x-custom-header-b",signature="NcA+44FFtl2rjNvV28wSn8Rln02i4i2tFXKp3/ahyYA="' \-H 'Date:Sat, 13 Sep 2025 00:09:40 GMT' \-H 'Digest:SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=' \-H 'X-Custom-Header-A:test1' \-H 'X-Custom-Header-B:test2' \-H 'Content-Type: application/json' \-d '{"key":"value"}' # Tampered request body
- Response:
401 Unauthorized
- Error Message:
{"message":"client request can't be validated: Invalid digest"}
Enable Global Authentication at the Gateway Instance Level
The following configuration enables Hmac Auth authentication at the gateway instance level. All requests must be authenticated to access the gateway:
global_auth: true # Enable global authenticationconsumers:- name: consumer1 access_key: consumer1-key secret_key: 2bda943c-ba2b-11ec-ba07-00163e1250b5- name: consumer2 access_key: consumer2-key secret_key: c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
Description: When global_auth: true
, all requests to the gateway must carry valid authentication information. Unauthenticated requests will be rejected directly.