All Products
Search
Document Center

JWT-based authentication

Last Updated: Sep 24, 2021

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 requesters who make API calls and determines whether to return requested resources to the requesters. Tokens are a mechanism used for identity authentication. Based on this mechanism, apps do not need to retain user authentication information or session information on the server side. This implements stateless and distributed web app authorization and facilitates app extension.

1.2 Procedure

The preceding figure shows a procedure that is used to implement JWT-based authentication. Detailed procedure description:

  1. The client sends an authentication request to API Gateway. In most cases, the username and password of the user are included in the request.

  1. API Gateway forwards the authentication request to the backend service.

  1. The backend service reads and verifies the authentication information, such as the username and password, in the authentication request. After the request passes the verification, the backend service uses a private key to generate a standard token and returns a token response to API Gateway.

  1. API Gateway forwards the token response to the client. The client locally caches the token.

  1. The client sends a business request to API Gateway. The token is included in the business request.

  1. API Gateway uses a configured public key to verify the token in the business request. If the request passes the verification, API Gateway transparently forwards the business request to the backend service.

  1. The backend service handles the business request and sends a business response to API Gateway.

  1. API Gateway forwards the business response to the client.

In the procedure, 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 an open standard (RFC 7519) that defines a compact and self-contained way to securely transmit information between parties. The information is encoded as a JSON object. 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. This is 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

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 you encoded 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 you add a random entropy component that cannot be obtained by an attacker to the structured JWT. 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

The encryption algorithm specified in the header is used to encrypt the string that is composed of the Base64-encoded header and payload to form the third part of the JWT. The header and payload are concatenated by a period (.). $secret specifies a 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. The JWT is formed.

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

  1. By default, a JWT is unencrypted. Do not write secret data to the JWT.

  1. 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.

  1. 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 the validity period to a short period for the JWT. Authenticate users who use important permissions.

  1. 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 a Java example. 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

  1. Log on to the API Gateway console.

  1. In the left-side navigation pane, choose Publish APIs > Plugin.

  1. In the upper-right corner of the Plugins list page, click Create Plugin.

  1. On the Create Plugin page, set Plugin 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. It 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 Request Parameter Mapping(Filter Unknown Parameters) or Request Parameter Mapping(Passthrough Unknown Parameters). This parameter is required if Request Mode for the bound API is set to Request Parameter Passthrough.
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 APIs to the JWT authentication plug-in

On the Plugins list page, find the JWT authentication plug-in you created and click Bind API in the Operation column. In the Bind API dialog box, add the required APIs that are in specific API groups and published to specified environments to the Selected API(s) 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", "userEmail@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:

  1. 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.

  1. You must set privateKeyJson to the Keypair JSON string that is generated by using method 1 or the privateKeyString JSON string that is generated by using method 2 in the "Generate a JWK pair" section.

  1. The validity period is required and must be less than seven days.

  1. When you add custom parameters, all parameter values must be of the STRING type.