Cloud-native API Gateway supports global authentication, route-level authentication, and consumer authorization. This topic describes how to configure each authentication method and authorize consumers to access specific routes and APIs.
Background
Global authentication suits B2C scenarios such as unified logon. Consumer authentication for routes and APIs suits B2B scenarios such as granting API access to partners.
|
Item |
Global authentication |
Route authentication and consumer authorization |
|
Use cases |
B2C scenarios, such as unified logon authentication. |
B2B scenarios, such as granting API access to partners. |
|
Key difference |
Authorization is enabled when authentication is enabled. |
After you enable authentication, you must configure authorization separately. |
|
Configuration path |
. |
|
|
Authentication method configuration (JWT authentication example) |
|
|
|
Authorization method configuration |
When you create a configuration, provide a list of Domain Name and Path for a blacklist or whitelist.
|
|
Usage notes
After you enable consumer authentication, the policy takes effect immediately. If no consumer or authorization rule is configured for a published route or API, the gateway denies all requests by default.
Use consumer authentication and authorization
JWT authentication
JWT authentication workflow
-
The client sends an authentication request to the gateway, typically with the end user's username and password.
-
The gateway forwards the request to the backend service.
-
The backend validates the credentials. After successful validation, it generates a token with a private key and returns the token to the gateway.
-
The gateway returns the token to the client. The client caches this token locally.
-
The client sends a business request with the cached token to the gateway.
-
The gateway verifies the token using the configured public key. If valid, the gateway forwards the request to the backend.
-
The backend processes the request and returns a response.
-
The gateway forwards the response to the client.
The following sections describe how to generate a token, send a request to the gateway, and validate the token using the configured public key.
Generate a token
The following Java example shows how to generate a token. You can use similar tools in other languages.
-
Create a Maven project and add the dependency.
Add the following dependency to the project:
<dependency> <groupId>org.bitbucket.b_c</groupId> <artifactId>jose4j</artifactId> <version>0.7.0</version> </dependency> -
Choose a method to generate the token.
You can generate a token using either a symmetric or asymmetric key.
Symmetric key
Sample code:
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 identifier. 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("Generate Json Web token , result is \n " + jwtResult); } }Code settings:
-
privateKeyJson: The JWKS used when creating the consumer. Save the JWKS during consumer creation, or retrieve it from the consumer's basic configuration page. -
Set the consumer identifier. The statement is
claims.setClaim("uid", "11215ac069234abcb8944232b79ae711"). This identifier is auto-generated when you create a consumer but can be modified. You can also retrieve it from the consumer's basic configuration page. -
Set the encryption algorithm. The statement is
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256). This algorithm must match the one in the JWKS.NoteSupported encryption algorithms include ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512, HS256, HS384, HS512, and EdDSA.
For symmetric encryption, the value of "k" must be decoded.
jws.setKey(new HmacKey(Base64.decode(JsonUtil.parseJson(privateKeyJson).get("k").toString())));
-
Set the expiration time. The token must expire within seven days. After expiration, generate a new token.
... NumericDate date = NumericDate.now(); date.addSeconds(120*60); claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); ... -
You can add custom parameters to the JWKS PAYLOAD based on your needs.
Asymmetric key
Sample code:
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 { // Sample 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 expiration to 120 minutes from now. claims.setExpirationTime(date); claims.setNotBeforeMinutesInThePast(1); // Set the not before (nbf) time to 1 minute in the past. // Add custom parameters. All values must be of the String type. // Set the consumer identifier. 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("Generate Json Web token , result is \n " + jwtResult); } }Code settings:
-
The settings for
privateKeyJson, the consumer identifier, and the expiration time are similar to those for symmetric encryption.Set the encryption algorithm by using the statement
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256). This algorithm must match the one in the JWKS.For an asymmetric encryption algorithm, you must use its private key for encryption.
... 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.
-
Send business requests
Cloud-native API Gateway supports passing the token in a request header. You can customize the header name and token prefix. The key and prefix in the request must match those configured in the consumer's authentication method.
-
Requests without a JWT return a 401 error.
curl http://xxx.hello.com/test -
Requests with an invalid JWT return a 401 error.
curl http://xxx.hello.com/test -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ1' -
If a request includes a valid JWT but the consumer is not authorized for the API or route, a 403 error is returned.
# consumer1 is not authorized for the route or API at the specified path. curl 'http://xxx.example.com/test' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxODY1NjczODE5fQ.-vBSV0bKeDwQcuS6eeSZN9dLTUnSnZVk8eVCXdooCQ4'
Server-side token validation
Token validation has three steps:
-
Check whether the request includes a token. If not, reject with 401.
-
Verify the token's validity and expiration using the public key from the consumer's JWKS. If invalid or expired, reject with 401.
-
Check whether the consumer is authorized to access the requested API or route.
Common error codes
|
HTTP status code |
Error message |
Description |
|
401 |
Jwt missing |
The JWT is missing from the request header. |
|
401 |
Jwt expired |
The JWT has expired. |
|
401 |
Jwt verification fails |
JWT payload verification failed, for example, due to an |
|
403 |
Access Denied |
No permission to access the current route. |
AK/SK (HMAC) authentication
Client-side signature generation
The client generates a signature in three steps:
-
Extract the string to sign: Extract key data from the original request to form a string for signing.
-
Create the signature: Encrypt the string to sign with the SK to produce the final signature.
-
Add the signature: Add all signature-related headers to the original HTTP request to form the final request.
Step 1: Extract the string to sign
Extract key data from the HTTP request and assemble it into a string to sign in the following format:
HTTPMethod
Accept
Content-MD5
Content-Type
Date
Headers
PathAndParameters
These seven fields constitute the string to sign, separated by newline characters (\n). If the Headers field is empty, no newline is needed after it. For any other empty field, include its newline character. The signature is case-sensitive. Each field is extracted as follows:
-
HTTPMethod: The HTTP method, in uppercase. Example:
POST. -
Accept: The value of the Accept header. Can be empty. We recommend that you set the Accept header explicitly. If empty, some HTTP clients set a default value of
*/*, which causes signature verification to fail. -
Content-MD5: The value of the
Content-MD5header. Can be empty. Calculate this only for requests with a non-form body. The following Java code shows how to calculate theContent-MD5value:String content-MD5 = Base64.encodeBase64(MD5(bodyStream.getbytes("UTF-8"))); -
Content-Type: The value of the
Content-Typeheader. Can be empty. -
Date: The value of the Date header. If
date_offsetis not enabled, this can be empty. Otherwise, it is used for time offset verification. -
Headers: Select specific headers to include in the signature. Concatenate them as follows:
-
Sort the header keys alphabetically and concatenate them:
HeaderKey1 + ":" + HeaderValue1 + "\n"\+ HeaderKey2 + ":" + HeaderValue2 + "\n"\+ ... HeaderKeyN + ":" + HeaderValueN + "\n"
-
If a header's value is empty, use
HeaderKey+":"+"\n"for the signature, retaining the key and the colon. -
List the keys of all signed headers in the
X-Ca-Signature-Headersheader, separated by commas. -
The following headers are not included in the signature calculation:
X-Ca-Signature,X-Ca-Signature-Headers,Accept,Content-MD5,Content-Type, andDate.
-
-
PathAndParameters: Includes the path, query parameters, and form parameters. Constructed as follows:
Path + "?" + Key1 + "=" + Value1 + "&" + Key2 + "=" + Value2 + ... "&" + KeyN + "=" + ValueNImportant-
Sort the query and form parameter keys alphabetically before concatenating.
-
If query and form parameters are empty, use the Path directly without a
?. -
If a parameter's value is empty, include only the key. The equals sign (
=) is not included. -
If the query or form contains array parameters (same key, different values), use the first value for signature calculation.
-
Step 2: Create the signature
Encrypt and encode the string to sign to produce the final signature. In the following example, stringToSign is the extracted string, secret is the SK from the AK/SK configuration, and sign is the 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);
Decode stringToSign into a UTF-8 byte array, encrypt it, and Base64-encode the result to produce the signature.
Step 3: Add the signature to the request
Include the following four headers in the HTTP request for signature verification:
-
x-ca-key: The AK from the AK/SK authentication configuration. -
x-ca-signature-method: The signature algorithm. Valid values areHmacSHA256orHmacSHA1. This is optional and defaults toHmacSHA256. -
x-ca-signature-headers: A comma-separated list of all header keys included in the signature. This is optional. -
x-ca-signature: The signature. This is required.
Server-side signature verification
Server-side signature verification works as follows:
-
Extract the string to sign: Extract key data from the received request to form a string for signing.
-
Retrieve the SK: Read the AK from the request and use it to retrieve the corresponding SK.
-
Calculate the signature: Use the same encryption algorithm and the retrieved SK to encrypt the string to sign, which produces a server-side signature.
-
Verify the signature: Read the client-side signature from the request and compare it with the server-side signature for a match.
Troubleshooting
When signature verification fails, the server returns its string to sign (StringToSign) in the X-Ca-Error-Message response header. Compare the server-side StringToSign with the one you calculated locally. If they match, check whether you used the correct SK. Because HTTP headers cannot contain newline characters, newlines in the StringToSign are replaced with #:
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 |
Description |
|
401 |
Invalid Key |
The |
|
401 |
Empty Signature |
The |
|
400 |
Invalid Signature |
The signature in the |
|
400 |
Invalid Content-MD5 |
The |
|
400 |
Invalid Date |
The time offset calculated from the |
|
413 |
Request Body Too Large |
The request body size exceeds the 32 MB limit. |
|
413 |
Payload Too Large |
The request body exceeds the globally configured |
|
403 |
Unauthorized Consumer |
The caller of the request is not authorized. |
Sample 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 verification fails, the X-Ca-Error-Message response header
// contains the server-side string to sign, which can be used for troubleshooting.
fmt.Printf(" %s: %s\n", k, v)
}
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
func main() {
test("appKey", "appSecret")
}
API key authentication
The gateway authenticates requests based on the configured credential source. The process is similar for APIs and routes. The following examples use routes.
Three credential source types are available for API keys:
-
Default credential source:
Authorization: Bearer <token>. -
Custom header: Provide a header name.
-
Custom query parameter: Provide a query parameter name.
Default credential source
Assume a request matches the route abc. The API key is provided in the Authorization header with the Bearer prefix (note the trailing space).
curl http://xxx.test.com/test -H 'Authorization: Bearer 2bda943c-ba2b-11ec-ba07-00163e1250b5'
Set the API key in the request header
curl http://xxx.test.com/test -H 'x-api-key: 2bda943c-ba2b-11ec-ba07-00163e1250b5'
Custom header source
Assume the request matches the route abc, and the API key is in a custom header.
-
If the API key is in the wrong location (for example, as a query parameter instead of the configured custom header), the request is denied with 401.
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5 -
If the policy is enabled but the consumer is not authorized, the request is denied with 403.
curl http://xxx.test.com/test -H 'x-api-key: 2bda943c-ba2b-11ec-ba07-00163e1250b5'
Custom query parameter
Assume the request matches the route abc, and the API key is in a URL parameter.
-
If consumer authentication is not enabled for the route, the request is denied with 401.
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5 -
If the policy is enabled but the consumer is not authorized, the request is denied with 403.
curl http://xxx.test.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5
Related error codes
|
HTTP status code |
Error message |
Description |
|
401 |
Request denied by Key Auth check. Multi API key found in request. |
Multiple API keys were provided in the request. |
|
401 |
Request denied by Key Auth check. No API key found in request. |
No API key was provided in the request. |
|
401 |
Request denied by Key Auth check. Invalid API key. |
The provided API key is not allowed access. |
|
403 |
Request denied by Key Auth check. Unauthorized consumer. |
The caller of the request is not authorized. |
References
You can authorize and manage your APIs through Authorization management.