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 = "XXXXX@XXXXXX.iam.gserviceaccount.com"
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?
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.