keratin-authn 0.6.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c8460a42cb6b34ecd0115366206cbda19e4e474
4
- data.tar.gz: 26ccf34ff11d3da767e8e78fea9df8f883f92442
3
+ metadata.gz: 675c925f05dfabe4bd0f21df787374d84656bb54
4
+ data.tar.gz: 6707f4d2b665485192152bf7a59c5a1d62800e7d
5
5
  SHA512:
6
- metadata.gz: b1dc1e52c700b90568b3c9c6548f703c7f1c817d9667a2ebb6fea983169de7f5c2ddff3b329e07999044b426c9023e61ebbd7f5036179a28829ed6bfc49a1000
7
- data.tar.gz: 3efd27f4e4a586f9f978fb735ac7ab5a8e8b7277bd8ac9ad6d55e6125c2ac3f8f5e35b35ff018c9727fffe6e0f780e0310feb35b4bd2228363125e47ac982a98
6
+ metadata.gz: 9c539082ff017a26237d848342769115ca0ad978ec470505e516fcb91879499fc7bb27d0f43601a434b480e70acfbb55ec2c124686e6b8bc3319e2d38a197d13
7
+ data.tar.gz: d41c0567fd3f85b164c82a8c0bb5237d4f0e0e9865fb3a0bfe6d2a5e6996381b55cb1c3658713be50525cdafce31480e34d1d73a4d0523a1530104f207ad8129
data/README.md CHANGED
@@ -23,12 +23,18 @@ Keratin::AuthN.config.tap do |config|
23
23
  # The base URL of your Keratin AuthN service
24
24
  config.issuer = 'https://authn.myapp.com'
25
25
 
26
- # The domain of your application
26
+ # The domain of your application (no protocol)
27
27
  config.audience = 'myapp.com'
28
28
 
29
29
  # HTTP basic auth for using AuthN's private endpoints
30
30
  config.username = 'secret'
31
31
  config.password = 'secret'
32
+
33
+ # OPTIONAL: enables debugging for the JWT verification process
34
+ config.logger = Rails.logger
35
+
36
+ # OPTIONAL: allows private API calls to use private network routing
37
+ config.authn_url = 'https://authn.internal.dns/
32
38
  end
33
39
  ```
34
40
 
@@ -137,9 +143,9 @@ AuthN provides helpers for working with tokens in your application's controller
137
143
  In your `test/test_helper.rb` or equivalent:
138
144
 
139
145
  ```ruby
140
- # Configuring AuthN to use the MockSignatureVerifier will stop your tests from attempting to connect
141
- # to the remote issuer during tests.
142
- Keratin::AuthN.signature_verifier = Keratin::AuthN::MockSignatureVerifier.new
146
+ # Configuring AuthN to use the MockKeychain will stop your tests from attempting to connect to the
147
+ # remote issuer during tests.
148
+ Keratin::AuthN.signature_verifier = Keratin::AuthN::MockKeychain.new
143
149
 
144
150
  # Including the Test::Helpers module grants access to `id_token_for(user.account_id)`, so that you
145
151
  # can test your system with real tokens.
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ['lib']
31
31
 
32
- spec.add_dependency 'json-jwt'
32
+ spec.add_dependency 'json-jwt', '!= 1.8.0'
33
33
  spec.add_dependency 'lru_redux'
34
34
 
35
35
  spec.add_development_dependency 'bundler', '~> 1.13'
@@ -1,17 +1,17 @@
1
1
  require_relative 'authn/version'
2
2
  require_relative 'authn/engine' if defined?(Rails)
3
3
  require_relative 'authn/id_token_verifier'
4
- require_relative 'authn/remote_signature_verifier'
5
- require_relative 'authn/mock_signature_verifier'
6
- require_relative 'authn/issuer'
4
+ require_relative 'authn/fetching_keychain'
5
+ require_relative 'authn/mock_keychain'
6
+ require_relative 'authn/api'
7
7
 
8
8
  require 'lru_redux'
9
9
  require 'json/jwt'
10
10
 
11
11
  module Keratin
12
12
  def self.authn
13
- @authn ||= AuthN::Issuer.new(
14
- AuthN.config.issuer,
13
+ @authn ||= AuthN::API.new(
14
+ AuthN.config.authn_url || AuthN.config.issuer,
15
15
  username: AuthN.config.username,
16
16
  password: AuthN.config.password
17
17
  )
@@ -19,7 +19,7 @@ module Keratin
19
19
 
20
20
  module AuthN
21
21
  class Config
22
- # the domain (host) of the main application.
22
+ # the domain (host) of the main application. no protocol.
23
23
  # e.g. "audience.tech"
24
24
  attr_accessor :audience
25
25
 
@@ -27,6 +27,11 @@ module Keratin
27
27
  # e.g. "https://issuer.tech"
28
28
  attr_accessor :issuer
29
29
 
30
+ # the base url for API calls. this is useful if you've divided your network so private API
31
+ # requests can not be probed by public devices. it is optional, and will default to issuer.
32
+ # e.g. "https://authn.internal.dns"
33
+ attr_accessor :authn_url
34
+
30
35
  # how long (in seconds) to keep keys in the keychain before refreshing.
31
36
  # default: 3600
32
37
  attr_accessor :keychain_ttl
@@ -51,38 +56,29 @@ module Keratin
51
56
  config.logger.debug{ yield } if config.logger
52
57
  end
53
58
 
54
- # The default strategy for signature verification will find the JWT's issuer, fetch the JWKs
55
- # from that server, choose the correct key by id, and finally verify the JWT. The keys are
56
- # then cached in memory to reduce network traffic.
57
- def self.signature_verifier
58
- @verifier ||= RemoteSignatureVerifier.new(
59
- LruRedux::TTL::ThreadSafeCache.new(25, config.keychain_ttl)
60
- )
59
+ # The default keychain will fetch JWKs from the configured issuer and return the correct key by
60
+ # id. Keys are cached in memory to reduce network traffic.
61
+ def self.keychain
62
+ @keychain ||= FetchingKeychain.new(issuer: config.issuer, ttl: config.keychain_ttl)
61
63
  end
62
64
 
63
- # If the default strategy is not desired (as in host application tests), different strategies
64
- # may be specified here. The strategy must define a `verify(jwt)` method.
65
- def self.signature_verifier=(val)
66
- unless val.respond_to?(:verify) && val.method(:verify).arity == 1
67
- raise ArgumentError, 'Please ensure that your signature verifier has been instantiated and implements `def verify(jwt)`.'
65
+ # If the default keychain is not desired (as in host application tests), different keychain may
66
+ # be specified here. The keychain must define a `[](kid)` method.
67
+ def self.keychain=(val)
68
+ unless val.respond_to?(:[]) && val.method(:[]).arity == 1
69
+ raise ArgumentError, 'Please ensure that your keychain has been instantiated and implements `[](kid)`.'
68
70
  end
69
71
 
70
- @verifier = val
72
+ @keychain = val
71
73
  end
72
74
 
73
75
  class << self
74
76
  # safely fetches a subject from the id token after checking relevant claims and
75
77
  # verifying the signature.
76
78
  def subject_from(id_token, audience: Keratin::AuthN.config.audience)
77
- verifier = IDTokenVerifier.new(id_token, signature_verifier, audience)
79
+ verifier = IDTokenVerifier.new(id_token, keychain, audience)
78
80
  verifier.subject if verifier.verified?
79
81
  end
80
-
81
- def logout_url(return_to: nil)
82
- query = {redirect_uri: return_to}.to_param if return_to
83
-
84
- "#{config.issuer}/sessions/logout#{'?' if query}#{query}"
85
- end
86
82
  end
87
83
  end
88
84
  end
@@ -2,7 +2,11 @@ require 'keratin/client'
2
2
  require 'net/http'
3
3
 
4
4
  module Keratin::AuthN
5
- class Issuer < Keratin::Client
5
+ class API < Keratin::Client
6
+ def get(account_id)
7
+ super(path: "/accounts/#{account_id}")
8
+ end
9
+
6
10
  def update(account_id, username:)
7
11
  patch(path: "/accounts/#{account_id}", body: {
8
12
  username: username
@@ -33,21 +37,5 @@ module Keratin::AuthN
33
37
  def expire_password(account_id)
34
38
  patch(path: "/accounts/#{account_id}/expire_password")
35
39
  end
36
-
37
- def signing_key(kid)
38
- keys.find{|k| k['use'] == 'sig' && (kid.blank? || kid == k['kid']) }
39
- end
40
-
41
- private def configuration
42
- @configuration ||= get(path: '/configuration').data
43
- end
44
-
45
- private def keys
46
- JSON::JWK::Set.new(
47
- JSON.parse(
48
- Net::HTTP.get(URI.parse(configuration['jwks_uri']))
49
- )
50
- )
51
- end
52
40
  end
53
41
  end
@@ -0,0 +1,25 @@
1
+ module Keratin::AuthN
2
+ class FetchingKeychain
3
+ def initialize(issuer:, ttl:)
4
+ @cache = LruRedux::TTL::ThreadSafeCache.new(25, ttl)
5
+ @issuer = issuer.chomp('/')
6
+ end
7
+
8
+ def [](kid)
9
+ @cache.getset(kid){ fetch(kid) }
10
+ end
11
+
12
+ def clear
13
+ @cache.clear
14
+ end
15
+
16
+ private def fetch(kid)
17
+ keys = JSON::JWK::Set.new(
18
+ JSON.parse(
19
+ Net::HTTP.get(URI.parse("#{@issuer}/jwks"))
20
+ )
21
+ )
22
+ keys.find{|k| k['use'] == 'sig' && (kid.blank? || kid == k['kid']) }
23
+ end
24
+ end
25
+ end
@@ -2,9 +2,9 @@ require 'uri'
2
2
 
3
3
  module Keratin::AuthN
4
4
  class IDTokenVerifier
5
- def initialize(str, signature_verifier, audience)
5
+ def initialize(str, keychain, audience)
6
6
  @id_token = str
7
- @signature_verifier = signature_verifier
7
+ @keychain = keychain
8
8
  @audience = audience
9
9
  @time = Time.now.to_i
10
10
  end
@@ -51,7 +51,9 @@ module Keratin::AuthN
51
51
  end
52
52
 
53
53
  def token_intact?
54
- @signature_verifier.verify(jwt)
54
+ jwt.verify!(@keychain[jwt.kid])
55
+ rescue JSON::JWT::VerificationFailed, JSON::JWT::UnexpectedAlgorithm
56
+ false
55
57
  end
56
58
 
57
59
  private def jwt
@@ -1,6 +1,6 @@
1
1
  module Keratin::AuthN
2
- class MockSignatureVerifier
3
- def verify(_)
2
+ class MockKeychain
3
+ def [](kid)
4
4
  true
5
5
  end
6
6
  end
@@ -1,5 +1,5 @@
1
1
  module Keratin # rubocop:disable Style/ClassAndModuleChildren
2
2
  module AuthN
3
- VERSION = '0.6.1'
3
+ VERSION = '1.0.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keratin-authn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lance Ivy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-09-26 00:00:00.000000000 Z
11
+ date: 2017-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json-jwt
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "!="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 1.8.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "!="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 1.8.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: lru_redux
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -155,11 +155,11 @@ files:
155
155
  - bin/setup
156
156
  - keratin-authn.gemspec
157
157
  - lib/keratin/authn.rb
158
+ - lib/keratin/authn/api.rb
158
159
  - lib/keratin/authn/engine.rb
160
+ - lib/keratin/authn/fetching_keychain.rb
159
161
  - lib/keratin/authn/id_token_verifier.rb
160
- - lib/keratin/authn/issuer.rb
161
- - lib/keratin/authn/mock_signature_verifier.rb
162
- - lib/keratin/authn/remote_signature_verifier.rb
162
+ - lib/keratin/authn/mock_keychain.rb
163
163
  - lib/keratin/authn/test/helpers.rb
164
164
  - lib/keratin/authn/testing.rb
165
165
  - lib/keratin/authn/version.rb
@@ -1,19 +0,0 @@
1
- module Keratin::AuthN
2
- class RemoteSignatureVerifier
3
- attr_reader :keychain
4
-
5
- def initialize(keychain)
6
- @keychain = keychain
7
- end
8
-
9
- def verify(jwt)
10
- jwt.verify!(key(jwt['iss'], jwt.kid))
11
- rescue JSON::JWT::VerificationFailed, JSON::JWT::UnexpectedAlgorithm
12
- false
13
- end
14
-
15
- private def key(issuer, kid)
16
- keychain.getset(kid){ Issuer.new(issuer).signing_key(kid) }
17
- end
18
- end
19
- end