How to sign JWT with ES256 algorithm?
Asked Answered
I

0

1

I am trying to generate a JWT token for appstore connect APIs with ES256 algorithm.

I have managed to call the APIs using python code JWT Token with below code.

from datetime import datetime, timedelta
import json
import time
import curlify
import jwt
import requests



def generate_access_token():

    # The path to your service account private key file
    PRIVATE_KEY_FILE = "private_apple_key"

    # The URL of the Apple token endpoint
    TOKEN_ENDPOINT = "appstoreconnect-v1"

    issuer_id = "fc7d6b48-0000-00000-00000-4e8230bfda8f".  //modified for security reasons

    key_id = "WL000000XAU" //modified for security reasons

    ALGORITHM = 'ES256'

    try:
        key = open(PRIVATE_KEY_FILE, 'r').read()
    except IOError as e:
        key = PRIVATE_KEY_FILE

    token_gen_date = datetime.now()

    exp = int(time.mktime((token_gen_date + timedelta(minutes=20)).timetuple()))

    jwt_token = jwt.encode({'iss': issuer_id, 'exp': exp, 'aud': TOKEN_ENDPOINT}, key,
                      headers={'kid': key_id, 'typ': 'JWT'}, algorithm=ALGORITHM).decode('ascii')

    # return access_token
    print("\n",jwt_token,"\n")
    return jwt_token


def get_user_list():
    access_token_value = generate_access_token()

    # Set the JWT token in the Authorization header.
    headers = {"Authorization": f"Bearer {access_token_value}"}

    # Make a GET request to the Google Play Developer API.
    response = requests.get("https://api.appstoreconnect.apple.com/v1/apps", headers=headers)
    # response = requests.get("https://api.appstoreconnect.apple.com/v1/userInvitations?filter[username]='[email protected]'", headers=headers)

    print(curlify.to_curl(response.request))

    # Check the response status code.
    if response.status_code == 200:
        # Success!
        contents = json.loads(response.content)

        # Do something with the users.
        print(contents)
    else:
        # Handle the error.
        print(response.status_code)
        print("\n", response.content)


get_user_list()
# generate_access_token()

Issue:: I want to use the Google appscript for the JWT and API calling so can make some automationed for updating Google Sheet. Due to security concerts I cant use python code for domain issue to share with google sheet with service account.

I was able to find this closest matched code executed to generate a token, however its not accepted by appstoreconnect and gives error as below.

{
    "errors": [
        {
            "status": "401",
            "code": "NOT_AUTHORIZED",
            "title": "Authentication credentials are missing or invalid.",
            "detail": "Provide a properly configured and signed bearer token, and make sure that it has not expired. Learn more about Generating Tokens for API Requests https://developer.apple.com/go/?id=api-generating-tokens"
        }
    ]
}

Here is my Google Appscript code and from my research I was able to find only RSA256, HS256 code and matching function Utilities.computeHmacSha256Signature(toSign, privateKey) with below code that is running successfully.

var appleCredentials = SecurityAdapter.getAppleCredentials();
  var issuerId = appleCredentials["issuer_id"];
  var key_id = appleCredentials["key_id"];
  var apple_p_key = appleCredentials["p_key"];

// ----- ======== ...... -------


const createJwt = ({ privateKey, data = {} }) => {
  // Sign token using HMAC with SHA-256 algorithm
 
   var appleCredentials = SecurityAdapter.getAppleCredentials();
   var key_id = appleCredentials["key_id"];
    
  const header = {
    alg: 'ES256',
    typ: 'JWT',
    kid: key_id,
  };

  const payload = {
    iss: "issuerId",
    exp: Math.floor(Date.now() / 1000) + 20 * 60,
    aud: 'appstoreconnect-v1',
  };

  // add user payload
  Object.keys(data).forEach(function (key) {
    payload[key] = data[key];
  });

  const base64Encode = (text, json = true) => {
    const data = json ? JSON.stringify(text) : text;
    return Utilities.base64EncodeWebSafe(data).replace(/=+$/, '');
  };

  const toSign = `${base64Encode(header)}.${base64Encode(payload)}`;
  const signatureBytes = Utilities.computeHmacSha256Signature(toSign, privateKey);
  const signature = base64Encode(signatureBytes, false);
  return `${toSign}.${signature}`;
};


const generateAccessToken = () => {
// Your super secret private key

var appleCredentials = SecurityAdapter.getAppleCredentials();
var key_id = appleCredentials["key_id"];
var privateKey = appleCredentials["p_key"];

var issuerId = appleCredentials["issuer_id"]; 

const accessToken = createJwt({
  privateKey,
  expiresInHours: 0.20, // expires in 6 hours
  data: {
    iss: issuerId,
    exp: Math.floor(Date.now() / 1000) + 20 * 60,
    aud: 'appstoreconnect-v1',
  },
  });
  Logger.log(accessToken);
};

Diving further to research more: The below code JWT tokens generated was verified in jwt.io and realised that the decoding is exact the same but the key size parts differ for the last section:

Not Working JWT:

eyJ0eXAiOiJKV1QiLCJhbGci---------pZCI6IldMMkgzVzVYQVUifQ.eyJpc3MiOiJmYzdkNmI0OC1iZTRkLTRhYWE--------iLCJleHAiOjE3MDIwMDMyMTAsImF1ZCI6ImFwcHN0b3JlY29ubmVjdC12MSJ9.DzXbndN_1l1O1Kr111111111195Zje0_7UKs9FrZck0

Working JWT:

eyJ0eXAiOiJKV1QiLCJhbGci---------pZCI6IldMMkgzVzVYQVUifQ.eyJpc3MiOiJmYzdkNmI0OC1iZTRkLTRhYWE--------iLCJleHAiOjE3MDIwMDMyMTAsImF1ZCI6ImFwcHN0b3JlY29ubmVjdC12MSJ9.R98fbDcwOfvMjGPJqFJAdYNLI1111111111Y0LUzFbNDq5dARTS7nja6LRB0Kw3Z1OEgcKuz2oV2MayB9HOMKg

The string is basically with HEADER.PAYLOAD.VERIFIY-SIG components, so the VERIFY-SIG is not same size and this is where the doubt arise that the function in Google computeHmacSha256Signature cannot be used, is there any alternative or anyone tried.

Insolent answered 8/12, 2023 at 2:26 Comment(1)
You may try github.com/kjur/jsrsasign by mocking the browser variables in server... Something like stackoverflow.com/a/63647410Curvilinear

© 2022 - 2024 — McMap. All rights reserved.