"InvalidProviderToken" with APN push service when using code-generated token

Problem

We have successfully set up push notifications using Apple APN service, that is push notifications work when using a token generated using the JSON Web Token Generator in the Push Notification console. However, we get an "InvalidProviderToken" error when creating using our own token using the following code.

The Key and TeamID is definitely correct (obviously, censored in the below code). When pasting our token in the JSON Web Token Validator in the Push Notification console we get the error „Invalid signing key“. We merely pasted our secret key in our setNewTokenIfNeeded code, separated on four lines using the “““ style.

Does anyone know why this error happens? Given that it works when we upload our .p8 file to the JSON Web Token Generator and we simply paste the text of this file (excluding the lines with "-----BEGIN/END PRIVATE KEY-----") I guess our secret key is correct?

Code to generate token

fileprivate var currentToken: String?
fileprivate var currentTokenCreateTime: Date?

fileprivate func setNewTokenIfNeeded() {
    
    // Ensure, token is at least 20 minutes but at most 60 minutes old
    if let currentTokenCreateTime = currentTokenCreateTime {
        let ageOfTokenInSeconds = abs(Int(currentTokenCreateTime.timeIntervalSinceNow))
        NSLog("Age of token: \(Int(ageOfTokenInSeconds / 60)) minutes.")
        if ageOfTokenInSeconds <= 20 * 60 { return }
    }
    
    // Generate new token
    NSLog("Renewing token.")
    let secret = """
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCABCABCABCABCABCABCABCABCABCABC+ABCABC+ABCABCABC+ABCABCAB/+
ABCABCAB
"""
    let privateKey = SymmetricKey(data: Data(secret.utf8))

    let headerJSONData = try! JSONEncoder().encode(Header())
    let headerBase64String = headerJSONData.urlSafeBase64EncodedString()

    let payloadJSONData = try! JSONEncoder().encode(Payload())
    let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString()

    let toSign = Data((headerBase64String + "." + payloadBase64String).utf8)

    let signature = HMAC<SHA256>.authenticationCode(for: toSign, using: privateKey)
    let signatureBase64String = Data(signature).urlSafeBase64EncodedString()

    let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".")
    
    currentToken = token
    currentTokenCreateTime = Date()
}

fileprivate struct Header: Encodable {
    let alg = "ES256"
    let kid: String = "ABCABCABC" // Key (censored here)
}

fileprivate struct Payload: Encodable {
    let iss: String = "ABCABCABC" // Team-ID (censored here)
    let iat: Int    = Int(Date().timeIntervalSince1970)
}

extension Data {
    func urlSafeBase64EncodedString() -> String {
        return base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}

Code to send the push notification

func SendPushNotification(category: ConversationCategory,
                          conversationID: UUID,
                          title: String,
                          subTitle: String?,
                          body: String,
                          devicesToSendTo: [String]) {
    
    // Für alle Felder s. https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification
    let payload = [
        "aps": [
            "alert": [
                "title": title,
                "subtitle" : subTitle ?? "",
                "body": body
            ],
            "category" : category.rawValue,
            "mutable-content": 1
        ],
        "conversationID": conversationID.uuidString
    ] as [String : Any]
    
    // Ggf. Token setzen
    setNewTokenIfNeeded()
    guard let currentToken = currentToken else {
        NSLog("Token not initialized.")
        return
    }
    NSLog(currentToken)
    
    // Notification an alle angegebenen Devices schicken
    let bundleID = "com.TEAMID.APPNAME"
    
    for curDeviceID in devicesToSendTo {
        NSLog("Sending push notification to device with ID \(curDeviceID).")
        
        let apnServerURL = "https://api.sandbox.push.apple.com:443/3/device/\(curDeviceID)"
        var request = URLRequest(url: URL(string: apnServerURL)!)
        request.httpMethod = "POST"
        request.allHTTPHeaderFields = [
            "authorization": "bearer " + currentToken,
            "apns-id": UUID().uuidString,
            "apns-topic": bundleID,
            "apns-priority": "10",
            "apns-expiration": "0"
        ]
        request.httpBody = try! JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted)
        
        URLSession(configuration: .ephemeral).dataTask(with: request) { data, response, error in
            if let error = error {
                NSLog(error.localizedDescription)
            }
            if let data = data {
                NSLog(String(data: data, encoding: .utf8)!)
            }
        }.resume()
        
    }
    
} 

On a similar note, some people seem to encounter this error when using the prettyPrinted option for the JSON serialization (i.e., in request.httpBody = try! JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted). Could this be the culprit, given our secret key contains „/„ and „+“?

Many thanks!

"InvalidProviderToken" with APN push service when using code-generated token
 
 
Q