7

So I'm trying to authenticate the Firebase REST API. I'm using the Vapor framework for server side swift and I installed the JWT package.

I'm trying to use the data in the serviceAccountKey.json file and JWT to generate an auth token.

Here is the code I've tried:

let payload = try JSON(node: [
        "iat": Date().timeIntervalSince1970,
        "exp": Date().timeIntervalSince1970 + 3600,
        "iss": "client_email from serviceAccountKey.json",
        "aud": "https://accounts.google.com/o/oauth2/token",
        "scope": [
            "https://www.googleapis.com/auth/firebase.database",
            "https://www.googleapis.com/auth/userinfo.email"
        ]
    ])
    let privateKey = "copied from serviceAccountKey.json"

    let signer = try HS256(bytes: privateKey.bytes)

    let jwt = try JWT(payload: payload, signer: signer)
    let token = try jwt.createToken()
    print(token)

serviceAccountKey.json

{
  "type": "service_account",
  "project_id": "",
  "private_key_id": "",
  "private_key": "",
  "client_email": "",
  "client_id": "",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": ""
}
rmaes4
  • 555
  • 9
  • 22
  • You have not explained a problem. You have that code. Good. What's wrong with it? – zerkms Sep 25 '17 at 01:03
  • @zerkms This was just my attempt at generating the token. The token doesn't work and I get "permission denied" – rmaes4 Sep 25 '17 at 02:31
  • Have you checked that the generated JWT looks exactly as you expect? – zerkms Sep 25 '17 at 03:22
  • @zerkms I'm not sure what it should look like – rmaes4 Sep 25 '17 at 03:43
  • As a first step, check if the token can be decoded on jwt.io. – Frank van Puffelen Sep 25 '17 at 04:56
  • @FrankvanPuffelen so I generated a valid access token. I figured out it uses RSA. Unfortunately when I paste it in that website it says "invalid signature". This is strange because It works fine with the google client api – rmaes4 Sep 25 '17 at 15:21
  • I found https://stackoverflow.com/questions/11126439/google-oauth-2-0-jwt-token-request-for-service-application?rq=1 I'm going to try and solve this. Unfortunately I'm running into an error with my JWT library. – rmaes4 Sep 25 '17 at 16:20
  • I found that an access token needs to be generated for each instance, but no straight forward answer how to do this on server-side swift. – Justin Sep 27 '17 at 00:43
  • any news? stuck with the same error – David Seek Oct 25 '17 at 01:33
  • @DavidSeek Please see my answer. It works on my server. I must say, it took me a while to get to that. – bibscy Oct 26 '17 at 09:10
  • @rmaes4, you may try to implement my answer. It should work. – bibscy Oct 27 '17 at 18:00

2 Answers2

4

At this time I am using Xcode 8.3.3. Package.swift contains:

let package = Package(
name: "StripePayment",
dependencies: [
    .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 5),
    .Package(url:"https://github.com/vapor/jwt.git", majorVersion: 0,minor: 8),
     .Package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", versions: Version(1, 0, 0)..<Version(3, .max, .max))

],
exclude: [
    "Config",
    "Database",
    "Localization",
    "Public",
    "Resources",
    "Tests",
]
)

If 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.

Note: You do NOT need to generate a service account at console.cloud.google.com . Just follow the steps 1...6 listed below.

  1. Go to https://console.firebase.google.com , click on your project, next to Overview click on the wheel Settings, click on Service Accounts, scroll to the bottom of the page and click on Generate New Private Key.

  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

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

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

  5. Convert .pem to der
    openssl rsa -in /path/to/secret.pem -outform der -out /path/to/private.der

  6. 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 your 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 returned 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"
bibscy
  • 2,598
  • 4
  • 34
  • 82
  • I have added the .package in my project. As it seems u have used the library so would u tell us why i m getting the error "import Node" not such module in the package. I also looked for the file in source but there is no such file. – Shashi Ranjan Dec 14 '17 at 10:59
  • @avi how is it not valid? can you explain? show some code? – bibscy May 27 '18 at 19:52
  • @bibscy, i am getting error message, unsupported_grant_type(error_description" = "Invalid grant_type: ";). – Avinash Kashyap May 28 '18 at 18:16
  • @You should put it in a question. I am running succesfully my project with `let grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer" ` Make sure you have the exact same line of code. – bibscy May 28 '18 at 18:23
  • I was wrong. Your answer is correct. sorry for that – Avinash Kashyap May 28 '18 at 19:16
  • Well, you can upvote :) so others will find the answer – bibscy May 28 '18 at 19:17
  • Meanwhile the whole thing has changed and I can only download the JSON file. I don't really get it to work in Swift. I'm using [SwiftJWT[(https://github.com/IBM-Swift/Swift-JWT) hwich works well for App Store Connect API and Apple Push Notification Service. But for the Firebase Cloud Messaging I don't get it right :-( The key is also much shorter when I generate it using the a node script with the googleapis compared to the swift version. I'm trying to make a command line tool to test push via APNS and FCM... – blackjacx Aug 18 '20 at 20:00
2

If you just want to get things working, better off using version 1.5.0

.Package(url: "https://github.com/gtchance/FirebaseSwift.git", Version(1,5,0)),

And using the legacy secret. Project Settings>Service Accounts>Database Secrets

Justin
  • 717
  • 1
  • 9
  • 15
  • This is what I am doing for the time being. Not sure if I should mark this as a "solution" though because the original issue remains. – rmaes4 Sep 29 '17 at 15:40