Get FCM Service Account Access Token in Ruby

by mmyoji

2 min read

When you want to send a push notification through Firebase Cloud Messaging (FCM), you have to get access token for it.

As this page says, you need a service account JSON file from your Firebase project and can get the access token with this JSON file and Google's Ruby SDK.

However, Google's Ruby SDKs are always confusing and not well-documented.

If you have a trouble to use it, this post will help you.

Get Access Token with Ruby SDK

Suppose you already have the Service Account JSON file.

If you already use google-api-client gem, you don't need to install extra dependencies to your project.

If you don't, use googleauth gem for the porpose. ( google-api-client depends on the googleauth )

For the Service Account, use the following code like README.md says:

I refers googleauth v0.7.1 here.

require "googleauth"

# scope for FCM
scope = "https://www.googleapis.com/auth/firebase.messaging"

authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
  json_key_io: File.open('/path/to/service_account_json_key.json'),
  scope: scope,
)

authorizer.fetch_access_token!

# You can access the token with this method.
authorizer.access_token
# You can know the expiration time with this method.
authorizer.expires_at

And if you want to cache the token, you can use 2 Stores classes.

  • Google::Auth::Stores::FileTokenStore
  • Google::Auth::Stores::RedisTokenStore

You will use the latter Redis store in most cases because the File one isn't thread-safe (if this code is YAML::Store.new(path, true), it's thread-safe.),

The usage is like that:

require "googleauth"
require "googleauth/stores/redis_token_store"

ACCESS_TOKEN = "access_token"
EXPIRES_AT   = "expires_at"

# this is from https://github.com/redis/redis-rb
redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
store = Google::Auth::Stores::RedisTokenStore.new(redis: redis)

authorizer = Google::Auth::ServiceAccountCredentials.make_creds(options)

if store.load(ACCESS_TOKEN) && store.load(EXPIRES_AT)
   authorizer.access_token = store.load(ACCESS_TOKEN)
   # this `#expires_at=` method convert Integer to Time.
   authorizer.expires_at   = store.load(EXPIRES_AT)
end

# this `#expired?` method will returns false if `#expires_at` is not set. confusing.
if authorizer.access_token.nil? || (authorizer.expires_at && authorizer.expired?)
  authorizer.fetch_access_token!
  store.store(ACCESS_TOKEN, authorizer.access_token)
  store.store(EXPIRES_AT, authorizer.expires_at.to_i)
end

# you can almost always valid access token.
authorizer.access_token

Or your app process lives for a long time, you just use singleton class for it.