I'm trying to generate a jwt token to connect to AppStore API. I'm using the jwt-dotnet library to do this.
Apple requires ES256 to be used and the jwt-dotnet is asking for a public key to do the job. I only downloaded a private key from AppStore. How do I handle this?
Here's my code:
public static string GenerateAppStoreJwtToken()
{
var header = new Dictionary<string, object>()
{
{ "kid", "MY_VALUE" },
{ "typ", "JWT" }
};
var scope = new string[1] { "GET /v1/apps?filter[platform]=IOS" };
var payload = new Dictionary<string, object>
{
{ "iss", "MY_VALUE" },
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "exp", DateTimeOffset.UtcNow.AddMinutes(20).ToUnixTimeSeconds() },
{ "aud", "appstoreconnect-v1" },
{ "scope", scope }
};
IJwtAlgorithm algorithm = new ES256Algorithm(???); // What am I going to use here?
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(header, payload, privateKey);
return token;
}
For anyone, like me, who usew JWT-dotnet elsewhere so doesn't want to use a different JWT package, this worked:
Converted the apple private key by removing the header and footer ("-----BEGIN PRIVATE KEY-----" etc) and removing the end of line characters to make a single string for easier storage.
Convert from Base64 and store in a ReadOnlySpan
ReadOnlySpan<byte> keyAsSpan = Convert.FromBase64String(key);
var prvKey = ECDsa.Create();
prvKey.ImportPkcs8PrivateKey(keyAsSpan,out var read);
Create the algorithm. A blank ECDsa instance is needed to prevent an NullException but it is not needed just for signing the token, only verifying which isn't necessary.
IJwtAlgorithm algorithm = new ES256Algorithm(ECDsa.Create(), prvKey)
I was able to receive a reply token from apple using this method.
==Edit== Added full method
public string GenerateAppleSecret(string client_id, string TeamId, string kid, string key)
{
var now = DateTimeOffset.Now.AddDays(-1);
ReadOnlySpan<byte> keyAsSpan = Convert.FromBase64String(key);
var prvKey = ECDsa.Create();
prvKey.ImportPkcs8PrivateKey(keyAsSpan,out var read);
return new JwtBuilder()
.WithAlgorithm(new ES256Algorithm(ECDsa.Create(), prvKey))
.AddHeader("kid", kid)
.ExpirationTime(now.AddDays(5).UtcDateTime)
.IssuedAt(now.UtcDateTime)
.Issuer(TeamId, )
.Audience("https://appleid.apple.com")
.Subject(client_id)
.Encode();
}
The result of this method is appended to the query url as "client_secret" and "client_id" is also included.
Here's the final solution that worked for me. I ended up switching to jose-jwt but I'm pretty sure you can handle the same thing with jwt-dotnet. I just found working with jose-jwt a bit easier. Here's the link to jose-jwt: https://github.com/dvsekhvalnov/jose-jwt
And here's the final code. Please note that I did indeed use the private key I find in the p8 file and didn't have to convert anything. So the privateKey parameter I'm passing to the GenerateAppStoreJwtToken() function comes directly from the p8 file.
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using Jose;
public static string GenerateAppStoreJwtToken(string privateKey)
{
var header = new Dictionary<string, object>()
{
{ "alg", "ES256" },
{ "kid", "MY_VALUE" },
{ "typ", "JWT" }
};
var scope = new string[1] { "GET /v1/apps?filter[platform]=IOS" };
var payload = new Dictionary<string, object>
{
{ "iss", "MY_VALUE" },
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "exp", DateTimeOffset.UtcNow.AddMinutes(15).ToUnixTimeSeconds() },
{ "aud", "appstoreconnect-v1" },
{ "scope", scope }
};
CngKey key = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
string token = JWT.Encode(payload, key, JwsAlgorithm.ES256, header);
return token;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With