Radosław Sakowicz
Radosław Sakowicz

Reputation: 101

Generating token for App Store Connect API

I need to generate JWT token for Store Connect API. I'm trying use jwt ruby gem ruby-jwt. This is my token generating code,

payload = {
      'iss': my_issuer_id_from_db,
      'exp': generated_unix_timestamp, #Time.now + 20min
      'aud': 'hard_coded_string_from_doc'
  }
  header = {
      'alg': 'ES256',
      'kid': my_key_id_from_db,
      'typ': 'JWT'
  }

private_key = OpenSSL::PKey.read(File.read('/tmp/private_key.pem'))
# private_key - <OpenSSL::PKey::EC:0x000000000XXXXXXX>

@token = JWT.encode(payload, private_key, 'ES256', header)
# encoded_header.encoded_payload.emcoded_signature

This token I put in to header of my request:

headers = { Authorization: 'Bearer' + @token }

in respons I receive:

        "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"
        }]
}

I thing the problem is with token(directly with signature). When I try decode token with online tool, my payload and header is decoded properly. Status: Invalid Signature

What I do wrong? Any ideas how do it properly?

Upvotes: 6

Views: 8070

Answers (4)

Giang
Giang

Reputation: 2719

I just created python3 version here. 401 Authentication credentials are missing or invalid maybe related to getting time issued or expired.

Check your function get time is UNIX epoch time as Apple recommends.

import jwt
import datetime as dt

key_id = '#####' 
alg = 'ES256'
typ = 'JWT'
issue_id = '##########################'
aud = 'appstoreconnect-v1'
bid = '####' # Your app’s bundle ID (Ex: “com.example.testbundleid2021”)

# Define issue timestamp.
issued_at_timestamp = int(dt.datetime.now().timestamp())
# Define expiration timestamp. May not exceed 20 minutes from issue timestamp.
expiration_timestamp = issued_at_timestamp + 20*60

# Define JWT headers.
headers = dict()
headers['alg'] = alg
headers['kid'] = key_id
headers['typ'] = typ

# Define JWT payload.
payload = dict()
payload['iss'] = issue_id
payload['iat'] = issued_at_timestamp
payload['exp'] = expiration_timestamp
payload['aud'] = aud
payload['bid'] = bid
# Path to signed private key.
KEY_FILE = '#########.p8' 

with open(KEY_FILE,'r') as key_file:
     key = ''.join(key_file.readlines())

client_secret = jwt.encode(
payload=payload,  
headers=headers,
algorithm=alg,  
key=key
)

with open('client_secret.txt', 'w') as output: 
     output.write(client_secret)
    
    
# Usage, after run this code by python3
# get token from `client_secret.txt` and replace to [signed token]
# Remember expired time maximum is 20 minutes
#
# curl -v -H 'Authorization: Bearer [signed token]' "https://api.appstoreconnect.apple.com/v1/apps"
#
# More detail https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests

Upvotes: 4

Dan Rice
Dan Rice

Reputation: 61

I have an answer from 2023 that leverages the Ruby script in @Nah's answer, but worked for me because it adds some keys that are missing in the old answer.

Ruby script:

require "base64"
require "jwt"
ISSUER_ID = "YOUR-ISSUER-ID"
KEY_ID = "YOUR-KEY-ID"
private_key = OpenSSL::PKey.read(File.read("PATH_TO_KEY_DOWNLOADED_FROM_APP_STORE_CONNECT.p8"))
token = JWT.encode(
  {
    iss: ISSUER_ID,
    exp: Time.now.to_i + 20 * 60,
    aud: "appstoreconnect-v1",
    bid: "YOUR_BUNDLE_ID"
  },
  private_key,
  "ES256",
  header_fields = {
    alg: "ES256",
    kid: KEY_ID,
    typ: "JWT"
  }
)
puts token

You can then take that key and put it in an Authorization header when communicating with Apple.

For example, I needed to get a test notification from Apple for in-app purchase events, so I can put the token in a curl request and get a 200:

curl -v --request POST \
-H "Authorization: Bearer YOUR_LONG_TOKEN_HERE" \
"https://api.storekit-sandbox.itunes.apple.com/inApps/v1/notifications/test"

If you would prefer to use Postman, in the Authorization tab, set a Bearer Token to the token that the Ruby script generated.

Upvotes: 1

ablarg
ablarg

Reputation: 2490

You had a missing space in your authorization string passed in. You code worked fine when modified to

headers = { Authorization: 'Bearer ' + @token }

Upvotes: 2

Nah
Nah

Reputation: 1768

I was facing similar kind of authentication error i.e. NOT_AUTHORIZED. And I resolved it by following these steps:

1. Create Ruby Script file to generate valid Bearer Token:

Ref: https://medium.com/xcblog/generating-jwt-tokens-for-app-store-connect-api-2b2693812a35

require "base64"
require "jwt"
ISSUER_ID = "YOUR_ISSUER_ID"
KEY_ID = "YOUR PRIVATE KEY ID"    // this is ID part from downloaded .p8 file name (see below for ref.)
private_key = OpenSSL::PKey.read(File.read(path_to_your_private_key/AuthKey_#{KEY_ID}.p8))   // you can enclose your file path in quotes if needed, and can pass here totally static file path (here we are reusing Key_ID variable)

token = JWT.encode(
   {
    iss: ISSUER_ID,
    exp: Time.now.to_i + 20 * 60,
    aud: "appstoreconnect-v1"
   },
   private_key,
   "ES256",
   header_fields={
       kid: KEY_ID }
)
puts token

Then run this script with following command on your Mac.

$ ruby jwt.rb

This will display a valid Bearer token on your terminal screen, that you can use in next step.

Notes:

  • In order to run above script, you'll need to have ruby installed.
  • You'll copy Issuer ID from you Developer account. Generate one if you don't have it.
  • Make sure you are using '.p8' certificate against authenticated user, which means the account against which you downloaded '.p8' certificate should have permission to perform API level operation. For my case I used Admin type account. Initially I was using Developer type user account which kept giving me Not_Authorized error when I go for final Curl call to get the token.

2. Using Token:

Now that, we have seen how to generate a token to access an App Store Connect API, we can use it by passing authorization header. e.g to get a list of all user we can use

$ curl  https://api.appstoreconnect.apple.com/v1/users --Header "Authorization: Bearer lOOOOOOOOOOOONG_GENERATED_TOKEN"

This will list all the users of App Store Connect. Remember that we have to use this token with every request we make and we have to create new token after every 20 minutes.

Upvotes: 2

Related Questions