Google API - Reply to Reviews (androidpublisher) Error 401

1.9k Views Asked by At

I'm trying to connect from Swift app I built to Google's Reply to Reviews API which is part of https://www.googleapis.com/auth/androidpublisher scope, in order to get a list of a specific app reviews. I created a Service Account and with that I managed to created a JWT token, then tried to make a GET request to the API, but i'm getting an error:

{
  "error": {
    "code": 401,
    "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
    "status": "UNAUTHENTICATED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "CREDENTIALS_MISSING",
        "domain": "googleapis.com",
        "metadata": {
          "service": "androidpublisher.googleapis.com",
          "method": "google.play.publishingapi.v3.ReviewsService.List"
        }
      }
    ]
  }
}

This is the request url:

https://www.googleapis.com/androidpublisher/v3/applications/<APP_PACKAGE>/reviews?access_token=<JWT-TOKEN>

This is my request code:

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    
    let auth = Authentication()
    let jwt = auth.generateJWT()
    
    self.retriveReviews(packageIdentifier: "<APP_PACKAGE>", auth: jwt!)
}


func retriveReviews(packageIdentifier: String, auth: String) {
    let url = URL(string: "https://www.googleapis.com/androidpublisher/v3/applications/\(packageIdentifier)/reviews?access_token=\(auth)")
    print("Request URL: \(url)")
    var request = URLRequest(url: url!)
    request.httpMethod = "GET"
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data else { return }
        print(String(data: data, encoding: .utf8)!)
    }

    task.resume()
}

JWT Token:

import JWTKit
import UIKit

class Authentication: NSObject {

    func generateJWT() -> String? {
            struct Header: JWTPayload {
                enum CodingKeys: String, CodingKey {
                    case alg = "alg"
                    case type = "typ"
                }
                
            var alg = "RS256"
            var type: String = "JWT"
            
            func verify(using signer: JWTSigner) throws {
                //            print(self.expireTime > Date().timeIntervalSince1970)
                fatalError()
            }
        }
        
        struct Payload: JWTPayload {
            enum CodingKeys: String, CodingKey {
                case email = "iss"
                case scope = "scope"
                case aud = "aud"
                case createdAt = "iat"
                case expireTime = "exp"
            }
            
            var email: String = "[email protected]"
            var scope: String = "https://www.googleapis.com/auth/androidpublisher"
            var aud: String = "https://oauth2.googleapis.com/token"
            var createdAt: Double = Date().timeIntervalSince1970
            var expireTime: Double = Date().advanced(by: 1000).timeIntervalSince1970
            
            
            func verify(using signer: JWTSigner) throws {
                print(self.expireTime > Date().timeIntervalSince1970)
                fatalError()
            }
        }
        
        do {
            if let certificatePath = Bundle.main.path(forResource: "k_created", ofType: "pem") {
                let certificateUrl = URL(fileURLWithPath: certificatePath)
                let certififcateData = try Data(contentsOf: certificateUrl)
                let signers = JWTSigners()
                let key = try RSAKey.private(pem: certififcateData)
                signers.use(.rs256(key: key))
                
                //                MARK: HEADER NOT USED
                let header = Header()
                let payload = Payload()
                let jwt = try signers.sign(payload)
                //                let jwt = try signers.sign(payload)
                print("JWT: \(jwt)")
                return jwt
            } else {
                return nil
            }
        } catch {
            print(error)
            return nil
        }
    }
}

What am I doing wrong? or maybe there is a step I am missing?

1

There are 1 best solutions below

4
On BEST ANSWER

You appear to be trying to send the access token inline as a query parameter. You need to send it as an authorization header.

curl \
  'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/[PACKAGENAME]/reviews' \
  --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
  --header 'Accept: application/json' \
  --compressed

The issue is you are doing access_token=\(auth) where you should be doing

request.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")

The authorization needs to be in the header as shown above and it needs to be a valid access token.

Have you run this JWT you are creating against the validation end point to ensure that its even valid. If your code works you are the first person i have seen in 10 years that has managed to create a access token from the service account credentials without using a client libray. Great job.