6

I have a firebase project which Im trying to authenticate from my rails server creating a custom token with the library ruby-jwt as it says on the docs, but i keep getting the same error:

auth/invalid-custom-token, The custom token format is incorrect. Please check the documentation.

The credentials.json is from the service account I made in google console, uid is sent from the front end to the api.

def generate_auth_token(uid)
  now_seconds = Time.now.to_i
  credentials = JSON.parse(File.read("credentials.json"))
  private_key = OpenSSL::PKey::RSA.new credentials["private_key"]
  payload = {
      :iss => credentials["client_email"],
      :sub => credentials["client_email"],
      :aud => 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
      :iat => now_seconds,
      :exp => now_seconds+(60*60), # Maximum expiration time is one hour
      :uid => uid.to_s,
      :claims => {:premium_account => true}
    }
  JWT.encode(payload, private_key, 'RS256')
end

it looks like this in jwt.io

{
  "iss": "defered@defered.iam.gserviceaccount.com",
  "sub": "defered@defered.iam.gserviceaccount.com",
  "aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
  "iat": 1486824545,
  "exp": 1486828145,
  "uid": "4",
  "claims": {
     "premium_account": true
   }
}
Porfirio Diaz
  • 81
  • 1
  • 7
  • Please, take a look at my [firebase_id_token](https://github.com/fschuindt/firebase_id_token) gem. It does exactly what you want. – fschuindt Sep 21 '17 at 04:26

3 Answers3

4

It looks like the accepted answer found a way to link authentication from Firebase to Rails, but the original question seems to be asking how to link Rails authentication to Firebase (which is what I was trying to do).

To keep your authentication logic in Rails (ex: from Devise) and share it with Firebase, first get a Firebase server key as a .json file from your Service Accounts page in your project's settings.

You'll only need the private_key and client_id from this file, which I recommend storing as environment variables so they're not potentially leaked in source code.

Next, make a Plain ol' Ruby object (PORO) that will take in a User and spit out a JSON Web Token (JWT) that Firebase can understand:

class FirebaseToken
  def self.create_from_user(user)
    service_account_email = ENV["FIREBASE_CLIENT_EMAIL"]
    private_key = OpenSSL::PKey::RSA.new ENV["FIREBASE_PRIVATE_KEY"]

    claims = {
      isCool: "oh yeah"
    }

    now_seconds = Time.now.to_i
    payload = {
      iss: service_account_email,
      sub: service_account_email,
      aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
      iat: now_seconds,
      exp: now_seconds + (60*60), # Maximum expiration time is one hour
      uid: user.id,
      # a hash to pass to the client as JSON
      claims: claims
    }
    JWT.encode payload, private_key, "RS256"
  end
end

Now send this JWT to authenticated users through javascript in your application layout:

window.firebaseJWT = "#{FirebaseToken.create_from_user(current_user)}";

In your frontend code, you can now use this token to authenticate users:

firebase
  .auth()
  .signInWithCustomToken(window.firebaseJWT)
  .catch(error => {
    console.error(error);
  });

Remember to sign them out of firebase when they sign out of your application:

firebase
  .auth()
  .signOut()
  .then(() => {
    // Sign-out successful.
  })
  .catch(error => {
    console.error(error);
  });
cgenco
  • 3,370
  • 2
  • 31
  • 36
  • 1
    Somehow I overlooked that "exp" is a *different time*, not a _difference_ in time. I had "exp: 3600" until I saw: `now_seconds + 60*60` – wtr Nov 24 '20 at 00:40
  • @cgenko do you know what data is stored in Firebase? I'm trying to understand what would be stored, and how I could use that data to authorize certain paths in Firebase's realtime database. For example, I want a user with id: 3 to be able to read/write in the path: /3/my_data – Matt Feb 21 '23 at 16:05
  • @Matt I'm not sure I understand your question, and I think it's out of scope for this answer. Try making a new question! – cgenco Feb 22 '23 at 19:59
  • @cgenco I'm more just curious if `firebase.auth().signInWithCustomToken(window.firebaseJWT)` stores any data in firebase. I suppose not, if all we're sending it is the jwt, a user.id, and a set of claims. – Matt Feb 22 '23 at 21:25
  • 1
    @Matt ahh I think I see what you're saying. I believe signing in users via. any method creates a new user in the Authentication tab but doesn't by default create any data in Firestore. You can set up firebase cloud functions to write data to Firestore triggered by user auth events though. – cgenco Mar 02 '23 at 23:45
2

I found a better way to authenticate, I'm just sending the token that firebase gives you and verifying it on rails with the information I need and that's it.

Porfirio Diaz
  • 81
  • 1
  • 7
0

Check if your secret key is wrapped in double quotes and not single as they contain '\n' escape sequences. An auth/invalid-custom-token error is thrown if the secret key is not as specified in the documentation.

jeanluc rotolo
  • 165
  • 1
  • 11