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.