JWT authentication with App Store server always 401

I have the following implementation in JS/Node for interacting with App Store server, yet all responses are 401 Unauthorized:

% curl -v -H 'Authorization: Bearer ***' "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/4"
* Trying 17.56.138.10...
* TCP_NODELAY set
* Connected to api.storekit-sandbox.itunes.apple.com (17.56.138.10) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
 CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
* subject: businessCategory=Private Organization; jurisdictionCountryName=US; jurisdictionStateOrProvinceName=California; serialNumber=C0806592; C=US; ST=California; L=Cupertino; O=Apple Inc.; OU=management:idms.group.506364; CN=commercegateway.itunes.apple.com
* start date: Jun 4 18:57:38 2021 GMT
* expire date: Jul 4 18:57:37 2022 GMT
* subjectAltName: host "api.storekit-sandbox.itunes.apple.com" matched cert's "api.storekit-sandbox.itunes.apple.com"
* issuer: C=US; O=Apple Inc.; CN=Apple Public EV Server RSA CA 2 - G1
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fca1e00d600)
> GET /inApps/v1/subscriptions/4 HTTP/2
> Host: api.storekit-sandbox.itunes.apple.com
> User-Agent: curl/7.64.1
> Accept: */*
> Authorization: Bearer ***
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 1024)!
< HTTP/2 401 
< server: daiquiri/3.0.0
< date: Thu, 26 Aug 2021 02:57:57 GMT
< content-type: text/plain
< strict-transport-security: max-age=31536000; includeSubDomains
< x-apple-jingle-correlation-key: KPN55ROQH3BLNL2AG7S7NEFZHA
< x-daiquiri-instance: daiquiri:17578001:mr85p00it-hyhk04164801:7987:21RELEASE140:daiquiri-amp-commerce-clients-ext-002-mr
< 
Unauthenticated

Request ID: KPN55ROQH3BLNL2AG7S7NEFZHA.0.0
* Connection #0 to host api.storekit-sandbox.itunes.apple.com left intact
* Closing connection 0

The header is hashed with ES256 as follows:

function jwtHeader() {
	let d = new Date()
	d.setHours(d.getHours() + 1)

	return {
	  "iss": issuerID,
	  "iat": Math.floor(new Date().getTime()/1000),
	  "exp": Math.floor(d.getTime()/1000),
	  "aud": "appstoreconnect-v1",
	  "nonce": Utils.uuid(),
	  "bid": bundleID
	}
}

function headerHash(cb) {
	fs.readFile(appleKeys.p8, 'utf-8', function(err, key) {
		if (err) return cb(err)
		let headers = jwtHeader()
		console.log(headers)
		jwt.sign(headers, key, { algorithm: 'ES256' }, cb)
	})
}

My In-App Purchase Key ID is completely unused. Besides registering a callback for notifications, I can't find anywhere to register my URL. In short, I can't tell what's not configured or carried out improperly on my end.

Answered by App Store Commerce Engineer in 686016022

Can you please ensure you are providing the correct information in the Header it should contain alg, kid, type. Below is an example payload for the header.

{ "alg": "ES256", "kid": "2X9R4HXF34", "typ": "JWT" }

Please refer to the links below for more information:

Creating API Keys to use with the App Store API: https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api

Generating Tokens for API requests: https://developer.apple.com/documentation/appstoreserverapi/generating_tokens_for_api_requests

Accepted Answer

Can you please ensure you are providing the correct information in the Header it should contain alg, kid, type. Below is an example payload for the header.

{ "alg": "ES256", "kid": "2X9R4HXF34", "typ": "JWT" }

Please refer to the links below for more information:

Creating API Keys to use with the App Store API: https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api

Generating Tokens for API requests: https://developer.apple.com/documentation/appstoreserverapi/generating_tokens_for_api_requests

It appears there may have been an issue with a missing or invalid "kid" in the JWT for your request; I suggest double checking that portion of your code.

Thanks very much, you are absolutely correct. I will mark the original answer correctly.

In essence, JWT should be structured with the correct header and claims as in the docs. The remainder were implementation details on my end that I recently cleared.

Jwt {
 header: JwtHeader { typ: 'JWT', alg: 'ES256', kid: 'keyIDHere' },
 body: JwtBody {
  iss: 'issuer-id-from-AppStoreConnect-KeysSection',
  aud: 'appstoreconnect-v1',
  iat: 1630030166,
  exp: 1630033766,
  nonce: '90516455-0a3a-4a40-a863-eab63c7359ac',
  bid: 'com.bundle.id',
 },
 signingKey: '-----BEGIN PRIVATE KEY-----\n' +
  'YOUR\n' +
  'PRIVATE\n' +
  'KEY\n' +
  'HERE\n' +
  '-----END PRIVATE KEY-----'
}

I am still not able to create a valid token using everything right still not able to figure out wats wrong!!

const OpenSSL = require('openssl-nodejs')
const fs = require('fs');
const path = require('path');
var jwt = require('jsonwebtoken');
var http = require('http')
function getToken() {

    const now = Math.round(new Date().getTime() / 1000);
    const expirationTime = now + 1999;

    const privateKey = fs.readFileSync('./AuthKey_W467528DJL.p8')
    const headers = {
        algorithm: 'ES256',
        header: {
            alg: 'ES256',
            kid: 'W467528DJL',
            typ: 'JWT',
        },
    };

    let payload = {
        iss: '8b3af5d2-f293-452c-8df1-6d023149279b',
        exp: expirationTime,
        aud: 'appstoreconnect-v1',
        bid: 'org.momly.app'
    }

    const token = jwt.sign(payload, privateKey, {
        algorithm: 'ES256',
        header: {
            alg: 'ES256',
            kid: 'W467528DJL',
            typ: 'JWT',
        }
    });

    console.log("token ---> " + token);

    const verify = jwt.verify(token, privateKey)
    console.log("Verify", verify)
};


getToken()


please provide help to how to get a valid token 
JWT authentication with App Store server always 401
 
 
Q