Reputation: 101
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
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
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
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
Reputation: 1768
I was facing similar kind of authentication error i.e. NOT_AUTHORIZED. And I resolved it by following these steps:
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.
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