2

I am trying to connect to Apple Store Connect through their REST API's. Though this was working a few days ago, but i am not able to figure out why it's stopped working. Now i am not able to get past being authenticated i.e. each request i make the server response is a 401. Am i missing something?

What i do:

  1. Generate the JWT (i use SwiftJWT library)
  2. Create URLRequest object with the relevant headers set
  3. Make a call to the API using the created URLRequest

Generating the JWT:

func generateJWT() -> String {
        var signedJWT = ""
        
        let pathToKey = URL(fileURLWithPath: "AuthKey.p8")
        
        // Header - alg is automatically set to ES256, and so is typ is set to JWT
        let header = Header(kid: "********")
        
        // Represents the payload being used to generate the JWT
        let claim = MyClaims(iss: "***", exp: Date(timeIntervalSinceNow: 3600), aud: "appstoreconnect-v1")
        var jwt = JWT(header: header, claims: claim)
        
        do {
            let privateKey: Data = try Data(contentsOf: pathToKey, options: .alwaysMapped)
            let jwtSigner = JWTSigner.es256(privateKey: privateKey)
            
            signedJWT = try jwt.sign(using: jwtSigner)
        } catch {
            print("There was an error getting the key...\(error)")
        }
        
        return signedJWT
    }

Then i create a URLRequest with all the required headers:

func createRequest(with jwt: String) -> URLRequest? {
        guard let url = URL(string: "https://api.appstoreconnect.apple.com/v1/users") else {
            print("Ugh! Something went wrong with the URL provided...")
            return nil
        }
        
        var request = URLRequest(url: url)
        request.setValue(jwt, forHTTPHeaderField: "Authorization")
        request.httpMethod = "GET"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        return request
    }

Then i make the request:

func performRequest(request: URLRequest) -> Bool {
        var status = false
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let data = data {
                if let decodedResponse = try? JSONDecoder().decode([String:String].self, from: data) {
                    // For this sample project we do nothing other than celebrate
                    
                    // everything is good, so we can exit
                    status = true
                    return
                }
            }
            
            // if we're still here it means there was a problem
            print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
            print("Fetch failed: \(String(describing: response))")
            status = false

        }.resume()
        
        return status
    }

Response

Fetch failed: Optional(<NSHTTPURLResponse: 0x600002ac95e0> { URL: https://api.appstoreconnect.apple.com/v1/users } { Status Code: 401, Headers {
    "Content-Length" =     (
        350
    );
    "Content-Type" =     (
        "application/json"
    );
    Date =     (
        "Fri, 09 Jul 2021 15:47:27 GMT"
    );
    Server =     (
        "daiquiri/3.0.0"
    );
    "Strict-Transport-Security" =     (
        "max-age=31536000; includeSubDomains"
    );
    "x-apple-jingle-correlation-key" =     (
        XXXXXXXXXXX
    );
    "x-daiquiri-instance" =     (
        "daiquiri:38493001:pv50p00it-hyhk12043901:7987:21HOTFIX14"
    );
} })

Is there anything i'm doing incorrectly or missing?

SteedsOfWar
  • 543
  • 2
  • 9
  • 23
  • 1
    `try? JSONDecoder().decode` throws away useful info. – matt Jul 09 '21 at 16:05
  • I'm having this issue too with the App Store Connect API. It's been working fine for months but I've noticed the JWT element is no longer working. Generating a JWT using another method worked fine but with SwiftJWT, it no longer works. – adamfootdev Jul 14 '21 at 13:24
  • I've posted the solution. It seems to be related to the expiry duration. Reaching out to Apple wasn't helpful, so i'm not sure if they've made any changes, as the expiry set to 3600 has been working successfully for weeks. – SteedsOfWar Jul 15 '21 at 07:21

1 Answers1

1

I reached out to Apple via a TSI request and a Apple Store Connect Help ticket. In both instances they weren't much help.

After trying different things i've managed to find the issue, and reproduce it.

Changing the expiry duration from 3600 to 1200 seconds seems to fix the issue.

From: exp: Date(timeIntervalSinceNow: 3600)
To: exp: Date(timeIntervalSinceNow: 1200)

Response:
< HTTP/2 200 
< server: daiquiri/3.0.0
< date: Thu, 15 Jul 2021 07:15:44 GMT
< content-type: application/json
< content-length: 964
SteedsOfWar
  • 543
  • 2
  • 9
  • 23
  • 1
    Apple's doco states: For most requests, App Store Connect rejects a token with a lifetime greater than 20 minutes. However, it accepts long-lived tokens for some inherently safe requests if: The token defines a scope. The scope only includes GET requests. The resources in the scope allow long-lived tokens. – Adam Aug 03 '22 at 01:05
  • Can confirm that 1200 works as above. Exceeding the API limit results in the misleading 401 with error title "Authentication credentials are missing or invalid.” and 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" – Max MacLeod Mar 17 '23 at 12:24