Alibaba Cloud API Gateway uses JSON Web Token (JWT) to authorize API access based on your own user system, enabling custom security settings.
1. Token-based authentication
Overview
Many public APIs must identify callers to determine whether to grant access to the requested resources. A token is a mechanism for authentication. With token-based authentication, an application does not need to store user authentication or session information on the server, enabling stateless, distributed web application authorization and simplifying application scaling.
Workflow
API Gateway uses the JWT authentication plug-in to handle authentication. The workflow is as follows:
-
A client sends a request that includes a token to API Gateway.
-
API Gateway uses the public key configured in the JWT authentication plug-in to verify the token. If the token is valid, API Gateway passes the request to the backend service.
-
The backend service processes the request and returns a response.
-
API Gateway returns the response from the backend service to the client.
Throughout this process, API Gateway uses token authentication to authorize API access based on your own user system. The following sections describe JSON Web Token (JWT), which API Gateway uses for authentication.
JWT
1.1 Overview
JSON Web Token (JWT) is a JSON-based open standard (RFC 7519) for securely transmitting claims between parties in a web application environment. A JWT serves as a self-contained authentication token that can include user identity, roles, and permissions. It can also contain additional claims required by business logic, making JWTs ideal for authentication in distributed applications.
1.2 JWT structure
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
As shown in the preceding example, a JWT is a string that consists of three parts:
-
Header
-
Payload
-
Signature
Header
The Header contains two parts:
-
The type of the token, which is JWT.
-
The signing algorithm being used.
A complete Header is a JSON object, as shown in the following example:
{
'typ': 'JWT',
'alg': 'HS256'
}
The Header is then Base64Url-encoded to form the first part of the JWT.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
The Payload contains claims.
iss: Issuer. The principal that issued the token. This claim is a string.
sub: Subject. The principal that is the subject of the token. This value is unique within the scope of the issuer. It is a case-sensitive string with a maximum length of 255 ASCII characters.
aud: Audience. The recipients that the token is intended for. This is a case-sensitive array of strings.
exp: Expiration Time. The time after which the token is considered invalid. This claim is an integer that represents the number of seconds from 1970-01-01T00:00:00Z.
iat: Issued At. The time at which the token was issued. This claim is an integer that represents the number of seconds from 1970-01-01T00:00:00Z.
jti: JWT ID. A unique identifier for the token. This value is unique for each token created by the same issuer and is often a cryptographically random value to prevent collisions. This value adds a random entropy component to the structured token that an attacker cannot obtain, which helps prevent token guessing and replay attacks.
You can also add custom claims that your user system requires. For example, you can add a name claim for a user's nickname:
{
"sub": "1234567890",
"name": "John Doe"
}
The Payload is then Base64Url-encoded to form the second part of the JWT:
JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE
Signature
To create the Signature, join the Base64Url-encoded Header and Payload with a period (.). Then, sign the resulting string using the algorithm specified in the Header and a private key ($secret). This forms the third part of the JWT.
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '$secret');
Join the three parts with a period (.) to create the complete JWT, as shown in the initial JWT example.
1.3 Authorization scope and validity
API Gateway considers a token authorized to access all APIs bound to the JWT plug-in within an API group. For finer-grained access control, your backend service must parse the token and perform authorization. API Gateway validates the exp field in the token. If the token has expired, API Gateway rejects the request immediately. You must set an expiration time, and the value must be less than 7 days.
1.4 Key characteristics of JWT
-
By default, JWTs are not encrypted. Do not include sensitive data in a JWT.
-
A JWT can be used for both authentication and information exchange, reducing the number of database queries on the server. The main disadvantage is its stateless nature: you cannot revoke a token or change its permissions before it expires. Once issued, a JWT remains valid until expiration unless the server implements custom revocation logic.
-
A JWT contains authentication information. If leaked, anyone who obtains the token gains all associated permissions. To reduce this risk, set a short validity period for JWTs. For high-security actions, re-authenticate the user before granting access.
-
To reduce the risk of theft, do not transmit JWTs in plaintext over HTTP. Use HTTPS instead.
2. Protect APIs with the JWT plug-in
2.1 Generate a JSON Web Key (JWK) pair
2.1.1 Generate a key pair online
You can visit https://tools.top/jwt-encode.html to generate a private key and a public key for token generation and verification. The private key is used by the authorization service to issue JWTs, and the public key is configured in the JWT authentication plug-in for API Gateway to verify request signatures. API Gateway supports the RSA SHA256 algorithm and a key size of 2048 bits.
After you open the site, select the JWK format tab. The page automatically generates a key pair. Click Copy Public Key or Copy Private Key to obtain the corresponding JWK content.
2.1.2 Generate a key pair locally
The following example uses Java. You can find similar tools in other programming languages to generate key pairs. 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>
Use the following code to generate an RSA key pair:
RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
rsaJsonWebKey.setKeyId("authServer");
final String publicKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.PUBLIC_ONLY);
final String privateKeyString = rsaJsonWebKey.toJson(JsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
2.2 Implement a token issuance service
Use the public key JWK JSON string generated online in Section 2.1 or the locally generated privateKeyString JSON string as the private key to issue tokens. These tokens authorize trusted users to access protected APIs. For more information, see Sample code for the token issuance and authentication service. How you issue tokens depends on your business scenarios. You can deploy the token issuance feature to a production environment as a regular API that allows visitors to obtain a token with a username and password, or generate a token locally and provide it directly to a specific user.
2.3 Configure the JWT plug-in
-
Log on to the API Gateway console.
-
In the left-side navigation pane, choose .
-
On the plug-in management page, click Create Plug-in in the upper-right corner.
-
On the Create Plug-in page, set Plug-in Name and select a Plug-in Type. The following code is an example configuration for a JWT authentication plug-in. For more information about the configuration, see JWT authentication plug-in.
---
parameter: X-Token # The parameter from which to retrieve the JWT. This corresponds to an API parameter.
parameterLocation: header # The location from which to read the JWT. This parameter is optional if the API is in mapping mode, but required if the API is in pass-through mode. Valid values: `query` and `header`.
claimParameters: # Claim parameter mapping. API Gateway maps JWT claims to backend parameters.
- claimName: aud # The name of the claim. Public and private claims are supported.
parameterName: X-Aud # The name of the mapped parameter.
location: header # The location of the mapped parameter. Valid values: `query`, `header`, `path`, and `formData`.
- claimName: userId # The name of the claim. Public and private claims are supported.
parameterName: userId # The name of the mapped parameter.
location: query # The location of the mapped parameter. Valid values: `query`, `header`, `path`, and `formData`.
preventJtiReplay: false # Specifies whether to enable anti-replay checks for the `jti` claim. Default value: false.
# The public key of the JSON Web Key (JWK) generated in section 2.1.
jwk:
kty: RSA
e: AQAB
use: sig
kid: uniq_key
alg: RS256
n: qSVxcknOm0uCq5vGsOmaorPDzHUubBmZZ4UXj-9do7w9X1uKFXAnqfto4TepSNuYU2bA_-tzSLAGBsR-BqvT6w9SjxakeiyQpVmexxnDw5WZwpWenUAcYrfSPEoNU-0hAQwFYgqZwJQMN8ptxkd0170PFauwACOx4Hfr-9FPGy8NCoIO4MfLXzJ3mJ7xqgIZp3NIOGXz-GIAbCf13ii7kSStpYqN3L_zzpvXUAos1FJ9IPXRV84tIZpFVh2lmRh0h8ImK-vI42dwlD_hOIzayL1Xno2R0T-d5AwTSdnep7g-Fwu8-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ
2.4 Bind the JWT plug-in to an API
On the Plug-ins page, find the JWT authentication plug-in that you created and click Bind API. In the dialog box, select APIs from the specified API group and environment, add them to the list on the right, and then click OK.
If an API is already bound to a plug-in of the same type, the new plug-in overwrites the existing one. Proceed with caution.
The API debugging feature in the API Gateway console does not support the JWT authentication plug-in. To test APIs bound to the plug-in, use a tool such as Postman or run the curl command.
3. Sample code for a token issuance service
import java.security.PrivateKey;
import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
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;
public class GenerateJwtDemo {
public static void main(String[] args) throws JoseException {
// Use the keyId that is set in API Gateway.
String keyId = "uniq_key";
// Use the key pair that you generated in Section 2.1.
String privateKeyJson = "{\n"
+ " \"kty\": \"RSA\",\n"
+ " \"d\": "
+
"\"O9MJSOgcjjiVMNJ4jmBAh0mRHF_TlaVva70Imghtlgwxl8BLfcf1S8ueN1PD7xV6Cnq8YenSKsfiNOhC6yZ_fjW1syn5raWfj68eR7cjHWjLOvKjwVY33GBPNOvspNhVAFzeqfWneRTBbga53Agb6jjN0SUcZdJgnelzz5JNdOGaLzhacjH6YPJKpbuzCQYPkWtoZHDqWTzCSb4mJ3n0NRTsWy7Pm8LwG_Fd3pACl7JIY38IanPQDLoighFfo-Lriv5z3IdlhwbPnx0tk9sBwQBTRdZ8JkqqYkxUiB06phwr7mAnKEpQJ6HvhZBQ1cCnYZ_nIlrX9-I7qomrlE1UoQ\",\n"
+ " \"e\": \"AQAB\",\n"
+ " \"kid\": \"myJwtKey\",\n"
+ " \"alg\": \"RS256\",\n"
+ " \"n\": \"vCuB8MgwPZfziMSytEbBoOEwxsG7XI3MaVMoocziP4SjzU4IuWuE_DodbOHQwb_thUru57_Efe"
+
"--sfATHEa0Odv5ny3QbByqsvjyeHk6ZE4mSAV9BsHYa6GWAgEZtnDceeeDc0y76utXK2XHhC1Pysi2KG8KAzqDa099Yh7s31AyoueoMnrYTmWfEyDsQL_OAIiwgXakkS5U8QyXmWicCwXntDzkIMh8MjfPskesyli0XQD1AmCXVV3h2Opm1Amx0ggSOOiINUR5YRD6mKo49_cN-nrJWjtwSouqDdxHYP-4c7epuTcdS6kQHiQERBd1ejdpAxV4c0t0FHF7MOy9kw\"\n"
+ "}";
JwtClaims claims = new JwtClaims();
claims.setGeneratedJwtId();
claims.setIssuedAtToNow();
// The expiration time must be set and be less than 7 days.
NumericDate date = NumericDate.now();
date.addSeconds(120*60);
claims.setExpirationTime(date);
claims.setNotBeforeMinutesInThePast(1);
claims.setSubject("YOUR_SUBJECT");
claims.setAudience("YOUR_AUDIENCE");
// Add custom parameters. All values must be of the String type.
claims.setClaim("userId", "1213234");
claims.setClaim("email", "userEm***@youapp.com");
JsonWebSignature jws = new JsonWebSignature();
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
// This parameter is required.
jws.setKeyIdHeaderValue(keyId);
jws.setPayload(claims.toJson());
PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyJson)).getPrivateKey();
jws.setKey(privateKey);
String jwtResult = jws.getCompactSerialization();
System.out.println("Generate Json Web token , result is " + jwtResult);
}
}
Note the following points:
-
The
keyIdmust be globally unique and must match in the following three locations:
-
The
keyIdthat you specify when you generate a key pair in the 2.1 Generate a JSON Web Key (JWK) pair section. -
The
kidthat you configure in the JWT authentication plug-in in the 2.3 Configure the public key in the JWT authentication plug-in section. -
The
keyIdin the code, which is the value of theKeyIdHeaderValueproperty of theJsonWebSignatureobject. This property is required.
-
For
privateKeyJson, use the private key's JWK JSON string. This can be the one generated online in Section 2.1 or theprivateKeyStringJSON string if you generated the key pair locally.privateKeyStringis a JSON string. -
The validity period is required and must be less than seven days.
-
Use String values for all custom parameters.
4. API Gateway error responses
|
Status |
Code |
Message |
Description |
|
400 |
I400JR |
JWT required |
JWT parameter not found. |
|
403 |
S403JI |
Claim jti is required when preventJtiReplay:true |
The |
|
403 |
S403JU |
Claim jti in JWT is used |
The provided |
|
403 |
A403JT |
Invalid JWT: ${Reason} |
The JWT provided in the request is invalid. |
|
400 |
I400JD |
JWT Deserialize Failed: ${Token} |
Failed to deserialize the JWT provided in the request. |
|
403 |
A403JK |
No matching JWK, kid:${kid} not found |
The |
|
403 |
A403JE |
JWT is expired at ${Date} |
The JWT provided in the request has expired. |
|
400 |
I400JP |
Invalid JWT plugin config: ${JWT} |
The JWT authentication plug-in is configured incorrectly. |
If you receive an unexpected status code, check the X-Ca-Error-Code response header for the ErrorCode and the X-Ca-Error-Message header for the ErrorMessage. If the error code is A403JT or I400JD, use jwt.io to verify the validity and format of your token.