Alibaba Cloud API Gateway provides a mechanism for authorized access to your APIs based on a JSON Web Token (JWT). You can use this mechanism to customize security settings.
1. Token-based authentication
1.1 Overview
API Gateway verifies the identities of API callers who call open APIs and determines whether to return requested resources to the callers based on the verification result. Tokens are a mechanism used for identity authentication. Based on this mechanism, applications do not need to retain user authentication information or session information on the server side. This implements stateless and distributed web application authorization and facilitates application extension.
1.2 Procedure
The preceding figure shows the workflow of API Gateway using the JWT authentication plug-in to implement authentication. Description:
The client sends a request to API Gateway. The request contains a token.
API Gateway uses the public key configured in the JWT authentication plug-in to verify the token in the request. If the request passes the verification, API Gateway passes the request to the backend service.
The backend service processes the request and returns a response.
API Gateway returns the backend service response to the client.
In the process, API Gateway allows you to use your own user system to implement token-based API access authentication. The following section describes the structured JWT that is used by API Gateway for authentication.
1.3 JWT
1.3.1 Overview
JWT is a JSON-based open standard (RFC 7519) that is used to transmit declarations between network application environments. A JWT can serve as an independent authentication token, which contains information such as the user ID, user role, and permissions, to help clients obtain resources from the resource server. A JWT can also provide additional claim information required for other business scenarios. JWTs are suitable for logons in distributed sites.
1.3.2 Composition of a JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
As described in the preceding example, a JWT is a string that consists of the following parts:
Header
Payload
Signature
Header
The header consists of the following parts:
Type of the token, which is JWT
Encryption algorithm
Example of a complete header in the JSON format:
{
'typ': 'JWT',
'alg': 'HS256'
}
The header is Base64 encoded to form the first part of the JWT. The header that you encode can be symmetrically decoded.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
Payload contains valid information specified by a set of claims. Claims:
iss: token issuer. This claim is a string.
sub: Subject Identifier. The identifier of a user. The value of this claim is unique. This claim can contain a maximum of 255 ASCII characters that are case-sensitive.
aud: Audience. Recipients for which the JWT is intended. The value of this claim is a string array that is case-sensitive.
exp: Expiration Time. The timestamp at which the token expires. When the timestamp is reached, the token becomes invalid. This claim is an integer representing the number of seconds that have elapsed since the epoch time January 1, 1970, 00:00:00 UTC.
iat: the time the token was issued. This claim is an integer representing the number of seconds that have elapsed since the epoch time January 1, 1970, 00:00:00 UTC.
jti: the unique identifier of the token. The value of this claim is a cryptographic random value to prevent conflicts. This claim functions in the same way as a random entropy component that is added to the structured JWT and cannot be obtained by an attacker. This helps prevent token guessing attacks and replay attacks.
You can also add custom claims. In the following example, the name
claim is added:
{
"sub": "1234567890",
"name": "John Doe"
}
The payload is Base64 encoded to form the second part of the JWT.
JTdCJTBBJTIwJTIwJTIyc3ViJTIyJTNBJTIwJTIyMTIzNDU2Nzg5MCUyMiUyQyUwQSUyMCUyMCUyMm5hbWUlMjIlM0ElMjAlMjJKb2huJTIwRG9lJTIyJTBBJTdE
Signature
This is the third part of the JWT. This part uses the string that is concatenated from the Base64-encoded header and the Base64-encoded payload by using a period (.
). This part is encrypted by using the encryption method declared in the header. $secret
indicates the user private key.
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '$secret');
These three parts are concatenated by periods (.
) to form a complete string, which is the JWT example provided in this section.
1.3.3 Authorization scope and validity period
In API Gateway, the issued token can be used to access all APIs that are bound to a JWT authentication plug-in in a specific API group. If fine-grained permission management is required, the backend service must verify the token for authentication. For the validity of a token, API Gateway checks the exp claim in the token in each API request. If the token has expired, API Gateway considers the token invalid and rejects the API request. You must specify a validity period for tokens. The validity period must be less than seven days.
1.3.4 Characteristics of a JWT
By default, a JWT is unencrypted. Do not write secret data to the JWT.
A JWT can be used to authenticate identities or exchange information. A JWT can help reduce the number of queries that are performed by the server on a specific database. The most noticeable disadvantage of JWTs is that the server cannot save the session status. Therefore, when a JWT is in use, you cannot revoke it or modify the permissions of the JWT. After a JWT is issued, it is valid until it expires. To invalidate the JWT, you must deploy new logic on the server.
A JWT contains authentication information. If the authentication information is disclosed, users can obtain all the permissions of the JWT. To reduce the possibility that a JWT is stolen, set a short validity period for the JWT. Authenticate users who use important permissions.
To reduce the possibility that a JWT is stolen, use HTTPS, instead of HTTP, to transmit data.
2. Use a JWT authentication plug-in to protect APIs
2.1 Generate a JWK pair
Method 1: online generation
Visit https://mkjwk.org. Specify a private key and a public key that are used to generate and verify a JWT. The private key is used by an authentication server to issue a JWT. The public key is configured in a JWT authentication plug-in for API Gateway to verify the signature of requests. API Gateway supports the 2048-bit RSA SHA256 encryption algorithm for the key pair.
Method 2: local generation
This topic provides an example in Java. Similar tools are provided for other programming languages to generate key pairs. Create a Maven project and add the following dependency to the project:
<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 Use the private key in the JWK pair to issue a token
Use the Keypair
JSON string that is generated by using method 1 or the privateKeyString
JSON string that is generated by using method 2 as a private key to issue a token. The token is used to authorize trusted users to access protected APIs. For more information, see the example in the "Sample code for an authentication server to issue a token" section.
The form of issuing a token is determined based on specific business requirements. You can deploy the token issuing feature to a production environment and configure a common API. Then, visitors can obtain the token by using a username and password. Alternatively, you can locally generate a token and copy the token for specific users.
2.3 Configure the public key in the JWK pair for a JWT authentication plug-in
Log on to the API Gateway console.
In the left-side navigation pane, choose Manage APIs > Plug-ins.
On the Plug-ins page, click
Create Plug-in
in the upper-right corner.On the Create Plug-in page, set Plug-in Type to
JWT Authorization
. The following example shows the configurations of a JWT authentication plug-in. For more information, see JWT authentication.
---
parameter: X-Token # The parameter from which the JWT is read. This parameter corresponds to a parameter in an API request.
parameterLocation: header # The location from which the JWT is read. Valid values: query and header. This parameter is optional if Request Mode for the bound API is set to Map (Filter Out Unknown Parameters) or Map (Pass-through Unknown Parameters). This parameter is required if Request Mode for the bound API is set to Pass-through.
claimParameters: # The claims to be converted into parameters. API Gateway maps JWT claims to backend parameters.
- claimName: aud # The name of the JWT claim, which can be public or private.
parameterName: X-Aud # The name of the backend parameter, to which the JWT claim is mapped.
location: header # The location of the backend parameter, to which the JWT claim is mapped. Valid values: query, header, path, and formData.
- claimName: userId # The name of the JWT claim, which can be public or private.
parameterName: userId # The name of the backend parameter, to which the JWT claim is mapped.
location: query # The location of the backend parameter, to which the JWT claim is mapped. Valid values: query, header, path, and formData.
preventJtiReplay: false # Controls whether to enable the anti-replay check for jti. Default value: false.
#
# `Public Key` in the `JSON Web Key` pair, which is generated in the "Generate a JWK pair" section
jwk:
kty: RSA
e: AQAB
use: sig
alg: RS256
n: qSVxcknOm0uCq5vGsOmaorPDzHUubBmZZ4UXj-9do7w9X1uKFXAnqfto4TepSNuYU2bA_-tzSLAGBsR-BqvT6w9SjxakeiyQpVmexxnDw5WZwpWenUAcYrfSPEoNU-0hAQwFYgqZwJQMN8ptxkd0170PFauwACOx4Hfr-9FPGy8NCoIO4MfLXzJ3mJ7xqgIZp3NIOGXz-GIAbCf13ii7kSStpYqN3L_zzpvXUAos1FJ9IPXRV84tIZpFVh2lmRh0h8ImK-vI42dwlD_hOIzayL1Xno2R0T-d5AwTSdnep7g-Fwu8-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ
2.4 Bind a JWT authentication plug-in to APIs
On the Plug-in List page, find the JWT authentication plug-in you created and click Bind API
in the Actions column. In the Bind API dialog box, specify an API group and environment to select APIs, add the APIs to the Selected APIs pane, and click OK
.
The API debugging feature in the API Gateway console does not support the JWT authentication plug-in. We recommend that you use Postman or run the curl
command in the command-line interface (CLI) to test the APIs that are bound to the JWT authentication plug-in.
3. Error codes
Status | Code | Message | Description |
400 | I400JR | JWT required | No JWT-related parameters are found. |
403 | S403JI | Claim jti is required when preventJtiReplay:true | No valid jti claim is included in the request when preventJtiReplay is set to true in a JWT authentication plug-in. |
403 | S403JU | Claim jti in JWT is used | The jti claim that is included in the request has been used when preventJtiReplay is set to true in a JWT authentication plug-in. |
403 | A403JT | Invalid JWT: ${Reason} | The JWT that is included in the request is invalid. |
400 | I400JD | JWT Deserialize Failed: ${Token} | The JWT that is read from the request failed to be parsed. |
403 | A403JK | No matching JWK, kid:${kid} not found | No JWK matches kid, which is configured in the JWT that is included in the request. |
403 | A403JE | JWT is expired at ${Date} | The JWT that is read from the request expired. |
400 | I400JP | Invalid JWT plugin config: ${JWT} | The JWT authentication plug-in is incorrectly configured. |
If an HTTP response message includes an unexpected response code specified by ErrorCode in the X-Ca-Error-Code header, such as A403JT or I400JD, you can visit the jwt.io website to check the token validity and format.
4. Sample code for an authentication server to issue a token
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 value of the keyId parameter that you specified when you configured basic information for the authorization API operation.
String keyId = "uniq_key";
// Use the key pair generated in the "Generate a JWK pair" section.
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 validity period is required and must be less than seven 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 parameter 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);
// The KeyIdHeaderValue 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);
}
}
Take note of the following items:
The value of the keyId parameter must be unique in API Gateway. You must keep the following values consistent for the keyId parameter:
The value of the keyId parameter that is specified in the "Generate a JWK pair" section.
The value of the keyId parameter that you specified when you configure basic information for the authorization API operation.
The value of the keyId parameter that is specified in code, which is the value of the KeyIdHeaderValue parameter in the JsonWebSignature object. The KeyIdHeaderValue parameter is required.
You must set privateKeyJson to the
Keypair
JSON string that is generated by using method 1 orthe
privateKeyString
JSON string that is generated by using method 2 in the "Generate a JWK pair" section.The validity period is required and must be less than seven days.
When you add custom parameters, all parameter values must be of the STRING type.