JWT-based token authentication

更新时间:
复制 MD 格式

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

image

API Gateway uses the JWT authentication plug-in to handle authentication. The workflow is as follows:

  1. A client sends a request that includes a token to API Gateway.

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

  3. The backend service processes the request and returns a response.

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

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

  1. By default, JWTs are not encrypted. Do not include sensitive data in a JWT.

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

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

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

  1. Log on to the API Gateway console.

  2. In the left-side navigation pane, choose API Management > Plug-in Management.

  3. On the plug-in management page, click Create Plug-in in the upper-right corner.

  4. 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:

  1. The keyId must be globally unique and must match in the following three locations:

  1. For privateKeyJson, use the private key's JWK JSON string. This can be the one generated online in Section 2.1 or the privateKeyString JSON string if you generated the key pair locally.

    privateKeyString is a JSON string.

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

  3. 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 jti claim is missing, but anti-replay checks are enabled.

403

S403JU

Claim jti in JWT is used

The provided jti has been used, and anti-replay checks are enabled.

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 kid in the JWT does not match a configured JWK.

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.