Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Nothing.
- SimpleCov JSON formatter and `json` as dependency.

### Changed
- Redis is no longer required. Any ActiveSupport cache will now work.
- It won't default to `Redis.new` anymore. You must now provide Redis details during configuration. [Details here](https://github.com/fschuindt/firebase_id_token/issues/30).
- Upgraded Redis to 5.0.6.
- Upgraded Redis Namespace to 1.10.
Expand Down
54 changes: 33 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,17 @@
[![Issue Count](https://codeclimate.com/github/fschuindt/firebase_id_token/badges/issue_count.svg)](https://codeclimate.com/github/fschuindt/firebase_id_token)
[![Inline docs](http://inch-ci.org/github/fschuindt/firebase_id_token.svg?branch=master)](http://inch-ci.org/github/fschuindt/firebase_id_token)

A Ruby gem to verify the signature of Firebase ID Tokens (JWT). It uses Redis to store Google's x509 certificates and manage their expiration time, so you don't need to request Google's API in every execution and can access it as fast as reading from memory.
A Ruby gem to verify the signature of Firebase ID Tokens (JWT). It uses ActiveSupport::Cache to store Google's x509 certificates and manage their expiration time, so you don't need to request Google's API in every execution and can access it as fast as reading from memory.

It also checks the JWT payload parameters as recommended [here](https://firebase.google.com/docs/auth/admin/verify-id-tokens) by Firebase official documentation.

Feel free to open any issue or to [contact me](https://fschuindt.github.io/blog/about/) directly.
Feel free to open any issue or to [contact me](https://fschuindt.github.io/blog/about/) directly.
Any contribution is welcome.

## Docs

+ http://www.rubydoc.info/gems/firebase_id_token

## Requirements

+ Redis

## Installing

```
Expand All @@ -42,13 +38,27 @@ It's needed to set up your Firebase Project ID.

If you are using Rails, this should probably go into `config/initializers/firebase_id_token.rb`.
```ruby
FirebaseIdToken.configure do |config|
config.cache_store = ActiveSupport::Cache::RedisCacheStore.new
config.project_ids = ['your-firebase-project-id']
end
```

You can use the old method of configuration as well. If you use this method, you'll have to proactively
download certificates (see [Downloading Certificates](#downloading-certificates) below)
```ruby
FirebaseIdToken.configure do |config|
config.redis = Redis.new
config.project_ids = ['your-firebase-project-id']
end
```

- `redis` with a `Redis` instance must be supplied. You can configure your Redis details here. Example: `Redis.new(host: '10.0.1.1', port: 6380, db: 15)`.
- A cache store instance inheriting from [ActiveSupport::Cache::Store](https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html) must be supplied.
- Examples:
- `ActiveSupport::Cache::RedisCacheStore.new(Redis.new(host: '10.0.1.1', port: 6380, db: 15))`
- `ActiveSupport::Cache::MemoryStore.new(namespace: "firebase_auth")`
- `ActiveSupport::Cache::FileStore.new("cache", namespace: "firebase_auth")`
- `Rails.cache`
- `project_ids` must be an Array.

*If you want to verify signatures from more than one Firebase project, just add more Project IDs to the list.*
Expand All @@ -67,22 +77,22 @@ To do it, simply:
FirebaseIdToken::Certificates.request
```

It will download the certificates and save it in Redis, but only if the Redis certificates database is empty. To force download and override of the Redis database, use:
It will download the certificates and save it in cache, but only if the cache certificates database is empty. To force download and override of the cache entries, use:
```ruby
FirebaseIdToken::Certificates.request!
```

Google give us information about the certificates' expiration time, it's used to set a Redis TTL (Time-To-Live) when saving it. By doing so, the certificates will be automatically deleted after its expiration.
Google give us information about the certificates' expiration time, it's used to set a cache TTL (Time-To-Live) when saving it. By doing so, the certificates will be automatically deleted after its expiration.

#### Certificates Info

Checks the presence of certificates in the Redis database.
Checks the presence of certificates in the cache.
```ruby
FirebaseIdToken::Certificates.present?
=> true
```

How many seconds until the certificate's expiration.
How many seconds until the certificate's expiration. _NOTE_: Currently only functional when using Redis
```ruby
FirebaseIdToken::Certificates.ttl
=> 22352
Expand All @@ -101,6 +111,8 @@ FirebaseIdToken::Certificates.find('ec8f292sd30224afac5c55540df66d1f999d')
```

#### Downloading in Rails
If you pass in the `cache_store` configuration option (see [configuration](#configuration)), the certificates will be
requested at runtime when needed and you can ignore this section.

If you are using Rails, it's clever to download certificates in a cron task, you can use [whenever](https://github.com/javan/whenever).

Expand All @@ -112,12 +124,12 @@ Create your task in `lib/tasks/firebase.rake`:
```ruby
namespace :firebase do
namespace :certificates do
desc "Request Google's x509 certificates when Redis is empty"
desc "Request Google's x509 certificates when the cache is empty"
task request: :environment do
FirebaseIdToken::Certificates.request
end

desc "Request Google's x509 certificates and override Redis"
desc "Request Google's x509 certificates and override the cache"
task force_request: :environment do
FirebaseIdToken::Certificates.request!
end
Expand All @@ -144,7 +156,7 @@ When developing, you should just run the task:
$ rake firebase:certificates:request
```

*You need Redis to be running.*
*You need Redis to be running if you're using RedisCacheStore*

### Verifying Tokens

Expand Down Expand Up @@ -180,7 +192,7 @@ More details [here](https://github.com/fschuindt/firebase_id_token/issues/29).

##### Trying to verify tokens without downloaded certificates will raise an error

If you try to verify a signature without any certificates in Redis database, it will raise a `FirebaseIdToken::Exceptions::NoCertificatesError`.
If you try to verify a signature without any certificates in the cache, it will raise a `FirebaseIdToken::Exceptions::NoCertificatesError`.

##### "I keep on getting `nil` on `verify`"

Expand All @@ -190,7 +202,7 @@ Poorly synchronized clocks will sometimes make the server think the token's `iat

In case you need, here's a example of the payload structure from a Google login in JSON.
```json
{
{
"iss":"https://securetoken.google.com/{{YOUR_FIREBASE_APP_ID}}",
"name":"Ugly Bob",
"picture":"https://someurl.com/photo.jpg",
Expand All @@ -202,12 +214,12 @@ In case you need, here's a example of the payload structure from a Google login
"exp":33029000017, // needs to be in the future
"email":"uglybob@emailurl.com",
"email_verified":true,
"firebase":{
"identities":{
"google.com":[
"firebase":{
"identities":{
"google.com":[
"1010101010101010101"
],
"email":[
"email":[
"uglybob@emailurl.com"
]
},
Expand Down Expand Up @@ -268,7 +280,7 @@ module Api
@routes = Engine.routes
@user = users(:one)
end

def create_token(sub: nil)
_payload = payload.merge({sub: sub})
JWT.encode _payload, OpenSSL::PKey::RSA.new(FirebaseIdToken::Testing::Certificates.private_key), 'RS256'
Expand Down
6 changes: 3 additions & 3 deletions firebase_id_token.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Gem::Specification.new do |spec|

spec.summary = 'A Firebase ID Token verifier.'
spec.description = "A Ruby gem to verify the signature of Firebase ID "\
"Tokens. It uses Redis to store Google's x509 certificates and manage "\
"Tokens. It uses ActiveSupport::Cache to store Google's x509 certificates and manage "\
"their expiration time, so you don't need to request Google's API in "\
"every execution and can access it as fast as reading from memory."
spec.homepage = 'https://github.com/fschuindt/firebase_id_token'
Expand All @@ -29,9 +29,9 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'simplecov', '~> 0.22.0'
spec.add_development_dependency 'simplecov_json_formatter', '~> 0.1.2'
spec.add_development_dependency 'pry', '~> 0.14.2'
spec.add_development_dependency 'redis', '~> 5.0', '>= 5.0.6'
spec.add_development_dependency 'redis-namespace', '~> 1.10'

spec.add_runtime_dependency 'redis', '~> 5.0', '>= 5.0.6'
spec.add_runtime_dependency 'redis-namespace', '~> 1.10'
spec.add_dependency 'httparty', '~> 0.21.0'
spec.add_runtime_dependency 'jwt', '~> 2.7'
spec.add_runtime_dependency 'activesupport', '~> 7.0', '>= 7.0.4.3'
Expand Down
17 changes: 11 additions & 6 deletions lib/firebase_id_token.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
require 'redis'
require 'redis-namespace'
require 'httparty'
require 'jwt'
require 'active_support'
require 'active_support/time'
require 'active_support/cache'

require 'firebase_id_token/version'
require 'firebase_id_token/exceptions/no_certificates_error'
require 'firebase_id_token/exceptions/certificates_request_error'
require 'firebase_id_token/exceptions/certificates_ttl_error'
require 'firebase_id_token/exceptions/unsupported_cache_operation_error'
require 'firebase_id_token/exceptions/certificate_not_found'
require 'firebase_id_token/configuration'
require 'firebase_id_token/certificates'
Expand All @@ -30,20 +30,18 @@
#
# ## Configuration
#
# You need to set your Firebase Project ID. Additionally you can set your Redis
# server instance in case you don't use Redis defaults.
# You need to set your Firebase Project ID and cache store.
#
# **WARNING:** Your `project_ids` must be a `Array`.
# ```
# FirebaseIdToken.configure do |config|
# config.project_ids = ['my-project-id', 'another-project-id']
# congig.redis = Redis.new(:host => "10.0.1.1", :port => 6380, :db => 15)
# congig.cache_store = ActiveSupport::Cache::RedisCacheStore.new
# end
# ```
#
# **Defaults**
# + `project_ids` => `[]`
# + `redis` => `Redis.new`
#
module FirebaseIdToken
class << self
Expand All @@ -61,6 +59,13 @@ def self.reset

def self.configure
yield configuration
# backward compatible with the config.redis = Redis.new setup that is the old way of configuing the gem
if configuration.redis
require 'firebase_id_token/certificates/redis'
configuration.cache_store = ActiveSupport::Cache::RedisCacheStore.new(redis: configuration.redis)
else
require 'firebase_id_token/certificates/active_support'
end
end

# Method for starting test mode.
Expand Down
Loading