All Products
Search
Document Center

Function Compute:Configure signature authentication for custom domain names

Last Updated:Apr 01, 2026

Function Compute allows you to configure signature authentication for HTTP triggers. When you enable signature authentication on a custom domain name, API Gateway validates the signature on every inbound request before forwarding it to your function. This offloads authentication from your function so you can focus on business logic.

Enable signature authentication when you want only callers with a valid AccessKey to invoke your function through a custom domain name. Leave it disabled for public, unauthenticated endpoints.

Prerequisites

Before you begin, ensure that you have:

Enable signature authentication

  1. Log on to the Function Compute console. In the left navigation pane, choose Function Management > Custom Domain Name.

  2. In the top navigation bar, select the region where the custom domain name resides. On the Custom Domains page, click the custom domain name.

  3. In the upper-right corner, click Modify. In the Authentication Settings section, set Authentication Method to Signature Authentication, then click Save.

Authentication Settings section with Signature Authentication selected

Verify the setup

After enabling signature authentication, write code on your on-premises machine and run the code to send a signed request and confirm that API Gateway accepts it. The following example uses Go to build and send a signed request.

The sample project contains two files:

FilePurpose
signature.goImplements the signing algorithm
main.goSends a signed POST request to your function endpoint
Sample project file structure

How the signing algorithm works

The signing algorithm builds the Authorization header in four steps:

  1. Build the canonical string — Concatenate the HTTP method, Accept, Content-MD5, Content-Type, and Date headers, followed by sorted x-acs-* headers (CanonicalizedOSSHeaders), and the canonicalized resource (URL path + sorted query parameters).

  2. Compute the HMAC-SHA1 signature — Apply HMAC-SHA1 using your AccessKey secret as the key, then Base64-encode the result.

  3. Format the authorization string — Combine the result as acs <AccessKey ID>:<signature>.

  4. Attach the header — Set Authorization: acs <AccessKey ID>:<signature> on the request.

signature.go

package sign

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"hash"
	"io"
	"net/http"
	"sort"
	"strings"
)

// GetPOPAuthStr returns the Authorization header value for a request.
func GetPOPAuthStr(accessKeyID string, accessKeySecret string, req *http.Request) string {
	return "acs " + accessKeyID + ":" + GetPOPSignature(accessKeySecret, req)
}

// GetPOPSignature computes the signature for a request.
func GetPOPSignature(akSecret string, req *http.Request) string {
	stringToSign := getStringToSign(req)
	return GetROASignature(stringToSign, akSecret)
}

// GetROASignature applies HMAC-SHA1 and Base64-encodes the result.
func GetROASignature(stringToSign string, secret string) string {
	h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(secret))
	io.WriteString(h, stringToSign)
	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
	return signedStr
}

// getStringToSign builds the canonical string from the request.
func getStringToSign(req *http.Request) string {
	queryParams := make(map[string]string)
	for k, v := range req.URL.Query() {
		queryParams[k] = v[0]
	}
	// Sort query parameters by key
	var queryKeys []string
	for key := range queryParams {
		queryKeys = append(queryKeys, key)
	}
	sort.Strings(queryKeys)
	tmp := ""
	for i := 0; i < len(queryKeys); i++ {
		queryKey := queryKeys[i]
		v := queryParams[queryKey]
		if v != "" {
			tmp = tmp + "&" + queryKey + "=" + v
		} else {
			tmp = tmp + "&" + queryKey
		}
	}
	resource := req.URL.EscapedPath()
	if tmp != "" {
		tmp = strings.TrimLeft(tmp, "&")
		resource = resource + "?" + tmp
	}
	return getSignedStr(req, resource)
}

func getSignedStr(req *http.Request, canonicalizedResource string) string {
	temp := make(map[string]string)

	for k, v := range req.Header {
		if strings.HasPrefix(strings.ToLower(k), "x-acs-") {
			temp[strings.ToLower(k)] = v[0]
		}
	}
	hs := newSorter(temp)

	// Sort x-acs-* headers alphabetically
	hs.Sort()

	// Build the canonicalized x-acs-* headers string
	canonicalizedOSSHeaders := ""
	for i := range hs.Keys {
		canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n"
	}

	date := req.Header.Get("Date")
	accept := req.Header.Get("Accept")
	contentType := req.Header.Get("Content-Type")
	contentMd5 := req.Header.Get("Content-MD5")

	signStr := req.Method + "\n" + accept + "\n" + contentMd5 + "\n" + contentType + "\n" +
		date + "\n" + canonicalizedOSSHeaders + canonicalizedResource
	return signStr
}

// Sorter sorts header keys and values together.
type Sorter struct {
	Keys []string
	Vals []string
}

func newSorter(m map[string]string) *Sorter {
	hs := &Sorter{
		Keys: make([]string, 0, len(m)),
		Vals: make([]string, 0, len(m)),
	}
	for k, v := range m {
		hs.Keys = append(hs.Keys, k)
		hs.Vals = append(hs.Vals, v)
	}
	return hs
}

func (hs *Sorter) Sort() {
	sort.Sort(hs)
}

func (hs *Sorter) Len() int {
	return len(hs.Vals)
}

func (hs *Sorter) Less(i, j int) bool {
	return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0
}

func (hs *Sorter) Swap(i, j int) {
	hs.Vals[i], hs.Vals[j] = hs.Vals[j], hs.Vals[i]
	hs.Keys[i], hs.Keys[j] = hs.Keys[j], hs.Keys[i]
}

go.mod

module auth.fc.aliyun.com

go 1.17

main.go

The following sample reads credentials from environment variables and sends a signed POST request to your function endpoint.

This example uses long-term AccessKey credentials for simplicity. For production workloads, use Security Token Service (STS) instead. See Create an AccessKey and Manage access credentials for details.
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"time"

	"auth.fc.aliyun.com/sign"
)

func main() {
	// Replace with your custom domain name or HTTP trigger endpoint.
	url := "A custom domain name or the endpoint of the HTTP trigger"

	// Read credentials from environment variables.
	// Make sure ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET are set.
	ak := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_ID")
	sk := os.Getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")

	// Build the request body.
	data := map[string]interface{}{
		"user": "FC 3.0",
	}
	jsonData, err := json.Marshal(data)
	if err != nil {
		fmt.Printf("Error encoding JSON: %s\n", err)
		return
	}

	// Create the request.
	request, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		fmt.Printf("Error creating request: %s\n", err)
		return
	}
	request.Header.Set("Content-Type", "application/json")

	// Sign the request and attach the Authorization header.
	addAuthInfo(request, ak, sk)

	// Send the request.
	client := &http.Client{}
	response, err := client.Do(request)
	if err != nil {
		fmt.Printf("Error sending request to server: %s\n", err)
		return
	}
	defer response.Body.Close()

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		fmt.Printf("Error reading response body: %s\n", err)
		return
	}

	fmt.Printf("Response Status: %s\n", response.Status)
	fmt.Printf("Response Body: %s\n", string(body))
}

func addAuthInfo(req *http.Request, ak, sk string) {
	if req.Header.Get("Date") == "" {
		req.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	}
	if req.URL.Path == "" {
		req.URL.Path = "/"
	}
	authHeader := sign.GetPOPAuthStr(ak, sk, req)
	req.Header.Set("Authorization", authHeader)
}

A successful response looks like this:

Response Status: 200 OK
Response Body: Hello World!

Troubleshoot common errors

"Required HTTP header Date was not specified"

Cause: The request is missing the Date header, the Authorization header, or both.

Fix:

  1. If the request has no Authorization header, add signing logic using the sample code above.

  2. If the Authorization header is present but Date is missing, add a Date header set to the current UTC time in RFC 7231 format — for example, Thu, 04 Jan 2024 01:33:13 GMT. The addAuthInfo function in main.go sets this automatically.

"The difference between the request time and the current time is too large"

Cause: The signature has expired. Re-sign the request using the current system time.

"The request signature we calculated does not match the signature you provided"

Cause: The signature in the request does not match what Function Compute computed from the same inputs.

Fix: Check the following in order:

  1. Credentials — Verify the AccessKey ID and AccessKey secret in your environment variables are correct.

  2. Canonical string order — Compare your implementation against the getStringToSign function step by step. The required order is: HTTP method → AcceptContent-MD5Content-TypeDate → sorted x-acs-* headers → canonicalized resource.

  3. Authorization format — Confirm the Authorization header value follows the format acs <AccessKey ID>:<signature>. Check the output of GetPOPAuthStr.