onmyway133 / blog

🍁 What you don't know is what you haven't learned
https://onmyway133.com/
MIT License
669 stars 33 forks source link

How to generate JWT token for App Store Connect API in Swift #868

Open onmyway133 opened 2 years ago

onmyway133 commented 2 years ago

Use JWTKit and code from AppStoreConnect library

import JWTKit

public struct Credential {
    let issuerId: String
    let privateKeyId: String
    let privateKey: String

    public init(
        issuerId: String,
        privateKeyId: String,
        privateKey: String
    ) {
        self.issuerId = issuerId
        self.privateKeyId = privateKeyId
        self.privateKey = privateKey
    }

    func generateJWT() throws -> String {
        guard let signer = try? JWTSigner.es256(
            key: ECDSAKey.private(pem: privateKey))
        else {
            throw AppStoreConnectError.invalidJWT
        }

        let payload = Payload(
            issueID: IssuerClaim(value: issuerId),
            expiration: ExpirationClaim(
                value: Date(
                    timeInterval: 2 * 60,
                    since: Date()
                )
            ),
            audience: AudienceClaim(
                value: "appstoreconnect-v1"
            )
        )

        guard let jwt = try? signer.sign(
            payload,
            kid: JWKIdentifier(string: privateKeyId)
        ) else {
            throw AppStoreConnectError.invalidJWT
        }

        return jwt
    }
}

struct Payload: JWTPayload {
    private enum CodingKeys: String, CodingKey {
        case issueID = "iss"
        case expiration = "exp"
        case audience = "aud"
    }

    var issueID: IssuerClaim
    var expiration: ExpirationClaim
    var audience: AudienceClaim

    func verify(using signer: JWTSigner) throws {
        try expiration.verifyNotExpired()
    }
}
dominiquemiller commented 1 year ago

Thanks! This saved me a lot of time! Only one change I had to make was to add the bundleID or "bid" to the Payload struct, which is required by Apple.

struct Payload: JWTPayload {
    private enum CodingKeys: String, CodingKey {
        case issueID = "iss"
        case expiration = "exp"
        case audience = "aud"
        case bundleId = "bid"
    }

    var issueID: IssuerClaim
    var expiration: ExpirationClaim
    var audience: AudienceClaim
    var bundleId: String

    func verify(using signer: JWTSigner) throws {
        try expiration.verifyNotExpired()
    }
}