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 +4 -4
- data/README.md +10 -4
- data/keratin-authn.gemspec +1 -1
- data/lib/keratin/authn.rb +22 -26
- data/lib/keratin/authn/{issuer.rb → api.rb} +5 -17
- data/lib/keratin/authn/fetching_keychain.rb +25 -0
- data/lib/keratin/authn/id_token_verifier.rb +5 -3
- data/lib/keratin/authn/{mock_signature_verifier.rb → mock_keychain.rb} +2 -2
- data/lib/keratin/authn/version.rb +1 -1
- metadata +9 -9
- data/lib/keratin/authn/remote_signature_verifier.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 675c925f05dfabe4bd0f21df787374d84656bb54
|
4
|
+
data.tar.gz: 6707f4d2b665485192152bf7a59c5a1d62800e7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
141
|
-
#
|
142
|
-
Keratin::AuthN.signature_verifier = Keratin::AuthN::
|
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.
|
data/keratin-authn.gemspec
CHANGED
@@ -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'
|
data/lib/keratin/authn.rb
CHANGED
@@ -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/
|
5
|
-
require_relative 'authn/
|
6
|
-
require_relative 'authn/
|
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::
|
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
|
55
|
-
#
|
56
|
-
|
57
|
-
|
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
|
64
|
-
#
|
65
|
-
def self.
|
66
|
-
unless val.respond_to?(:
|
67
|
-
raise ArgumentError, 'Please ensure that your
|
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
|
-
@
|
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,
|
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
|
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,
|
5
|
+
def initialize(str, keychain, audience)
|
6
6
|
@id_token = str
|
7
|
-
@
|
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
|
-
|
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
|
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.
|
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-
|
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:
|
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:
|
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/
|
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
|