2
votes

we launched an iOS-App and I want to grab some Information (e.g. Installations, Updates, Reviews) from the App Store Connect API.

I create an JSON Web Token as described in the official Apple documentation: Link

Afterwards I make a request with the token in the header. Now I get an '401' | 'NOT_AUTHORIZED' each time as an answer, see the following picture: REST Response

In the following snippets you can see my python code (I tried to solve it in Python and R, but the result is always the same).

First, I create an JWT:


    from datetime import datetime, timedelta
    from jose import jwt, jws
    import ecdsa

    KEY_ID = "XXXXXXXXXX"
    ISSUER_ID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
    PRIVATE_KEY = open('AuthKey_XXXXXXXXXX.p8', 'r').read()
    TIMESTAMP = int( (datetime.now() - timedelta(minutes = 45)).timestamp() * 1000)

    claim = {"iss" : ISSUER_ID,
             "exp" : TIMESTAMP,
             "aud" : "appstoreconnect-v1"}

    header = {
             "alg": "ES256",
             "kid": KEY_ID,
             "typ": "JWT"
             }

    # Create the JWT
    encoded = jwt.encode(claim, PRIVATE_KEY, algorithm='ES256', headers=header)

Now when I print encoded, I get to following JWT (looks valid for me):


'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlhYWFhYWFhYWFgifQ.eyJpc3MiOiJYWFhYWFhYWC1YWFhYLVhYWFgtWFhYWC1YWFhYWFhYWFhYWFgiLCJleHAiOjE1NDUzOTc1MTQ1ODAsImF1ZCI6ImFwcHN0b3JlY29ubmVjdC12MSJ9.eTl6iaAW-Gp67FNmITrWCpLTtJzVdLYXIl5_KKgqaNgzwyGo7npBOBo9_u5PtLNnssQFEwJWbPND-6Ww5ACgEg'

Even if I decode the first two parts of the JWT via Base64 I get the right Header (it also contains the right algorithm for encoding: 'alg': 'ES256') and Claim:


    from jose.utils import base64url_decode
    print(base64url_decode(b'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IlhYWFhYWFhYWFgifQ'))
    print(base64url_decode(b'eyJpc3MiOiJYWFhYWFhYWC1YWFhYLVhYWFgtWFhYWC1YWFhYWFhYWFhYWFgiLCJleHAiOjE1NDUzOTc1MTQ1ODAsImF1ZCI6ImFwcHN0b3JlY29ubmVjdC12MSJ9'))

See the following picture: Output Base64 Decoding

So now, that I'm think that the JWT-Object is ready I send the request to the API:


    import requests

    JWT = 'Bearer ' + encoded

    URL = 'https://api.appstoreconnect.apple.com/v1/apps'
    HEAD = {'Authorization': JWT} 
    print(HEAD)

    R = requests.get(URL, headers=HEAD)
    R.json()

And now we can see my problem, see the picture: Header | REST Response

Please note that I have hidden the KEY_ID, ISSUER_ID and PRIVATE_KEY for the example.

3
Have you been able to use URLRequest and URLSession with the token ?the Reverend

3 Answers

1
votes

Your token contains an expiry time

"exp": 1545397514580,

which equals September 12th, 50941.

When I delete the last three digits

"exp": 1545397514,

I get December 21st, 2018 which makes much more sense.

Change that line

TIMESTAMP = int( (datetime.now() - timedelta(minutes = 45)).timestamp() * 1000)

to

TIMESTAMP = int( (datetime.now() - timedelta(minutes = 45)).timestamp())

exp is a timestamp that is defined as seconds since 01.01.1970 00:00 See also here

0
votes

First of all, please don't open files without context managers. This string:

PRIVATE_KEY = open('AuthKey_XXXXXXXXXX.p8', 'r').read()

should be:

with open('AuthKey_XXXXXXXXXX.p8', 'r') as f:
    PRIVATE_KEY = f.read()

It save you from many problems with unclosed files in future.

Then, check what token you've read from file. Is it correct?

The next problem I see is timestamp. "The token's expiration time, in Unix epoch time;" You provide it in milliseconds, I guess.

0
votes

Here is working solution for me. Without delay it return 401 error

KEY_ID = "xxxxx"
ISSUER_ID = "xxxxx"
EXPIRATION_TIME = int(round(time.time() + (20.0 * 60.0))) # 20 minutes timestamp
PATH_TO_KEY = '../credentials/AuthKey_xxxxx.p8'
with open(PATH_TO_KEY, 'r') as f:
    PRIVATE_KEY = f.read()

header = {
    "alg": "ES256",
    "kid": KEY_ID,
    "typ": "JWT"
}

payload = {
    "iss": ISSUER_ID,
    "exp": EXPIRATION_TIME,
    "aud": "appstoreconnect-v1"
}

# Create the JWT
token = jwt.encode(header, payload, PRIVATE_KEY)
JWT = 'Bearer ' + token.decode()
HEAD = {'Authorization': JWT}

# Without delay I got 401 error
time.sleep(5)

URL = 'https://api.appstoreconnect.apple.com/v1/apps';
r = requests.get(URL, params={'limit': 200}, headers=HEAD)