A service account is used for API authentication between servers. You can create a service account on the API Console of HUAWEI Developers, and then generate a JSON Web Token (JWT) in your app based on the public and private keys returned. With this JWT, your app can call Huawei public APIs that support service account–based authentication.
The JWT generated from a service account consists of the following parts:
Header
Payload
Signature
The three parts are concatenated through dots (.). You can obtain the signature by concatenating the header and payload and then signing the concatenated string using the SHA256withRSA/PSS algorithm.
Example
eyJra*****JjNjBjMXXX. eyJhd*****JodHRXXX. BRNss*****7az5oU7-Zp5g9X2WJVXXX
For more about JWT, please refer to Introduction to JSON Web Tokens.
Create the key file of a service account.
Select your project on the API Console of HUAWEI Developers, create a service account key, and download the service account key file to call Push Kit APIs. For details, please refer to API Console Operation Guide - Creating a Service Account Key.

The following is an example of the service account key file (with key information anonymized):
{
"project_id": "*****",
"key_id": "*****",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCKw6kJKtCh7qmMvp1u1dI27z2TKZrPOzHbQaXO/Eez0AWZ2EN+ouF496R3pfo7fQXC1XOT/YTbVC4DNZwWSMA54fu3/AOCY9Zzyi46OK*****==\n-----END PRIVATE KEY-----\n",
"sub_account": "*****",
"auth_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/authorize",
"token_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
"auth_provider_cert_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/certs",
"client_cert_uri": "https://oauth-login.cloud.huawei.com/oauth2/v3/x509?client_id="
} Ensure that the value of project_id in the key file is the same as the ID of the project to which your app belongs.
To check the actual project ID, sign in to AppGallery Connect, click Development and services, click the desired project, and go to Project settings.

Generate the header in a JWT string.
Generate a JSON body and encode it using Base64 to obtain the header. In the JSON body, the value of kid is the same as the value of key_id in the service account key file.
Example
{
"kid": "*****",
"typ": "JWT",
"alg": "PS256"
} | Field | Description |
|---|---|
| kid | The value is the same as that of the key_id field in the key file. |
| typ | Data type. The value is always JWT. |
| alg | Algorithm type. The value is always PS256. |
Generate the payload in a JWT string.
Generate a JSON body and encode it using Base64 to obtain the payload. In the JSON body, the value of iss is the same as the value of sub_account in the service account key file.
Example
{
"aud": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
"iss": "*****",
"exp": 1581410664,
"iat": 1581407064
} | Field | Description |
|---|---|
| iss | Data generator ID. The value is the same as that of the sub_account field in the key file. |
| aud | The value is always https://oauth-login.cloud.huawei.com/oauth2/v3/token. |
| iat | UTC timestamp when a JWT string is signed. The value is the number of seconds counting from 1970-01-01 00:00:00 (UTC) to the time when the JWT string is signed. |
| exp | UTC timestamp when a JWT string expires. The value is 3600 seconds later than that of iat. |
Generate a JWT.
Use a dot (.) to concatenate the header and payload generated after Base64 encoding, and use private_key (kept by yourself, not by Huawei) in the key file and the SHA256withRSA/PSS algorithm to sign the concatenated string. Then, a signature is generated.
By now, the required parts of the JWT string have been prepared.
When your app calls a Push Kit REST API, you need to add the JWT to the Authorization header of the API call request. Call the Push Kit REST API using the URL of the V3 version.
Example
POST "https://push-api.cloud.huawei.com/v3/3158882***52863/messages:send" Authorization: Bearer eyJr*****OiIx---****.eyJh*****iJodHR--***.QRod*****4Gp---**** push-type:0
The value of Authorization is Bearer followed by a space character and then the obtained authentication information.
API request URL version: Call the Push Kit REST API using the URL of the V3 version.
In the request body of a scenario-specific message, if the API request URL version is V3 (https://push-api.cloud.huawei.com/v3/[projectId]/messages:send), the message can be pushed to HarmonyOS NEXT/5.x and later versions. If the API request URL version is V2 (https://push-api.cloud.huawei.com/v2/[projectId]/messages:send), the message can be pushed only to HarmonyOS 3.x/4.x.
You can generate a JWT from a service account by referring to the Java sample code. Modify parameters based on comments in the sample code before running it.
If you do not use Java, select the corresponding JWT open-source component for development.
Perform the following steps to generate a JWT:
Create and download the service account key file (JSON file) that has been generated to call Push Kit APIs in step 1 in Development Procedure from the API Console of HUAWEI Developers. The file format is as follows:

Copy the preceding JSON file to your project and parse the file by referring to the following code. (private.json is used as an example. This example is developed based on io.jsonwebtoken:jjwt 0.11.5. The APIs vary greatly in different versions of the library. You need to adapt the code based on the actual dependency version.)
Java:
/* The recommended Java version is Java 8. The Maven dependency is as follows:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.1</version>
<scope>runtime</scope>
</dependency>
*/
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import io.jsonwebtoken.lang.Maps;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
public class JsonWebTokenFactory {
// Store the public address in the configuration file or database during development.
private static final String AUD = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
public static String createJwt() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, NullPointerException {
// Read the configuration file.
ObjectMapper mapper = new ObjectMapper();
// Save the private.json file in the src/main/resources directory of the project.
URL url = JsonWebTokenFactory.class.getClassLoader().getResource("private.json");
if (url == null) {
throw new NullPointerException("File not exist");
}
JsonNode rootNode = mapper.readTree(new File(url.getPath()));
RSAPrivateKey privateKey = (RSAPrivateKey) generatePrivateKey(rootNode.get("private_key").asText()
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", ""));
long iat = System.currentTimeMillis() / 1000;
long exp = iat + 3600;
Map<String, Object> header = Maps.<String, Object>of(JwsHeader.KEY_ID, rootNode.get("key_id").asText())
.and(JwsHeader.TYPE, JwsHeader.JWT_TYPE)
.and(JwsHeader.ALGORITHM, SignatureAlgorithm.PS256.getValue())
.build();
Map<String, Object> payload = Maps.<String, Object>of(Claims.ISSUER, rootNode.get("sub_account").asText())
.and(Claims.ISSUED_AT, iat)
.and(Claims.EXPIRATION, exp)
.and(Claims.AUDIENCE, AUD)
.build();
return Jwts.builder()
.setHeader(header)
.setPayload(new ObjectMapper().writeValueAsString(payload))
.signWith(privateKey, SignatureAlgorithm.PS256)
.compact();
}
private static PrivateKey generatePrivateKey(String base64Key) throws NoSuchAlgorithmException, InvalidKeySpecException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64Key.getBytes(StandardCharsets.UTF_8)));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
public static void main(String[] args) {
try {
// Obtain the JWT.
String jwt = createJwt();
} catch (NoSuchAlgorithmException e) {
// Exception handling process 1.
} catch (InvalidKeySpecException e) {
// Exception handling process 2.
} catch (IOException e) {
// Exception handling process 3.
} catch (NullPointerException e) {
// Exception handling process 4.
}
}
} Node.js:
// Dependency: npm i jsonwebtoken
const jwt = require('jsonwebtoken');
const fs = require('fs');
let privateJson;
try {
// Change the first parameter of readFileSync to the actual path of private.json.
const data = fs.readFileSync('private.json', 'utf8');
privateJson = JSON.parse(data);
// Custom header.
const header = {
alg: 'PS256', // The PS256 algorithm is recommended.
kid: privateJson?.key_id,
typ: 'JWT' // The type is JWT.
};
// Create a JWT payload.
const payload = {
iss: privateJson?.sub_account,
aud: 'https://oauth-login.cloud.huawei.com/oauth2/v3/token', // Store the public address in the configuration file or database during development.
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600
};
const private_key = privateJson?.private_key;
// Replace \\n in the string with \n, and split the string by lines and convert them into an array.
const lines = private_key.replace(/\\n/g, '\n').split('\n');
// Extract the first three lines.
const firstThreeLines = lines.slice(0, 3);
// Combine the extracted lines into a three-line string.
const PRIVATE_KEY = firstThreeLines.join('\n');
// Obtain the JWT.
const token = jwt.sign(payload, PRIVATE_KEY, { algorithm: 'PS256', header: header });
} catch (error) {
console.error("File processing error:", error);
} Go:
// Dependency: go get github.com/golang-jwt/jwt/v5
package main
import (
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"log"
"os"
"strings"
"time"
)
type ServiceAccountKey struct {
KeyID string `json:"key_id"`
SubAccount string `json:"sub_account"`
PrivateKey string `json:"private_key"`
}
func main() {
// Replace it with the actual JSON file path. In this example, the JSON file is at the same directory level as the sample code.
signedToken, err := generateJWTToken("private.json")
if err != nil {
log.Fatalf("Failed to generate JWT token: %v", err)
}
// signedToken indicates the authentication token. It is passed in the Authorization header of a Push Kit REST API request for authentication.
sendMessage(signedToken)
}
func sendMessage(token string) {
// Implement the service process by yourself.
}
func generateJWTToken(keyFile string) (string, error) {
saKey, err := loadServiceAccountKey(keyFile)
if err != nil {
return "", err
}
formattedPrivateKey, err := formatPrivateKey(saKey.PrivateKey)
if err != nil {
return "", err
}
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(formattedPrivateKey))
if err != nil {
return "", fmt.Errorf("failed to parse private key: %w", err)
}
token, err := buildJWTToken(saKey.KeyID, saKey.SubAccount)
if err != nil {
return "", err
}
return token.SignedString(privateKey)
}
// Call buildJWTToken to build a JWT object.
func buildJWTToken(keyID, subAccount string) (*jwt.Token, error) {
now := time.Now().UTC()
iat := now.Unix()
exp := iat + 3600 // Token expiration time: one hour later.
claims := jwt.MapClaims{
// Store the public address in the configuration file or database during development.
"aud": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
"iss": subAccount,
"exp": exp,
"iat": iat,
}
token := jwt.NewWithClaims(jwt.SigningMethodPS256, claims)
// Set the header.
token.Header["kid"] = keyID
token.Header["typ"] = "JWT"
token.Header["alg"] = "PS256"
return token, nil
}
// loadServiceAccountKey loads a service account key from the JSON file.
func loadServiceAccountKey(filename string) (*ServiceAccountKey, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read key file: %w", err)
}
var saKey ServiceAccountKey
if err := json.Unmarshal(data, &saKey); err != nil {
return nil, fmt.Errorf("failed to parse key file: %w", err)
}
if saKey.KeyID == "" || saKey.SubAccount == "" || saKey.PrivateKey == "" {
return nil, errors.New("invalid service account key file: missing required fields")
}
return &saKey, nil
}
// The formatPrivateKey is in PEM format.
func formatPrivateKey(privateKeyStr string) (string, error) {
trimmed := strings.TrimSpace(privateKeyStr)
// If the private key is already in PEM format, it is returned directly.
if strings.HasPrefix(trimmed, "-----BEGIN PRIVATE KEY-----") &&
strings.HasSuffix(trimmed, "-----END PRIVATE KEY-----") {
return trimmed, nil
}
block, _ := pem.Decode([]byte(trimmed))
if block == nil {
return "", errors.New("failed to decode PEM block")
}
pemBytes := pem.EncodeToMemory(block)
if pemBytes == nil {
return "", errors.New("failed to encode private key to PEM format")
}
return string(pemBytes), nil
} Python:
# Dependency: pip install PyJWT cryptography
import jwt
import json
import time
from cryptography.hazmat.primitives import serialization
def load_private_key_from_json(json_file_path):
"""
Load private key information from a JSON file.
:param json_file_path: JSON file path.
:return: (key_id, sub_account, private_key_pem)
"""
with open(json_file_path, 'r') as f:
data = json.load(f)
# Obtain the values of kid and iss.
key_id = data.get('key_id')
sub_account = data.get('sub_account')
# Convert the private key to PEM format.
private_key_str = data.get('private_key')
private_key_pem = serialization.load_pem_private_key(
private_key_str.encode(),
password=None
)
return key_id, sub_account, private_key_pem
def generate_jwt_token(json_file_path):
# Load information from a JSON file.
kid, iss, private_key = load_private_key_from_json(json_file_path)
# Current time and expiration time. (Fixed values are used in the example. Calculate them based on the actual situation.)
iat = int(time.time())
exp = iat + 3600
# Construct a header.
header = {
"kid": kid,
"typ": "JWT",
"alg": "PS256"
}
# Construct a payload.
payload = {
# Store the public address in the configuration file or database during development.
"aud": "https://oauth-login.cloud.huawei.com/oauth2/v3/token",
"iss": iss,
"exp": exp,
"iat": iat
}
# Generate a JWT.
token = jwt.encode(
payload=payload,
key=private_key,
algorithm='PS256',
headers=header
)
return token
def send_message(jwt_token):
# Implement the service process by yourself.
pass
if __name__ == "__main__":
json_file = "private.json" # Replace it with your actual JSON file path. In this example, the JSON file is at the same directory level as the sample code.
try:
# jwt_token indicates the authentication token. It is passed in the Authorization header of a Push Kit REST API request for authentication.
jwt_token = generate_jwt_token(json_file)
send_message(jwt_token)
except Exception as e:
print(f"Error generating JWT token: {str(e)}") PHP:
<?php
// Dependency: composer require lcobucci/jwt:^5.4.2
// Dependency: composer require lcobucci/jwt-rsassa-pss
// php: ~8.2.0 || ~8.3.0 || ~8.4.0
require 'vendor/autoload.php';
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\RsaPss\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
class ServiceAccount
{
public string $keyId;
public string $subAccount;
public string $privateKey;
public string $tokenURI;
public function __construct(string $keyId, string $subAccount, string $privateKey, string $tokenURI)
{
$this->keyId = $keyId;
$this->subAccount = $subAccount;
$this->privateKey = $privateKey;
$this->tokenURI = $tokenURI;
}
}
function loadServiceAccount(string $filePath): ServiceAccount
{
if (!file_exists($filePath)) {
throw new RuntimeException("The configuration file does not exist: $filePath");
}
$json = file_get_contents($filePath);
$config = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException("JSON parsing error: " . json_last_error_msg());
}
// Verify required fields.
$requiredKeys = ['key_id', 'sub_account', 'private_key', 'token_uri'];
foreach ($requiredKeys as $key) {
if (!isset($config[$key])) {
throw new RuntimeException("The required field does not exist: $key");
}
}
// Process the newline characters in the private key.
$privateKey = str_replace('\n', "\n", $config['private_key']);
return new ServiceAccount(
$config['key_id'],
$config['sub_account'],
$privateKey,
$config['token_uri']
);
}
function sendMessage()
{
// Implement the service process by yourself.
}
function generateJWTToken(ServiceAccount $serviceAccount)
{
$now = new DateTimeImmutable();
$expire = $now->modify("+3600 seconds");
$configuration = Configuration::forSymmetricSigner(
new Sha256(),
InMemory::plainText($serviceAccount->privateKey)
);
return $configuration->builder()
->withHeader('alg', 'PS256') // Specify the PS256 algorithm.
->withHeader('typ', 'JWT') // JWT type.
->withHeader('kid', $serviceAccount->keyId) // Key ID.
->issuedBy($serviceAccount->subAccount) // iss
->permittedFor($serviceAccount->tokenURI) // aud
->issuedAt($now) // iat
->expiresAt($expire) // exp
->getToken($configuration->signer(), $configuration->signingKey())
->toString();
}
function main()
{
try {
// Replace it with the actual JSON file path. In this example, the JSON file is at the same directory level as the sample code.
$filePath = 'private.json';
$serviceAccount = loadServiceAccount($filePath);
$signedToken = generateJWTToken($serviceAccount);
// signedToken indicates the authentication token. It is passed in the Authorization header of a Push Kit REST API request for authentication.
sendMessage($signedToken);
} catch (Exception $e) {
error_log("Error: " . $e->getMessage());
exit(1);
}
}
main();
?>