Jump to content
Sign in to follow this  
Dave Nottage

App Store Connect REST API problem

Recommended Posts

I wish to use the App Store Connect REST API to retrieve some information, and I have created a key as per these instructions.

 

Next thing is to create a JWT for passing in the Authorization as per these instructions. I have chosen Paolo Rossi's excellent JOSE JWT library for Delphi (especially since it now includes an ES256 signing algorithm), and have come up with the following code for generating the JWT and sending via HTTP:

const
  cTokenExpirySeconds = 60;
  cIssuerID = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx';
  cKeyID = 'yyyyyyyy';
  cAppStoreConnectAPIURL = 'https://api.appstoreconnect.apple.com/v1';
  cAppStoreConnectAPIGetProfiles = cAppStoreConnectAPIURL + '/profiles';

// AIssuer = id obtained from https://appstoreconnect.apple.com/access/api
// AKeyID = id associated with the key
// ASecret = text read from the .p8 file
function CreateJWT(const AIssuer, AKeyID, ASecret: string): string;
var
  LJWT: TJWT;
  LSigner: TJWS;
  LKey: TJWK;
  // LScope: TJSONArray;
begin
  LJWT := TJWT.Create;
  try
    LJWT.Header.Algorithm := 'ES256';
    LJWT.Header.KeyID := AKeyID;
    LJWT.Claims.Audience := 'appstoreconnect-v1';
    LJWT.Claims.Issuer := AIssuer;
    LJWT.Claims.IssuedAt := Now;
    LJWT.Claims.Expiration := IncSecond(Now, cTokenExpirySeconds);
    // LScope := TJSONArray.Create;
    // LScope.Add('GET /v1/profiles') ;
    // LJWT.Claims.JSON.AddPair('scope', LScope);
    LSigner := TJWS.Create(LJWT);
    try
      LSigner.SkipKeyValidation := False;
      LKey := TJWK.Create(ASecret);
      try
        LSigner.Sign(LKey, TJOSEAlgorithmId.ES256);
      finally
        LKey.Free;
      end;
      Result := LSigner.Payload + '.' + LSigner.Signature;
    finally
      LSigner.Free;
    end;
  finally
    LJWT.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  LHTTP: THTTPClient;
  LResponse: IHTTPResponse;
  LToken: string;
begin
  LToken := CreateJWT(cIssuerID, cKeyID, TFile.ReadAllText('Z:\Config\AppStoreConnectAPI\AuthKey.p8'));
  LHTTP := THTTPClient.Create;
  try
    LHTTP.CustomHeaders['Authorization'] := 'Bearer ' + LToken;
    LResponse := LHTTP.Get(cAppStoreConnectAPIGetProfiles);
    Memo1.Lines.Add(LResponse.ContentAsString);
  finally
    LHTTP.Free;
  end;
end;

The response is:

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

So I assume I am missing something. According to the documentation, adding the scope (the code that is commented out) can be optional depending on the request. Adding the scope yields the same result in this case.

 

Any clues as to what the problem may be?

Share this post


Link to post

Hi Dave,

 

how does the AIssuer looks like ?
It should be like 57246542-96fe-1a63-e053-0824d011072a , but I cannot find this from the Users table or anywhere else.

Where did you get this from ?

 

Edited by Rollo62

Share this post


Link to post
Guest

Well, i am not familiar with any of this specific Apple API and token, but i hope can help in clearing few things.

 

1) JWT will lose its most of its security value without attaching it to a URL (or path), so whenever you see JWT you can safely assume it should include the path as essential part of its signed content usage, in some cases it goes beyond the path and require majority of the header including the full request method line and a nonce may be, also i think "Scope" documentation is somehow is unclear, you mention it is optional referring to the page, i don't think it is, on contrary it should be essential, and reading that part again, though the paragraph does pointing to be optional, i might be mistaken here with this, anyway from what i saw in many different JWT login mechanism, it always required the path of the GET to be signed along its content, this mean you have to check that the scope include cAppStoreConnectAPIGetProfiles (the one passed to LHTTP), here you should be careful if your LHTTP (assuming it is using HTTP/1.1) using full path then the scope should use the same, if your HTTP is removing the host then the scope should be trimmed too.

 

2) Make sure your JWT does look like the example in the page, eg. i don't see "typ" (type) assigning in the above code, and don't know of the library does add it by default or not, you should check that, but to be sure i suggest to log your header in full along with the JWT and closely examine its structure, also check time as "Now" for me is suspicious there, does JOSE JWT convert it to what Apple assume ? (UTC) may be.

 

Hope that help and not wasting your time.

Share this post


Link to post
46 minutes ago, Rollo62 said:

It should be like 57246542-96fe-1a63-e053-0824d011072a , but I cannot find this from the Users table or anywhere else.

 

Mine looks exactly like that, but I've obfuscated the value in the code, since I don't want to give out my private info

Share this post


Link to post
1 hour ago, Kas Ob. said:

2) Make sure your JWT does look like the example in the page, eg. i don't see "typ" (type) assigning in the above code,

Yes, this was also my first thought, but I have checked that the Jose JWT library adds this "JWT" (uppercase) automatically.

But maybe worth to doublecheck, if this stays like that also after creation.

 

The notes about URL are interesting, but I think Apple uses this in its own way.
I also thought some times to use JWT Token, to create some unique, secured data, but without URL,

that should make sense too IMHO.

 

 

56 minutes ago, Dave Nottage said:

Mine looks exactly like that, but I've obfuscated the value in the code, since I don't want to give out my private info 

Did you get if from the AppStoreConnect, user section ?

I see all the user/roles, etc. but no such ID anywhere.

The problem I see is that I have several entries there anyway, under the same "Name", probably even with different Apple-Ids.

 

If this is from the MemberCenter/Certification section, I cannot spot it either.

 

I would assume that this is maybe taken from a different item, not the one that Apple wants to see here.

Nevertheless, Apple seems not havin explained that origin very well.

 

 

Share this post


Link to post
Guest
25 minutes ago, Rollo62 said:

The notes about URL are interesting, but I think Apple uses this in its own way.
I also thought some times to use JWT Token, to create some unique, secured data, but without URL,

that should make sense too IMHO.

Yes, Apple might be using different approach like depending on IssuedAt to validate the request and also to protect against repeated request.

 

The point of attaching JWT signed data in this case (JWT can provide full PKI data encryption and encapsulation) is data integrity and to provide protection against repeated requests, the idea in general is to assume the TLS layer is not there or already violated by MITM, meaning JWT will provide a different trust chain between client and server without providing privacy, while eliminating the possibility 3rd party attack who could have access to the request body from using it in malicious request (disturbing things).

 

Remembering an example: ACME protocol do require by design to include a nonce per request, this nonce is sent by server in the HTTP header in a previous response.

Another example : many exchanges uses HMAC instead of JWT or PKI singing operation, because it will do and it is easier and cheaper on heavy traffic server. yet it will require a secret or nonce to be unique for each request and server will only store this secret (to save memory ) and refuse using it within lets say 1000 request.

 

May be Apple handle IssuedAt just like 2FA because it is doable in this case, so be on the right time (UNIX epoch time) is crucial in this case. 

Share this post


Link to post

Yes, I think its just kind of access-token, and Apple recommends also a quite short lifetime < 20min for the token, to be accepted.

By the way, I think all this is still beta probably, and I know Apple for changing a lot when its about improving security 🙂

Share this post


Link to post
4 hours ago, Rollo62 said:

Did you get if from the AppStoreConnect, user section ?

I see all the user/roles, etc. but no such ID anywhere.

image.thumb.png.dfb0eee89d7cc37db075c1a6f2939650.png

 

 

Share this post


Link to post

Ok, I see.

Yes, it should be it, but stange that the issuer name is in the list below, but not the issuer key.

 

I thought that several Issuers could be possible, it looks like there can be only one, or is it different for the currently logged in user ?

I would have expected this issuer key near to the key's issuer name in the list below.

 

Share this post


Link to post
On 9/27/2021 at 8:29 PM, Dave Nottage said:

Result := LSigner.Payload + '.' + LSigner.Signature;

D'Oh. Should be:

      Result := LSigner.CompactToken;

Bit of a head-slap moment, there..

Share this post


Link to post
7 hours ago, Dave Nottage said:

D'Oh. Should be:


      Result := LSigner.CompactToken;

Bit of a head-slap moment, there..

Ok, thats an explanation.

BTW: I have found this nice article from Paolo, maybe thats helpful too

http://blog.paolorossi.net/2017/04/27/jwt-authentication-with-delphi/

I think it was planned to part 3-of-3, and part-4 came later:

http://blog.paolorossi.net/2019/07/15/jwt-authentication-with-delphi-part-4/

 

I've noticed this only later, when appearing in DelphiMagazine, so maybe I should monitor Paolo's blog more often 🙂

http://www.delphimagazine.com/2019/07/16/jwt-authentication-with-delphi-part-4/

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×