firebase_token_auth 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +98 -8
- data/lib/firebase_token_auth/client.rb +3 -3
- data/lib/firebase_token_auth/configuration.rb +1 -2
- data/lib/firebase_token_auth/exceptions.rb +104 -0
- data/lib/firebase_token_auth/public_key_manager.rb +8 -1
- data/lib/firebase_token_auth/validator.rb +7 -7
- data/lib/firebase_token_auth/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dff32ef2301d4eb08f46ac3bd840f4c3e9adf5cd7f37c59c79d53b0dfbccdf1b
|
4
|
+
data.tar.gz: 5102df68ce6e5f516d044c4af295c92030a50fa226fd510cac996e72a36553f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f659ddb2edf477b495a2be519cef6c96b5c083737521c60016a80bfa28264a67b4a8a054d12b1be929d1e0dc56ab033a504451efb36887bbb2770a2be0700707
|
7
|
+
data.tar.gz: 6d15331ff417a6fc5d1c45ebfbb44e830ea95aebf3535b6930bb61f045322fc55c79be42975ae8003c0f264985ba0506d00b981c89a395a16a50f1c2333318fb
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# FirebaseTokenAuth
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
FirebaseTokenAuth is an Firebase Auth Client. It supports below.
|
4
|
+
- verify id_token method
|
5
|
+
- create custom token
|
6
|
+
- fetch user info by uid/email
|
6
7
|
|
7
8
|
## Installation
|
8
9
|
|
@@ -22,17 +23,106 @@ Or install it yourself as:
|
|
22
23
|
|
23
24
|
## Usage
|
24
25
|
|
25
|
-
|
26
|
+
on Rails, config/initializers/firebase_token_auth.rb
|
27
|
+
```ruby
|
28
|
+
FirebaseTokenAuth.configure do |config|
|
29
|
+
## for id_token_verify
|
30
|
+
# firebase web console => project settings => general => project ID
|
31
|
+
config.project_id = "your_project_id" # required
|
32
|
+
|
33
|
+
# firebase web console => project settings => service account => firebase admin sdk => generate new private key
|
34
|
+
# pass string of path to credential file to config.json_key_io
|
35
|
+
config.json_key_io = "#{Rails.root}/path/to/service_account_credentials.json"
|
36
|
+
# Or content of json key file wrapped with StringIO
|
37
|
+
# config.json_key_io = StringIO.new('{ ... }')
|
38
|
+
|
39
|
+
# Or set environment variables
|
40
|
+
# ENV['GOOGLE_ACCOUNT_TYPE'] = 'service_account'
|
41
|
+
# ENV['GOOGLE_CLIENT_ID'] = '000000000000000000000'
|
42
|
+
# ENV['GOOGLE_CLIENT_EMAIL'] = 'xxxx@xxxx.iam.gserviceaccount.com'
|
43
|
+
# ENV['GOOGLE_PRIVATE_KEY'] = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n\'
|
44
|
+
end
|
45
|
+
```
|
46
|
+
for more detail. see [here](https://github.com/googleapis/google-auth-library-ruby#example-service-account).
|
26
47
|
|
27
|
-
|
48
|
+
### token verify
|
49
|
+
```ruby
|
50
|
+
require 'firebase_token_auth'
|
51
|
+
|
52
|
+
FirebaseTokenAuth.configure do |config|
|
53
|
+
config.project_id = 'your_project_id'
|
54
|
+
end
|
55
|
+
|
56
|
+
client = Firebase.build
|
57
|
+
result = client.verify_id_token(id_token)
|
58
|
+
|
59
|
+
puts result.uid
|
60
|
+
# => "hMPHt8RyDpOsHi1oH5XaVirSYyq2"
|
61
|
+
|
62
|
+
puts result.id_token.payload # you can see decoded id_token payload
|
63
|
+
# => {"iss"=>"https://securetoken.google.com/<your_project_id>",
|
64
|
+
# "aud"=>"<your_project_id>",
|
65
|
+
# "auth_time"=>1594494935,
|
66
|
+
# "user_id"=>"hMPHt8RyDpOsHi1oH5XaVirSYyq2",
|
67
|
+
# "sub"=>"hMPHt8RyDpOsHi1oH5XaVirSYyq2",
|
68
|
+
# "iat"=>1594494935,
|
69
|
+
# "exp"=>1594498535,
|
70
|
+
# "email"=>"<your_user_email>",
|
71
|
+
# "email_verified"=>false,
|
72
|
+
# "firebase"=>{"identities"=>{"email"=>["<your_user_email>"]}, "sign_in_provider"=>"custom"}}
|
73
|
+
|
74
|
+
puts result.id_token.header
|
75
|
+
# => {"alg"=>"RS256", "kid"=>"7623e10a045140f1cfd4be0466cf80352b59f81e", "typ"=>"JWT"}
|
76
|
+
```
|
28
77
|
|
29
|
-
|
78
|
+
### custom token create
|
79
|
+
```ruby
|
80
|
+
require 'firebase_token_auth'
|
30
81
|
|
31
|
-
|
82
|
+
FirebaseTokenAuth.configure do |config|
|
83
|
+
config.project_id = 'your_project_id'
|
84
|
+
config.json_key_io = "#{Rails.root}/path/to/service_account_credentials.json"
|
85
|
+
end
|
86
|
+
|
87
|
+
client = FirebaseTokenAuth.new
|
88
|
+
c_token = client.create_custom_token(test_uid)
|
89
|
+
puts c_token
|
90
|
+
# => "eyJhbGciOXXX.eyJpc3MiOiJmaXJlYmFzXXXX.v7y7LoBXXXXX" # dummy
|
91
|
+
```
|
92
|
+
|
93
|
+
### fetch users info from firebase
|
94
|
+
```ruby
|
95
|
+
require 'firebase_token_auth'
|
96
|
+
|
97
|
+
FirebaseTokenAuth.configure do |config|
|
98
|
+
config.project_id = 'your_project_id'
|
99
|
+
config.json_key_io = "#{Rails.root}/path/to/service_account_credentials.json"
|
100
|
+
end
|
101
|
+
|
102
|
+
client = FirebaseTokenAuth.new
|
103
|
+
result = client.user_search_by_email(test_user_email)
|
104
|
+
# result = client.user_search_by_uid(test_uid)
|
105
|
+
puts result
|
106
|
+
# => [{:created_at=>1594132097140,
|
107
|
+
# :custom_auth=>true,
|
108
|
+
# :disabled=>false,
|
109
|
+
# :email=>"<your_user_email>",
|
110
|
+
# :email_verified=>false,
|
111
|
+
# :last_login_at=>1594495792373,
|
112
|
+
# :local_id=>"hMPHt8RyDpOsHi1oH5XaVirSYyq2",
|
113
|
+
# :password_hash=>"REDACTED",
|
114
|
+
# :password_updated_at=>1594132097140,
|
115
|
+
# :provider_user_info=>
|
116
|
+
# [{:email=>"<your_user_email>",
|
117
|
+
# :federated_id=>"<your_user_email>",
|
118
|
+
# :provider_id=>"password",
|
119
|
+
# :raw_id=>"<your_user_email>"}],
|
120
|
+
# :valid_since=>1594132097}]
|
121
|
+
```
|
32
122
|
|
33
123
|
## Contributing
|
34
124
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
125
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/miyataka/firebase_token_auth.
|
36
126
|
|
37
127
|
|
38
128
|
## License
|
@@ -5,6 +5,7 @@ require 'jwt'
|
|
5
5
|
require 'firebase_token_auth/public_key_manager'
|
6
6
|
require 'firebase_token_auth/validator'
|
7
7
|
require 'firebase_token_auth/admin_client'
|
8
|
+
require 'firebase_token_auth/exceptions'
|
8
9
|
|
9
10
|
module FirebaseTokenAuth
|
10
11
|
ALGORITHM = 'RS256'.freeze
|
@@ -25,7 +26,7 @@ module FirebaseTokenAuth
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def verify_id_token(id_token, options = {})
|
28
|
-
raise if id_token.nil? || id_token.empty?
|
29
|
+
raise ArgumentError, 'Firebase ID token must not null or blank strings.' if id_token.nil? || id_token.empty?
|
29
30
|
|
30
31
|
public_key_id, decoded_jwt = validator.extract_kid(id_token)
|
31
32
|
public_key_manager.refresh_publickeys!
|
@@ -36,8 +37,7 @@ module FirebaseTokenAuth
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def create_custom_token(uid, additional_claims = nil)
|
39
|
-
|
40
|
-
raise unless configuration.configured_for_custom_token?
|
40
|
+
raise ConfigurationError, 'To create custom token, You must configure credentials via json or environmental variables.' unless configuration.configured_for_custom_token?
|
41
41
|
|
42
42
|
now_seconds = Time.now.to_i
|
43
43
|
payload = { iss: configuration.client_email,
|
@@ -24,8 +24,7 @@ module FirebaseTokenAuth
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def prepare
|
27
|
-
|
28
|
-
raise unless project_id
|
27
|
+
raise ConfigurationError, 'project_id is required to use firebase_token_auth gem.' unless project_id
|
29
28
|
return unless configured_for_custom_token?
|
30
29
|
|
31
30
|
@auth = if json_key_io
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module FirebaseTokenAuth
|
2
|
+
class APIError < StandardError; end
|
3
|
+
class NetworkError < APIError; end
|
4
|
+
|
5
|
+
class HttpError < APIError
|
6
|
+
attr_reader :response
|
7
|
+
|
8
|
+
def initialize(message, response)
|
9
|
+
super(message)
|
10
|
+
@response = response
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ClientError < HttpError; end
|
15
|
+
|
16
|
+
class BadRequest < ClientError; end # status: 400
|
17
|
+
class Unauthorized < ClientError; end # status: 401
|
18
|
+
class PaymentRequired < ClientError; end # status: 402
|
19
|
+
class Forbidden < ClientError; end # status: 403
|
20
|
+
class NotFound < ClientError; end # status: 404
|
21
|
+
class MethodNotAllowed < ClientError; end # status: 405
|
22
|
+
class NotAcceptable < ClientError; end # status: 406
|
23
|
+
class ProxyAuthenticationRequired < ClientError; end # status: 407
|
24
|
+
class RequestTimeout < ClientError; end # status: 408
|
25
|
+
class Conflict < ClientError; end # status: 409
|
26
|
+
class Gone < ClientError; end # status: 410
|
27
|
+
class LengthRequired < ClientError; end # status: 411
|
28
|
+
class PreconditionFailed < ClientError; end # status: 412
|
29
|
+
class PayloadTooLarge < ClientError; end # status: 413
|
30
|
+
class URITooLong < ClientError; end # status: 414
|
31
|
+
class UnsupportedMediaType < ClientError; end # status: 415
|
32
|
+
class RangeNotSatisfiable < ClientError; end # status: 416
|
33
|
+
class ExpectationFailed < ClientError; end # status: 417
|
34
|
+
class ImaTeapot < ClientError; end # status: 418
|
35
|
+
class MisdirectedRequest < ClientError; end # status: 421
|
36
|
+
class UnprocessableEntity < ClientError; end # status: 422
|
37
|
+
class Locked < ClientError; end # status: 423
|
38
|
+
class FailedDependency < ClientError; end # status: 424
|
39
|
+
class UpgradeRequired < ClientError; end # status: 426
|
40
|
+
class PreconditionRequired < ClientError; end # status: 428
|
41
|
+
class TooManyRequests < ClientError; end # status: 429
|
42
|
+
class RequestHeaderFieldsTooLarge < ClientError; end # status: 431
|
43
|
+
class UnavailableForLegalReasons < ClientError; end # status: 451
|
44
|
+
|
45
|
+
class ServerError < HttpError; end
|
46
|
+
|
47
|
+
class InternalServerError < ServerError; end # status: 500
|
48
|
+
class NotImplemented < ServerError; end # status: 501
|
49
|
+
class BadGateway < ServerError; end # status: 502
|
50
|
+
class ServiceUnavailable < ServerError; end # status: 503
|
51
|
+
class GatewayTimeout < ServerError; end # status: 504
|
52
|
+
class HTTPVersionNotSupported < ServerError; end # status: 505
|
53
|
+
class VariantAlsoNegotiates < ServerError; end # status: 506
|
54
|
+
class InsufficientStorage < ServerError; end # status: 507
|
55
|
+
class LoopDetected < ServerError; end # status: 508
|
56
|
+
class NotExtended < ServerError; end # status: 510
|
57
|
+
class NetworkAuthenticationRequired < ServerError; end # status: 511
|
58
|
+
|
59
|
+
STATUS_TO_EXCEPTION_MAPPING = {
|
60
|
+
'400' => BadRequest,
|
61
|
+
'401' => Unauthorized,
|
62
|
+
'402' => PaymentRequired,
|
63
|
+
'403' => Forbidden,
|
64
|
+
'404' => NotFound,
|
65
|
+
'405' => MethodNotAllowed,
|
66
|
+
'406' => NotAcceptable,
|
67
|
+
'407' => ProxyAuthenticationRequired,
|
68
|
+
'408' => RequestTimeout,
|
69
|
+
'409' => Conflict,
|
70
|
+
'410' => Gone,
|
71
|
+
'411' => LengthRequired,
|
72
|
+
'412' => PreconditionFailed,
|
73
|
+
'413' => PayloadTooLarge,
|
74
|
+
'414' => URITooLong,
|
75
|
+
'415' => UnsupportedMediaType,
|
76
|
+
'416' => RangeNotSatisfiable,
|
77
|
+
'417' => ExpectationFailed,
|
78
|
+
'418' => ImaTeapot,
|
79
|
+
'421' => MisdirectedRequest,
|
80
|
+
'422' => UnprocessableEntity,
|
81
|
+
'423' => Locked,
|
82
|
+
'424' => FailedDependency,
|
83
|
+
'426' => UpgradeRequired,
|
84
|
+
'428' => PreconditionRequired,
|
85
|
+
'429' => TooManyRequests,
|
86
|
+
'431' => RequestHeaderFieldsTooLarge,
|
87
|
+
'451' => UnavailableForLegalReasons,
|
88
|
+
'500' => InternalServerError,
|
89
|
+
'501' => NotImplemented,
|
90
|
+
'502' => BadGateway,
|
91
|
+
'503' => ServiceUnavailable,
|
92
|
+
'504' => GatewayTimeout,
|
93
|
+
'505' => HTTPVersionNotSupported,
|
94
|
+
'506' => VariantAlsoNegotiates,
|
95
|
+
'507' => InsufficientStorage,
|
96
|
+
'508' => LoopDetected,
|
97
|
+
'510' => NotExtended,
|
98
|
+
'511' => NetworkAuthenticationRequired
|
99
|
+
}.freeze
|
100
|
+
|
101
|
+
class ValidationError < StandardError; end
|
102
|
+
class ConfigurationError < StandardError; end
|
103
|
+
class ArgumentError < StandardError; end
|
104
|
+
end
|
@@ -19,7 +19,7 @@ module FirebaseTokenAuth
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def fetch_publickeys_hash
|
22
|
-
res = Net::HTTP.get_response(URI(PUBLIC_KEY_URL))
|
22
|
+
res = exception_handler(Net::HTTP.get_response(URI(PUBLIC_KEY_URL)))
|
23
23
|
@public_keys = JSON.parse(res.body).transform_values! { |v| OpenSSL::X509::Certificate.new(v) }
|
24
24
|
@expire_time = cache_control_header_to_expire_time(res['Cache-Control'])
|
25
25
|
end
|
@@ -31,5 +31,12 @@ module FirebaseTokenAuth
|
|
31
31
|
def cache_control_header_to_expire_time(cache_control_header)
|
32
32
|
Time.now.to_i + cache_control_header.match(/max-age=([0-9]*)/)[1].to_i
|
33
33
|
end
|
34
|
+
|
35
|
+
def exception_handler(response)
|
36
|
+
error = STATUS_TO_EXCEPTION_MAPPING[response.code]
|
37
|
+
raise error.new("Receieved an error response #{response.code} #{error.to_s.split('::').last}: #{response.body}", response) if error
|
38
|
+
|
39
|
+
response
|
40
|
+
end
|
34
41
|
end
|
35
42
|
end
|
@@ -7,13 +7,13 @@ module FirebaseTokenAuth
|
|
7
7
|
payload = decoded_jwt[0]
|
8
8
|
header = decoded_jwt[1]
|
9
9
|
issuer = ISSUER_BASE_URL + project_id
|
10
|
-
raise unless header['kid']
|
11
|
-
raise unless header['alg'] == ALGORITHM
|
12
|
-
raise unless payload['aud'] == project_id
|
13
|
-
raise unless payload['iss'] == issuer
|
14
|
-
raise unless payload['sub'].is_a?(String)
|
15
|
-
raise if payload['sub'].empty?
|
16
|
-
raise if payload['sub'].size > 128
|
10
|
+
raise ValidationError, 'Firebase ID token has no "kid" claim.' unless header['kid']
|
11
|
+
raise ValidationError, "Firebase ID token has incorrect algorithm. Expected \"#{ALGORITHM}\" but got \"#{header['alg']}\"." unless header['alg'] == ALGORITHM
|
12
|
+
raise ValidationError, "Firebase ID token has incorrect \"aud\" (audience) claim. Expected \"#{project_id}\" but got \"#{payload['aud']}\"." unless payload['aud'] == project_id
|
13
|
+
raise ValidationError, "Firebase ID token has \"iss\" (issuer) claim. Expected \"#{issuer}\" but got \"#{payload['iss']}\"." unless payload['iss'] == issuer
|
14
|
+
raise ValidationError, 'Firebase ID token has no "sub" (subject) claim.' unless payload['sub'].is_a?(String)
|
15
|
+
raise ValidationError, 'Firebase ID token has an empty string "sub" (subject) claim.' if payload['sub'].empty?
|
16
|
+
raise ValidationError, 'Firebase ID token has "sub" (subject) claim longer than 128 characters.' if payload['sub'].size > 128
|
17
17
|
end
|
18
18
|
|
19
19
|
def extract_kid(id_token)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: firebase_token_auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- miyataka
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: google-api-client
|
@@ -61,6 +61,7 @@ files:
|
|
61
61
|
- lib/firebase_token_auth/admin_client.rb
|
62
62
|
- lib/firebase_token_auth/client.rb
|
63
63
|
- lib/firebase_token_auth/configuration.rb
|
64
|
+
- lib/firebase_token_auth/exceptions.rb
|
64
65
|
- lib/firebase_token_auth/public_key_manager.rb
|
65
66
|
- lib/firebase_token_auth/validator.rb
|
66
67
|
- lib/firebase_token_auth/version.rb
|
@@ -85,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
85
86
|
- !ruby/object:Gem::Version
|
86
87
|
version: '0'
|
87
88
|
requirements: []
|
88
|
-
rubygems_version: 3.
|
89
|
+
rubygems_version: 3.0.3
|
89
90
|
signing_key:
|
90
91
|
specification_version: 4
|
91
92
|
summary: Firebase Authentication API wrapper for serverside. It support custom token
|