Swift Vapor unsupported_grant_type invalid signatu

2019-07-27 05:00发布

问题:

I am running Xcode 8.1 with Vapor and SWIFT 3.

I am posting a request to to google server to get an auth token, so I can call FireBaseDB API, but I get error: unsupported_grant_type/Invalid grant_type.
On developers.google.com it says that I have to encode in a URL the following: https://www.googleapis.com/oauth2/v4/token + grant_type + assertion, and pass the encoded URL in the body of the POST request. I pass it as a string.

I have noticed that the private key from the JSON file downloaded from my service account contains characters such as /n , ----,==, should I delete them before posting the key?

    let dateNow = Date()
      var expDate = String(Int(dateNow.timeIntervalSince1970 + (60 * 60)))
        var iatDate = String(Int(dateNow.timeIntervalSince1970))


 let headerJWT = ["alg":"HS256","typ":"JWT"]
    let jwtClaimSet =
    ["iss":"firebase-adminsdk-c7i48@fir-10c2e.iam.gserviceaccount.com",
     "scope":"https://www.googleapis.com/auth/firebase.database",
      "aud":"https://www.googleapis.com/oauth2/v4/token",
       "exp": expDate,
         "iat": iatDate]

      //create and sign JSON Web Token
   let jwt = try JWT(headers: Node(node: headerJWT),
              payload: Node(node: jwtClaimSet),
               signer: HS256(key:"-----BEGIN PRIVATE KEY-----\nMIIEvAWvQ== \n-----END PRIVATE KEY-----\n"))

    // Store JSON Web Token
      let JWTtoken = try jwt.createToken()

func createUrlWithString() -> NSURL {
    var urlString = "https://www.googleapis.com/oauth2/v4/token"
     urlString.append("?grant_type=")
      urlString.append("urn:ietf:params:oauth:grant-type:jwt-bearer")
       urlString.append("&assertion=")
        urlString.append(JWTtoken)
  return NSURL(string: urlString)!
 }

        // make the body input for our POST
      let bodyURL =  createUrlWithString().absoluteURL

     drop.get("any") { request in
        let response =  
 try drop.client.request(.other(method:"Post"),
           "https://www.googleapis.com/oauth2/v4/token", 
             headers: ["Content-Type": "application/x-www-form-urlencoded"], 
                 query: [:], 
                  body: String(describing: bodyURL) )


     let serverResp = response.headers
        let serverBody = response.body.bytes
          let serverJson = try JSON(bytes: serverBody!)
             print(serverJson)
    return "POST Request went through"
}

Update

As per Karol Gasienica suggestion, I am passing grant_type and assertion parameters as POST request parameters. Now I get "error_description": Node.Node.string("SSL is required to perform this operation.")]))

func createUrlWithString() -> NSURL {
 var urlString = "https://www.googleapis.com/oauth2/v4/token"
  urlString.append("?grant_type=")
    urlString.append("urn:ietf:params:oauth:grant-type:jwt-bearer")
     urlString.append("&assertion=")
      urlString.append(JWTtoken)
  return NSURL(string: urlString)!
}

  let response =  try drop.client.request(.other(method:"Post"), 
   String(describing: bodyURL),
      headers: ["Content-Type": "application/x-www-form-urlencoded"], 
         query: [:])

回答1:

When you generate a service account credential you need to have in mind the following, taken from https://cloud.google.com/storage/docs/authentication: You can create a private key in the Cloud Platform Console by creating an OAuth Client ID for a service account. You can get your private key in JSON and PKCS12 format:

JSON keys are required if you are using Application Default Credentials in a production environment outside of Google Cloud Platform. JSON keys cannot be converted to other formats. PKCS12 (.p12) is supported by many different programming languages and libraries. If needed, you can convert the key into other formats using OpenSSL (see Converting the private key to other formats). However, PKCS12 keys cannot be converted to JSON format.

  1. Create your service account and then download your .p12 file.
  2. Convert the p.12 (a.k.a pkcs12) file to .pem (a.k.a pkcs1) using OpenSSL

cat /path/to/xxxx-privatekey.p12 | openssl pkcs12 -nodes -nocerts -passin pass:notasecret | openssl rsa > /path/to/secret.pem

  1. Go to github and search VaporJWT and import it in Xcode. It will help you create a signed JSON Web Token.

  2. On this github page you will learn how to extract the private key for RSA use.

  3. // convert .pem to der
    openssl rsa -in /path/to/secret.pem -outform der -out /path/to/private.der

  4. //convert .der to .base64
    openssl base64 -in /path/to/private.der -out /path/to/Desktop/private.txt
    In private.txt you have the private key encoded in base64 which you can finally use to sign you JWT. Then you can make calls to Google API with the signed JWT.

import Vapor
import VaporJWT

  let drop = Droplet()
   var tokenID:String!

  //set current date
   let dateNow = Date()

// assign to expDate the validity period of the token returnd by OAuth server (3600 seconds)
  var expDate = String(Int(dateNow.timeIntervalSince1970 + (60 * 60)))

 // assign to iatDate the time when the call was made to request an access token
    var iatDate = String(Int(dateNow.timeIntervalSince1970))

// the header of the JSON Web Token (first part of the JWT)
let headerJWT = ["alg":"RS256","typ":"JWT"]

// the claim set of the JSON Web Token
let jwtClaimSet =
   ["iss":"firebase-adminsdk-c7i38@fir-30c9e.iam.gserviceaccount.com",
     "scope":"https://www.googleapis.com/auth/firebase.database",
       "aud":"https://www.googleapis.com/oauth2/v4/token",
        "exp": expDate,
         "iat": iatDate]


//Using VaporJWT construct a JSON Web Token and sign it with RS256 algorithm
//The only signing algorithm supported by the Google OAuth 2.0 Authorization Server is RSA using SHA-256 hashing algorithm.

    let jwt = try JWT(headers: Node(node: headerJWT), payload: Node(node:jwtClaimSet), encoding: Base64URLEncoding(), signer: RS256(encodedKey: "copy paste here what you have in private.txt as explained at point 7 above "))

// create the JSON Web Token
 let JWTtoken = try jwt.createToken()
let grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer" // this value must not be changed
     let unreserved = "*-._"
      let allowed = NSMutableCharacterSet.alphanumeric()
        allowed.addCharacters(in: unreserved)

 // percent or URL encode grant_type
  let grant_URLEncoded = grant_type.addingPercentEncoding(withAllowedCharacters: allowed as CharacterSet)

 // create a string made of grant_type and assertion. NOTE!!! only grant_type's value is URL encoded.
 //JSON Web Token value does not need to be URL encoded
   var fullString = "grant_type=\(grant_URLEncoded!)&assertion=\(JWTtoken)"


 //pass fullString in the body parameter
 drop.get("call") { request in


     let response =  try drop.client.post("https://www.googleapis.com/oauth2/v4/token", headers: ["Content-Type": "application/x-www-form-urlencoded"], query: [:],body: fullString)

    let serverResp = response.headers
       let serverBody = response.body.bytes
          let serverJson = try JSON(bytes: serverBody!)
            print(serverJson)

return "Success"
}


回答2:

It seems like you dont set properly grant_type in your code:

urlString.append("?grant_type=")

In your case it might be grant_type=authorization_code or grant_type=jwt-bearer

It seems like you set grant_type in wrong place.

Update

Also I think grant_type and assertion parameters are not passed as request headers but as post request parameters

Update

Im not really sure you are using correct way to put POST (body) parameters. In this documentation is example how to create request with post prams and it looks like follows:

try drop.client.request(.other(method: "POST"), "http://some-domain", headers: ["My": "Header"], query: ["key": "value"], body: [])