Reputation: 41
I need to generate JWT token for Store Connect API. This is my token generating code:
console.log("🏃 appStoreConnectAPIFromNode.js running 🏃")
const fs = require('fs');
const jwt = require('jsonwebtoken'); // npm i jsonwebtoken
// You get privateKey, apiKeyId and issuerId from your Apple App Store Connect account
const privateKey = fs.readFileSync("./AuthKey-XXXXXXXX.p8") // this is the file you can only download once and should treat like a real, very precious key.
const apiKeyId = "XXXXXXXX"
const issuerId = "XXXXXXXX-XXXX-XXXX-XXX-XXXXXXXXXX"
let now = Math.round((new Date()).getTime() / 1000); // Notice the /1000
let nowPlus20 = now + 1199 // 1200 === 20 minutes
let payload = {
"iss": issuerId,
"exp": nowPlus20,
"aud": "appstoreconnect-v1",
"iat": now
}
let signOptions = {
"algorithm": "ES256", // you must use this algorythm, not jsonwebtoken's default
header : {
"alg": "ES256",
"kid": apiKeyId,
"typ": "JWT"
}
};
let token = jwt.sign(payload, privateKey, signOptions);
console.log('@token: ', token);
fs.writeFile('Output.txt', token, (err) => {
// In case of a error throw err.
if (err) throw err;
})
I'm getting this response though
"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 on https://jwt.io/#debugger-io, my payload and header is decoded properly. Status: Invalid Signature
What I do wrong? Any ideas how do it properly?
Upvotes: 4
Views: 2841
Reputation: 428
You can use simple sign option for this.
const PRIVATE_KEY = fs.readFileSync(p8FilePath, "utf8");
const ISSUER_ID = "ID";
const KEY_ID = "Key_ID";
let payload = {
iss: ISSUER_ID,
exp: NOW_PLUS_20,
aud: "appstoreconnect-v1",
};
let signOptions = {
algorithm: "ES256",
header: {
alg: "ES256",
kid: KEY_ID,
typ: "JWT",
},
};
let token = jwt.sign(payload, PRIVATE_KEY, signOptions);
You can use this token in your API call
Upvotes: 2
Reputation: 4723
I recently needed to solve this problem too and after following a lot of tutorials which often gave the wrong answer for the app store connect api. I found a way to make it work. This is how I did it in Java.
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.interfaces.ECPrivateKey;
import java.security.KeyFactory;
import java.io.FileReader;
import java.util.Base64;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
// The code is like this so try it in whatever method you want,
// break it up into pieces so it's a bit more readable
// These variables are the minimum you should need
String keyFile = "/path/to/app_store_AuthKey_XXXX.p8";
// The XXXX is the same XXXX from the keyFile filename itself
String keyId = "XXXX";
String issuerId = "1234-1234-1234-1234-1234";
FileReader kfReader = new FileReader(keyFile);
PEMParser pemParser = new PEMParser(kfReader);
PrivateKeyInfo info = (PrivateKeyInfo) pemParser.readObject();
byte[] pkData = info.getEncoded();
// Convert the PrivateKeyInfo object to a PKCS8EncodedKeySpec object
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkData);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
ECPrivateKey privateKey = (ECPrivateKey) keyFactory.generatePrivate(keySpec);
Algorithm algorithm = Algorithm.ECDSA256(null, privateKey);
Instant expiration = Instant.now().plus(TIMEOUT_MINUTES, ChronoUnit.MINUTES);
return JWT.create()
.withKeyId(keyId)
.withIssuer(issuerId)
.withExpiresAt(expiration)
.withAudience("appstoreconnect-v1")
.sign(algorithm);
Upvotes: 1
Reputation: 41
I'm also facing this and me and teammate solved it.....
solution:
let payload = {
iss: *******,
exp: *******,
aud: "appstoreconnect-v1",
bid: "your_bundle_id",
};
const { data } = await axios({
method: "GET",
url: "https://api.storekit.itunes.apple.com/inApps/v1/history/",
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${token}`,
},
});
res.json(data);
Upvotes: 4
Reputation: 1404
I was trying to resolve my own issues dealing with making a JWT to connect to the Apple Music API and there were two things I noticed in your payload. The first, that you are adding the 1200 to the iat, fixed my issued, so thank you. The second is the function for now is returning a float. I think the API would like an int. I would try "iat": parseInt(now)
or now = Math.floor(new Date().getTime() / 1000)
Upvotes: 0
Reputation: 7
According to jsonwebtoken usage instructions. You can use the options
directly on an empty payload as follows:
let signOptions = {
issuer: issuerId,
keyid: apiKeyId,
expiresIn: '20m',
audience: 'appstoreconnect-v1',
algorithm: 'ES256'
};
let token = jwt.sign({}, privateKey, signOptions);
Upvotes: -2