Cloud-native API Gateway supports global authentication, route-level authentication, and consumer authorization to ensure that only authorized requests can access services. This topic describes how to use authentication and authorization in cloud-native API Gateway to help consumers securely access resources.
Background information
Global authentication and authorization is ideal for business-to-consumer (B2C) scenarios, such as unified logon. In contrast, route-level and API-level consumer authentication is designed for business-to-business (B2B) scenarios, such as granting API access to partners.
Comparison | Global authentication and authorization | Route authentication + Consumer authorization |
Scenarios | Customer-facing (ToC) scenarios, such as unified logon and authentication. | B2B scenarios, such as granting API access to partners. |
Core differences | When you enable authentication, authorization is also enabled. | After you enable authentication, you must configure authorization separately. |
Configuration entry point | . |
|
Authentication method configuration (using JWT authentication as an example) |
|
|
Authorization method configuration | When you create the configuration, enter the list of Domain Names and Paths for the blacklist or whitelist.
|
|
Notes
After you enable consumer authentication, the policy takes effect immediately. If a route or API is already published but has no consumers or authorization rules configured, all access requests are denied by default.
Use consumer authentication and authorization
JWT authentication
JWT authentication flow overview
The client sends an authentication request to API Gateway. The request usually contains the end user's username and password.
The gateway forwards the request directly to the backend service.
The backend service reads and validates the verification information, such as the username and password, from the request. After the information is validated, the service uses a private key to generate a standard token and returns it to the gateway.
The gateway returns the response containing the token to the client. The client must cache this token locally.
The client sends a business request to API Gateway with the token.
The gateway uses the user-configured public key to verify the token in the request. After verification passes, the request is forwarded to the backend service.
The backend service processes the business logic and sends a response.
The gateway returns the business response to the client.
The following sections describe how to generate a token, how the client sends a request to the gateway, and how the gateway uses the configured public key to verify the token.
Generate a token with an authentication service
The following Java example shows how to generate a token. For other languages, use the corresponding tools to generate key pairs.
Create a Maven project and add the following dependency:
First, create a Maven project and add the following dependency:
<dependency> <groupId>org.bitbucket.b_c</groupId> <artifactId>jose4j</artifactId> <version>0.7.0</version> </dependency>Select a method to generate the token.
You can generate a token using either a symmetric key or an asymmetric key. Choose the method that meets your requirements.
Generate a token using a symmetric key
Code example:
package org.example; import java.io.UnsupportedEncodingException; import java.security.PrivateKey; import org.jose4j.base64url.Base64; import org.jose4j.json.JsonUtil; import org.jose4j.jwk.OctJwkGenerator; import org.jose4j.jwk.OctetSequenceJsonWebKey; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.NumericDate; import org.jose4j.keys.HmacKey; import org.jose4j.lang.JoseException; import sun.lwawt.macosx.CSystemTray; public class Main { public static void main(String[] args) throws JoseException, UnsupportedEncodingException { // Use the example from this topic String privateKeyJson = "{\n" + " \"k\": \"VoBG-oyqVoyCr9G56ozmq8n_rlDDyYMQOd_DO4GOkEY\",\n" + " \"kty\": \"oct\",\n" + " \"alg\": \"HS256\",\n" + "}"; JwtClaims claims = new JwtClaims(); claims.setGeneratedJwtId(); claims.setIssuedAtToNow(); // Set the expiration time to less than 7 days NumericDate date = NumericDate.now(); date.addSeconds(120*60); claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); // Add custom parameters. All values must be of the String type. // Set the consumer ID claims.setClaim("uid", "11215ac069234abcb8944232b79ae711"); JsonWebSignature jws = new JsonWebSignature(); // Set the encryption algorithm jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); jws.setKey(new HmacKey(Base64.decode(JsonUtil.parseJson(privateKeyJson).get("k").toString()))); jws.setPayload(claims.toJson()); String jwtResult = jws.getCompactSerialization(); System.out.println("Generated JSON Web Token result is: \n" + jwtResult); } }Code settings:
privateKeyJson: This is the JSON Web Key Set (JWKS) used when you create the consumer. You can record the JWKS when you create the consumer or retrieve it later from the consumer's basic configuration page.Set the consumer ID. For example,
claims.setClaim("uid", "11215ac069234abcb8944232b79ae711"). The console generates a default consumer ID when you create a consumer. You can modify this ID as needed. You can also retrieve the consumer ID from the consumer's basic configuration page.Set the encryption algorithm. For example,
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256). This algorithm must match the one in the JWKS.NoteThe supported encryption algorithms are ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512, HS256, HS384, HS512, and EdDSA.
When using symmetric encryption, decode the "k" value.
jws.setKey(new HmacKey(Base64.decode(JsonUtil.parseJson(privateKeyJson).get("k").toString())));
Set the expiration time. The expiration time must be less than 7 days. After the token expires, you must regenerate it to ensure security.
... NumericDate date = NumericDate.now(); date.addSeconds(120*60); claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); ...You can add custom parameters to the JWKS PAYLOAD to meet your business needs.
Generate a token using an asymmetric key
Code example:
package org.example; import java.security.PrivateKey; import org.jose4j.json.JsonUtil; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.NumericDate; import org.jose4j.lang.JoseException; import org.jose4j.jwk.RsaJsonWebKey; public class Main { public static void main(String[] args) throws JoseException { // Example of a key in JWK format String privateKeyJson = "{" + "\"kty\":\"RSA\"," + "\"n\":\"u-8lR9lyRhu8tl4vRxOl7yfshssx5jRstabc9n4Rxrz102Z7TPFYrXBZHgf67Y0d-zx9tWd5j91WZxLHv4K6VWPN7zEWEQNn3vUg76dPHPzVkZJWziFPS1EvS4a2gRZdrE4nPogaQ72WySVC0yUF2fL0NKeOclD__coCFxn4QQjcDXyu_CUbI_FuDcdw267mVjjylAaFvOZHH0pXsV8m5zXlpc2aiemWYQJD9MtWRcoKlexWMkTwbEqW5-NWAl0Uo202ahDA1NiaQ98Ch4nw6g2E1GvwxxHkbvMuZcs5z8F5Ct_w0IPtvY7ngSyEN-WCU40oj-C5NUCy_73FpXXdWQ\"," + "\"e\":\"AQAB\"," + "\"d\":\"Uxg1IqSZazg-Y2AXhVTBrJG5egwD3yZU3qiN0IsDbx0DkFoisG2R6PXg4W9j2n7nv7sKVhgPXrXdyys5mIrDuperaVQJzrHzzlgSHQSb7VQ5Vekfanq95a5avAkvTrpF5raTkYl6G3OLZRqNhnA7Oxe6NEHVsOPxnBQignZgFtiBtCSZY4RVH6Dx4jFfBBNMC9ifyLWLpHut7eczvxI412nBkxgtEjSeFe083NlumO_ZYHbijPcQf5zFWPLEj2EvlgbSwhjc-uSAF4OljAyG_DHZYvztEIGMdxMgBHgwtEvCfzS6PeUgvUR-SB7m_L8z4gjz6TlpSYe3CnZqE69KdQ\"," + "\"p\":\"9mNw8U_GxbcSknueUeFFSU9wKD9E9P2ZO5RP5d7o3qGUXOfbrH_g5GMH3YJiPBDZs_2BYFrAACOY4YM-QTiXWVs4xS3OeD8AdH3wEXR_3DMEkOez3cNq3Jy3ZJabm7IUnPMIWv5gpIHghcx0YTtM5RabSgexLMKh2-6sB466378\"," + "\"q\":\"w0PzXI_8Q5jv15OUq-dV_Z0kp91Icumf6PEERZN4v3i3VolLBnamMiIQiF74ywclKpZmtfOQTfyL41Xj2vbm2Aus6akRsU3NhQlVtIQTzHWUuEQgMIJYDK7--FzEcZORm1qBiEffiWv6U-slyCcLcNDNT-wjMX8BrS4oWHIoCOc\"," + "\"dp\":\"fRRiY76yE_EqVn63Eq4ftGXFdEkaQpzzS1GxderBoTO506hI1rtcedTkS0lDgWa0fjE1mqq3SdrIY8NyuT13Z_9tRHxKkrS5EGpWkyXnOuwTZ1SY9P2dpD1SxJfIizPOTxb5qOf2O81LI-F1O18VXD8rultJUIXGEZaKcpO8vpU\"," + "\"dq\":\"V6EP_vMzD5b707AMYVURFx7Fi3vX_pHvzJcVBrBW2P6wsGouvDjU_tygtMKCPoL3X_RdJbynfwgeMyihd-ujz0L2F2pjYUF8QP7ecoNvays9UbBpDbwBDbge_pCLLDlAeAqW5PT0UXSew7hcnUVAciGSchKT_Kt1siVrv72DT_M\"," + "\"qi\":\"w5fENzxOivbbbUbawAWSuLgWPtvbTKn2XuPzwr_JzZH08-nadWwQFChcvAiCs8V-306TQOh8NfY308QpGDIq-iRfrS7CEePOjRzpHJfsaQ1IFQqzgDZ9VGdJRDlZqRHx0DbqwMlleVKTC6ER6varalbr4lKU-ZPUQLCzj0e4PMs\"" + "}"; JwtClaims claims = new JwtClaims(); claims.setGeneratedJwtId(); // Automatically generate a jti (JWT ID) claims.setIssuedAtToNow(); // Set the issued-at time (iat) to the current time // Set the expiration time to less than 7 days NumericDate date = NumericDate.now(); date.addSeconds(120 * 60); // Set the expiration time to the current time plus 120 minutes claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); // Set the not-before time (nbf) to 1 minute in the past // Add custom parameters. All values must be of the String type. // Set the consumer ID claims.setClaim("uid", "11215ac069234abcb8944232b79ae711"); JsonWebSignature jws = new JsonWebSignature(); // Set the encryption algorithm to RSA-SHA256 jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); // Parse the private key and set it in the signer PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyJson)).getPrivateKey(); jws.setKey(privateKey); // Set the payload content jws.setPayload(claims.toJson()); // Generate the JWT String jwtResult = jws.getCompactSerialization(); System.out.println("Generated JSON Web Token result is: \n" + jwtResult); } }Code settings:
Set
privateKeyJson, the consumer ID, and the expiration time. This is the same as for the symmetric encryption algorithm.Set the encryption algorithm. For example,
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256). This algorithm must match the one in the JWKS.To encrypt data using an asymmetric key encryption algorithm, you must use the private key.
... jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyJson)).getPrivateKey(); jws.setKey(privateKey); ...You can add custom parameters to the
PAYLOADof the JWT as needed.
Send a business request from the client to the gateway
Cloud-native API Gateway supports passing the token in a header. You can customize the request header name and the token prefix. During request validation, the key and prefix must match the consumer authentication configuration.
If the request does not contain a JWT, a 401 error is returned.
curl http://xxx.hello.com/testIf the request contains an invalid JWT, a 401 error is returned.
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ1'If the request contains a valid JWT but the consumer does not have permission to access the API or route, a 403 error is returned.
# consumer1 is not authorized for the route or API at the following path curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
Verify the token on the server
Server-side token verification involves the following three steps:
When the server receives a user request, it first checks if a token is included. If not, the request is rejected with a 401 error.
For requests with a token, the server uses the public key from the configured JWKS to verify that the token is valid and has not expired. If the token is invalid or expired, the request is rejected with a 401 error.
If the token is valid, the server then checks if the consumer represented by the token is authorized to access the requested API or route.
Common error codes
HTTP status code | Error message | Cause |
401 | Jwt missing | The request header does not contain a JWT. |
401 | Jwt expired | The JWT has expired. |
401 | Jwt verification fails | JWT payload verification failed. For example, the `iss` claim does not match. |
403 | Access Denied | No permission to access the current route. |
AK/SK (HMAC) authentication
Generate a signature on the client
Client-side signature generation flow:
Extract the string-to-sign: Extract key data from the original request to create a string for signing.
Generate the signature: Use an encryption algorithm and the configured Secret Key (SK) to encrypt the string-to-sign and generate the signature.
Add the signature: Add all signature-related headers to the original HTTP request to create the final HTTP request.
Step 1: Extract the string-to-sign
The client needs to extract key data from the HTTP request and combine it into a string-to-sign. The format of the generated string-to-sign is as follows:
HTTPMethod
Accept
Content-MD5
Content-Type
Date
Headers
PathAndParametersThese seven fields form the entire string-to-sign. The fields are separated by line feeds (`\n`). If the `Headers` field is empty, do not add a line feed after the `Headers` field. For all other empty fields, you must retain the line feed. The signature is case-sensitive. The rules for extracting each field are as follows:
HTTPMethod: The HTTP method in uppercase. For example, POST.
Accept: The value of the `Accept` header in the request. This field can be empty. You must explicitly set the `Accept` header. If this header is empty, some HTTP clients set a default value of
*/*, which causes signature verification to fail.Content-MD5: The value of the `Content-MD5` header in the request. This field can be empty. You must calculate the `Content-MD5` header only if the request has a body that is not in form format. The following Java code shows how to calculate the Content-MD5 value:
String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8")));Content-Type: The value of the `Content-Type` header in the request. This field can be empty.
Date: The value of the `Date` header in the request. This field can be empty if the
date_offsetconfiguration is not enabled. Otherwise, the gateway uses this field for time offset verification.Headers: You can select specific headers to be included in the signature. The rules for concatenating the header string are as follows:
The keys of the headers included in the signature calculation are sorted alphabetically and then concatenated as follows.
HeaderKey1 + ":" + HeaderValue1 + "\n"\+ HeaderKey2 + ":" + HeaderValue2 + "\n"\+ ... HeaderKeyN + ":" + HeaderValueN + "\n"
If a header's value is empty, use `HeaderKey+":"+"\n"` for the signature. The key and the colon must be retained.
A comma-separated list of all header keys included in the signature is placed in the `X-Ca-Signature-Headers` header.
The following headers are not included in the header signature calculation: `X-Ca-Signature`, `X-Ca-Signature-Headers`, `Accept`, `Content-MD5`, `Content-Type`, and `Date`.
PathAndParameters: This field contains the path and all parameters from the query and form. It is structured as follows:
Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueNImportantThe keys of query and form parameter pairs are sorted alphabetically and then concatenated as shown above.
If there are no query or form parameters, use only the path. Do not add a
?.If a parameter's value is empty, only include the key in the signature. Do not add the equal sign.
If an array parameter exists in the query or form (same key, different values), use the first value for the signature calculation.
Step 2: Encrypt the signature
After the client extracts the key data from the HTTP request and assembles the string-to-sign, it must encrypt the string and encode the result to generate the final signature. The encryption format is as follows, where stringToSign is the extracted string-to-sign, secret is the Secret Key (SK) from the Access Key (AK)/SK authentication configuration, and sign is the final generated signature:
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] secretBytes = secret.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(secretBytes, 0, secretBytes.length, "HmacSHA256"));
byte[] result = hmacSha256.doFinal(stringToSign.getBytes("UTF-8"));
String sign = Base64.encodeBase64String(result);To summarize: Encode stringToSign using UTF-8 to obtain a byte array. Encrypt the byte array with the specified algorithm. Then, encode the result using Base64 to generate the final signature.
Step 3: Add the signature
The client must include the following four headers in the HTTP request sent to API Gateway for signature verification:
x-ca-key: The value is the Access Key (AK) from the AK/SK authentication configuration.
x-ca-signature-method: The signature algorithm. Valid values are `HmacSHA256` or `HmacSHA1`. This is optional. The default value is `HmacSHA256`.
x-ca-signature-headers: A comma-separated list of all signed header keys. This is optional.
x-ca-signature: The signature. This is required.
Verify the signature on the server
Server-side client signature verification overview:
Extract the string-to-sign: Extract key data from the received request to create a string for signing.
Extract the AK: Read the AK from the received request and use it to find the corresponding SK.
Calculate the signature: Use the encryption algorithm and the SK to encrypt the string-to-sign and generate a signature.
Verify the signature: Read the client's signature from the received request and compare it with the server-generated signature for consistency.
Error handling
If the gateway fails to verify a signature, it returns the server-side string-to-sign to the client in the X-Ca-Error-Message header of the HTTP response. You can identify the issue by comparing the client-side string-to-sign with the server-side string-to-sign. If the string-to-sign values from the client and server are the same, check the SK used for signature calculation. Because HTTP headers cannot contain line feeds, the line feeds in the string-to-sign are replaced with #, as shown below:
X-Ca-Error-Message: Server StringToSign:`GET#application/json##application/json##X-Ca-Key:200000#X-Ca-Timestamp:1589458000000#/app/v1/config/keys?keys=TEST`Related error codes
HTTP status code | Error message | Cause |
401 | Invalid Key | The `x-ca-key` header is not provided or is invalid. |
401 | Empty Signature | The `x-ca-signature` header is not provided. |
400 | Invalid Signature | The signature in the `x-ca-signature` header does not match the signature calculated by the server. |
400 | Invalid Content-MD5 | The `content-md5` header is incorrect. |
400 | Invalid Date | The time offset calculated from the `date` header exceeds the configured `date_offset`. |
413 | Request Body Too Large | The request body exceeds the size limit of 32 MB. |
413 | Payload Too Large | The request body exceeds the global `DownstreamConnectionBufferLimits` configuration. |
403 | Unauthorized Consumer | The caller does not have access permissions. |
Example code (Go)
go
package main
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
"time"
)
func generateHMACSignature(toSign string, key string) (string, error) {
h := hmac.New(sha256.New, []byte(key))
h.Write([]byte(toSign))
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
}
func test(accessKey, secretKey string) {
body := `{"hello":"world"}`
h := md5.New()
h.Write([]byte(body))
payload := base64.StdEncoding.EncodeToString(h.Sum(nil))
headers := map[string]string{
"accept": "application/json",
"content-type": "application/json",
"date": time.Now().Format("2006-01-02 15:04:05"),
"x-ca-key": accessKey,
"foo": "bar",
"x-ca-signature-headers": "foo",
"content-md5": payload,
}
sts := strings.Join([]string{"POST", headers["accept"], headers["content-md5"], headers["content-type"], headers["date"], "foo:bar", "/post"}, "\n")
fmt.Printf("String to sign is: %s\n", strings.ReplaceAll(sts, "\n", "#"))
sign, _ := generateHMACSignature(sts, secretKey)
fmt.Printf("Signed string is: %s\n", sign)
headers["x-ca-signature"] = sign
req, _ := http.NewRequest("POST", "http://localhost:8080/post", bytes.NewBufferString(body))
for k, v := range headers {
req.Header.Add(k, v)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("read body error")
}
defer resp.Body.Close()
fmt.Println("Headers are as follows:")
for k, v := range resp.Header {
// If signature calculation fails, the X-Ca-Error-Message response header will contain the string-to-sign calculated by the server. You can use this to troubleshoot the issue.
fmt.Printf(" %s: %s\n", k, v)
}
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
func main() {
test("appKey", "appSecret")
}
API key authentication
Requests are verified based on the configured credential source. The process is similar for APIs and routes. The following examples use a route.
There are three main types of credential sources for an API key:
Default credential source: `Authorization: Bearer `.
Custom Header: Enter the header parameter name.
Custom Query parameter: Enter the query parameter name.
Default credential source
Assume the following request matches route `abc`. Set the API key in the `Authorization` HTTP request header and add the prefix `Bearer`. Note that there is a space between `Bearer` and the API key.
curl http://xxx.test.com/test -H 'x-api-key: Bearer 2bda943c-ba2b-11ec-ba07-00163e1250b5'Set the API key in the HTTP request header
curl http://xxx.test.com/test -H 'x-api-key: Bearer 2bda943c-ba2b-11ec-ba07-00163e1250b5'Custom header source
Assume that the following request matches route `abc`, and the API key is set in a custom HTTP request header.
If the route does not have a consumer authentication and authorization policy enabled, access is denied with a 401 error.
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5If the route has a consumer authentication and authorization policy enabled but is not authorized, a 403 error is returned.
curl http://xxx.test.com/test -H 'x-api-key: 2bda943c-ba2b-11ec-ba07-00163e1250b5'
Custom query parameter
Assume that the following request matches route `abc`, and the API key is set in a URL query parameter.
If the route does not have a consumer authentication and authorization policy enabled, access is denied with a 401 error.
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5If the route has a consumer authentication and authorization policy enabled but is not authorized, a 403 error is returned.
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5
Related error codes
HTTP status code | Error message | Cause |
401 | Key authentication check failed. Multiple API keys were found in the request. | You can request multiple API keys. |
401 | Key authentication check failed. No API key was found in the request. | The request does not contain an API key. |
401 | Key authentication check failed. The API key is invalid. | The API key is not authorized to access the resource. |
403 | Key authentication check failed. The consumer is unauthorized. | The consumer does not have the required access permissions. |
References
For more information, see Authorization management.