jwt_auth_client 0.1.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 +7 -0
- data/Gemfile +27 -0
- data/Gemfile.lock +109 -0
- data/README.md +116 -0
- data/Rakefile +27 -0
- data/lib/jwt_auth_client/billing_client.rb +13 -0
- data/lib/jwt_auth_client/configuration.rb +48 -0
- data/lib/jwt_auth_client/http_client.rb +63 -0
- data/lib/jwt_auth_client/token_issuer.rb +60 -0
- data/lib/jwt_auth_client/version.rb +3 -0
- data/lib/jwt_auth_client.rb +16 -0
- data/pkg/jwt_auth_client-0.1.0.gem +0 -0
- metadata +153 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a68ca9dd3459f00b139bd71b8a52a5a24112fab256c0aadeebb594f4704159e8
|
|
4
|
+
data.tar.gz: 8a9dfabd1723a404c01fa213fd2589abf8f6f5a4500a7a42f25ce90f5d1938ee
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3014ed4df9591c1a3115380a78c222df936ca0e81c46cb22fa1721308f582e563049eaa6fe9a602505b90daf38a792cd60f0c5ab73883ba140cf4332cedac407
|
|
7
|
+
data.tar.gz: c59efce14020d02c89c77c7ac18fed487f618862e4150f93d63c7e6d41f9bbb9c3dc46724360f756e758d0a2455836c3811753b1640c7fca00410549c99716bd
|
data/Gemfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
gemspec
|
|
3
|
+
group :development, :test do
|
|
4
|
+
gem 'pry', '~> 0.14'
|
|
5
|
+
|
|
6
|
+
# Core HTTP Client
|
|
7
|
+
gem 'faraday', '~> 2.7'
|
|
8
|
+
|
|
9
|
+
# For conn.request :retry (now typically part of the faraday-excon or faraday-net_http gems)
|
|
10
|
+
# gem 'faraday-retry' # You might need this explicitly if on an older version of Faraday 2+
|
|
11
|
+
|
|
12
|
+
gem 'webmock', '~> 3.19' # For HTTP stubbing/testing
|
|
13
|
+
gem 'timecop', '~> 0.9' # For time-based testing
|
|
14
|
+
# Testing tools
|
|
15
|
+
gem 'rspec', '~> 3.0'
|
|
16
|
+
|
|
17
|
+
# Faraday and required middleware
|
|
18
|
+
# The core faraday gem is already included via gemspec
|
|
19
|
+
# We must explicitly add the middleware that replaced faraday-middleware and faraday-json
|
|
20
|
+
gem 'faraday-typhoeus' # A robust adapter
|
|
21
|
+
gem 'faraday-retry', '~> 2.0' # Explicit gem for the :retry middleware
|
|
22
|
+
|
|
23
|
+
# Note: JSON encoding/decoding middleware is often implicitly handled
|
|
24
|
+
# by faraday v2, or included in faraday-typhoeus or a different gem
|
|
25
|
+
# depending on the setup. Explicitly adding :json requests/responses
|
|
26
|
+
# in the HttpClient often just needs the `json` gem itself.
|
|
27
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
jwt_auth_client (0.1.0)
|
|
5
|
+
activesupport (>= 6.0)
|
|
6
|
+
faraday (~> 2.9)
|
|
7
|
+
jwt (~> 2.8)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
activesupport (8.1.0)
|
|
13
|
+
base64
|
|
14
|
+
bigdecimal
|
|
15
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
16
|
+
connection_pool (>= 2.2.5)
|
|
17
|
+
drb
|
|
18
|
+
i18n (>= 1.6, < 2)
|
|
19
|
+
json
|
|
20
|
+
logger (>= 1.4.2)
|
|
21
|
+
minitest (>= 5.1)
|
|
22
|
+
securerandom (>= 0.3)
|
|
23
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
24
|
+
uri (>= 0.13.1)
|
|
25
|
+
addressable (2.8.7)
|
|
26
|
+
public_suffix (>= 2.0.2, < 7.0)
|
|
27
|
+
base64 (0.3.0)
|
|
28
|
+
bigdecimal (3.3.1)
|
|
29
|
+
coderay (1.1.3)
|
|
30
|
+
concurrent-ruby (1.3.5)
|
|
31
|
+
connection_pool (2.5.4)
|
|
32
|
+
crack (1.0.1)
|
|
33
|
+
bigdecimal
|
|
34
|
+
rexml
|
|
35
|
+
diff-lcs (1.6.2)
|
|
36
|
+
drb (2.2.3)
|
|
37
|
+
ethon (0.15.0)
|
|
38
|
+
ffi (>= 1.15.0)
|
|
39
|
+
faraday (2.14.0)
|
|
40
|
+
faraday-net_http (>= 2.0, < 3.5)
|
|
41
|
+
json
|
|
42
|
+
logger
|
|
43
|
+
faraday-net_http (3.4.1)
|
|
44
|
+
net-http (>= 0.5.0)
|
|
45
|
+
faraday-retry (2.3.2)
|
|
46
|
+
faraday (~> 2.0)
|
|
47
|
+
faraday-typhoeus (1.1.0)
|
|
48
|
+
faraday (~> 2.0)
|
|
49
|
+
typhoeus (~> 1.4)
|
|
50
|
+
ffi (1.17.2-x86_64-linux-gnu)
|
|
51
|
+
hashdiff (1.2.1)
|
|
52
|
+
i18n (1.14.7)
|
|
53
|
+
concurrent-ruby (~> 1.0)
|
|
54
|
+
json (2.15.1)
|
|
55
|
+
jwt (2.10.2)
|
|
56
|
+
base64
|
|
57
|
+
logger (1.7.0)
|
|
58
|
+
method_source (1.1.0)
|
|
59
|
+
minitest (5.26.0)
|
|
60
|
+
net-http (0.6.0)
|
|
61
|
+
uri
|
|
62
|
+
pry (0.15.2)
|
|
63
|
+
coderay (~> 1.1)
|
|
64
|
+
method_source (~> 1.0)
|
|
65
|
+
public_suffix (6.0.2)
|
|
66
|
+
rake (13.3.0)
|
|
67
|
+
rexml (3.4.4)
|
|
68
|
+
rspec (3.13.2)
|
|
69
|
+
rspec-core (~> 3.13.0)
|
|
70
|
+
rspec-expectations (~> 3.13.0)
|
|
71
|
+
rspec-mocks (~> 3.13.0)
|
|
72
|
+
rspec-core (3.13.6)
|
|
73
|
+
rspec-support (~> 3.13.0)
|
|
74
|
+
rspec-expectations (3.13.5)
|
|
75
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
76
|
+
rspec-support (~> 3.13.0)
|
|
77
|
+
rspec-mocks (3.13.6)
|
|
78
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
79
|
+
rspec-support (~> 3.13.0)
|
|
80
|
+
rspec-support (3.13.6)
|
|
81
|
+
securerandom (0.4.1)
|
|
82
|
+
timecop (0.9.10)
|
|
83
|
+
typhoeus (1.5.0)
|
|
84
|
+
ethon (>= 0.9.0, < 0.16.0)
|
|
85
|
+
tzinfo (2.0.6)
|
|
86
|
+
concurrent-ruby (~> 1.0)
|
|
87
|
+
uri (1.0.4)
|
|
88
|
+
webmock (3.25.1)
|
|
89
|
+
addressable (>= 2.8.0)
|
|
90
|
+
crack (>= 0.3.2)
|
|
91
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
92
|
+
|
|
93
|
+
PLATFORMS
|
|
94
|
+
x86_64-linux
|
|
95
|
+
|
|
96
|
+
DEPENDENCIES
|
|
97
|
+
bundler (~> 2.0)
|
|
98
|
+
faraday (~> 2.7)
|
|
99
|
+
faraday-retry (~> 2.0)
|
|
100
|
+
faraday-typhoeus
|
|
101
|
+
jwt_auth_client!
|
|
102
|
+
pry (~> 0.14)
|
|
103
|
+
rake (~> 13.0)
|
|
104
|
+
rspec (~> 3.0)
|
|
105
|
+
timecop (~> 0.9)
|
|
106
|
+
webmock (~> 3.19)
|
|
107
|
+
|
|
108
|
+
BUNDLED WITH
|
|
109
|
+
2.4.22
|
data/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
JwtAuthClient
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
A minimal, robust Ruby client for internal service-to-service authentication using JSON Web Tokens (JWTs).
|
|
5
|
+
|
|
6
|
+
This gem simplifies the process of creating, signing, and injecting short-lived JWTs into outgoing HTTP requests, ensuring secure communication between your internal microservices.
|
|
7
|
+
|
|
8
|
+
Features
|
|
9
|
+
--------
|
|
10
|
+
|
|
11
|
+
* **Token Generation:** Creates industry-standard JWTs with required claims (`iss`, `sub`, `iat`, `exp`, `jti`).
|
|
12
|
+
|
|
13
|
+
* **Service-Specific Scopes:** Allows injection of custom claims (`aud`, `scopes`) to authorize access for specific target services.
|
|
14
|
+
|
|
15
|
+
* **Faraday Integration:** Wraps around Faraday to automatically attach the generated JWT as a `Bearer` token in the `Authorization` header.
|
|
16
|
+
|
|
17
|
+
* **High-Level Clients:** Supports creating specialized clients (e.g., `BillingClient`) for clean API consumption.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Installation
|
|
21
|
+
------------
|
|
22
|
+
|
|
23
|
+
Add this line to your application's Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'jwt_auth_client', '~> 0.1.0'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
And then execute:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
$ bundle install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Configuration
|
|
36
|
+
-------------
|
|
37
|
+
|
|
38
|
+
You must configure the client once in your application's initialization file (e.g., `config/initializers/jwt_auth_client.rb` in a Rails app).
|
|
39
|
+
|
|
40
|
+
Setting | Type | Description |
|
|
41
|
+
| - | - | - |
|
|
42
|
+
| **shared_secret** | String | The cryptographic key known to all services (used for signing and verification). **CRITICAL.**
|
|
43
|
+
| **algorithm** | String | The JWT signing algorithm (e.g., '`HS256`').
|
|
44
|
+
| **default_expiry_seconds** | Integer | Default lifespan for the tokens (e.g., `300` seconds = 5 minutes).
|
|
45
|
+
| **issuer** | String | The identifier of the application issuing the token (e.g., '`main_sso_app`').
|
|
46
|
+
| **service_urls** | Hash | Map of internal service keys to their base URLs.
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# config/initializers/jwt_auth_client.rb
|
|
50
|
+
|
|
51
|
+
JwtAuthClient.configure do |config|
|
|
52
|
+
# Load the secret from an environment variable!
|
|
53
|
+
config.shared_secret = ENV.fetch('JWT_SERVICE_SECRET') { 'a_fallback_secret_for_dev' }
|
|
54
|
+
config.algorithm = 'HS256'
|
|
55
|
+
config.default_expiry_seconds = 300 # 5 minutes
|
|
56
|
+
config.issuer = 'main_app_sso'
|
|
57
|
+
# Configure base URLs for your internal services
|
|
58
|
+
config.service_urls = {
|
|
59
|
+
billing_api: 'http://billing-service.internal',
|
|
60
|
+
user_data_api: 'http://user-service.internal'
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Usage
|
|
66
|
+
-----
|
|
67
|
+
|
|
68
|
+
### 1\. High-Level Service Client (Recommended)
|
|
69
|
+
|
|
70
|
+
Use the built-in or custom client wrappers for clean dependency management. These clients automatically use the configured `target_service`.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
# The BillingClient is a specialized wrapper around HttpClient
|
|
74
|
+
# it defaults target_service to :billing_api
|
|
75
|
+
client = JwtAuthClient::BillingClient.call(
|
|
76
|
+
user_id: 'user-id-456',
|
|
77
|
+
scopes: ['read:invoices', 'write:payments']
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# client is a Faraday connection object
|
|
81
|
+
response = client.get('/v1/invoices/latest')
|
|
82
|
+
if response.success?
|
|
83
|
+
puts "Invoices: #{response.body}"
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2\. General HTTP Client (Advanced)
|
|
88
|
+
|
|
89
|
+
If you need dynamic control over the target service, you can use the base `HttpClient`.
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
client = JwtAuthClient::HttpClient.call(
|
|
93
|
+
user_id: 'service-account-etl',
|
|
94
|
+
target_service: :user_data_api, # Must be a key defined in service_urls
|
|
95
|
+
scopes: ['read:all_users']
|
|
96
|
+
)
|
|
97
|
+
# Override the base URL dynamically if needed
|
|
98
|
+
override_client = JwtAuthClient::HttpClient.call(
|
|
99
|
+
user_id: 'guest',
|
|
100
|
+
base_url: 'http://temporary-api.test' # Overrides configured service_urls
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3\. Token Generation Only
|
|
105
|
+
|
|
106
|
+
If you only need the raw JWT string for non-HTTP purposes (e.g., message queues), use the `TokenIssuer`.
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
token = JwtAuthClient::TokenIssuer.call(
|
|
110
|
+
user_id: 'system-job-id',
|
|
111
|
+
target_service: 'data_pipeline',
|
|
112
|
+
scopes: ['process:orders']
|
|
113
|
+
)
|
|
114
|
+
# token will be the signed JWT string
|
|
115
|
+
# puts token
|
|
116
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'rspec/core/rake_task'
|
|
4
|
+
require 'rubygems/package_task'
|
|
5
|
+
require "bundler/gem_tasks"
|
|
6
|
+
|
|
7
|
+
# Load the gemspec to get gem metadata
|
|
8
|
+
spec = Gem::Specification.load("jwt_auth_client.gemspec")
|
|
9
|
+
|
|
10
|
+
# Task to run all RSpec tests
|
|
11
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
12
|
+
# Use RSpec's command-line options
|
|
13
|
+
t.rspec_opts = "--color --format documentation"
|
|
14
|
+
# Specify the files to run
|
|
15
|
+
t.pattern = 'spec/**/*_spec.rb'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Define 'test' as an alias for 'spec'
|
|
19
|
+
task :test => :spec
|
|
20
|
+
|
|
21
|
+
# Define a task for building the gem (creates the .gem file)
|
|
22
|
+
Gem::PackageTask.new(spec) do |pkg|
|
|
23
|
+
# Optional: Customize the package task if necessary
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Default task is usually to run tests
|
|
27
|
+
task :default => :spec
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require_relative 'http_client'
|
|
2
|
+
|
|
3
|
+
module JwtAuthClient
|
|
4
|
+
# A high-level, service-specific client that inherits from HttpClient
|
|
5
|
+
# and automatically sets the target_service to :billing_api.
|
|
6
|
+
class BillingClient < HttpClient
|
|
7
|
+
# The public entry point, mirroring the service object pattern,
|
|
8
|
+
# but hardcoding the target_service.
|
|
9
|
+
def self.call(user_id:, scopes: [], base_url: nil)
|
|
10
|
+
super(user_id: user_id, target_service: :billing_api, scopes: scopes, base_url: base_url)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module JwtAuthClient
|
|
2
|
+
class Configuration
|
|
3
|
+
# The cryptographic key known to all internal services
|
|
4
|
+
attr_accessor :shared_secret
|
|
5
|
+
|
|
6
|
+
# The algorithm used for signing (e.g., 'HS256')
|
|
7
|
+
attr_accessor :algorithm
|
|
8
|
+
|
|
9
|
+
# Default token validity period in seconds (e.g., 300 seconds = 5 minutes)
|
|
10
|
+
attr_accessor :default_expiry_seconds
|
|
11
|
+
|
|
12
|
+
# The issuer of the token (conventionally the main application ID)
|
|
13
|
+
attr_accessor :issuer
|
|
14
|
+
|
|
15
|
+
# The mapping of target service names (Symbol) to their base URLs (String)
|
|
16
|
+
attr_accessor :service_urls
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@algorithm = 'HS256'
|
|
20
|
+
@default_expiry_seconds = 300
|
|
21
|
+
@issuer = 'main_sso_app'
|
|
22
|
+
# Added a default value for local testing if ENV variable is missing
|
|
23
|
+
@shared_secret = ENV['JWT_SERVICE_SECRET'] || 'development_secret'
|
|
24
|
+
@service_urls = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Retrieves the base URL for a given target service.
|
|
28
|
+
# The HttpClient relies on this method to determine where to send the request.
|
|
29
|
+
#
|
|
30
|
+
# @param target_service [Symbol, String] The name of the service (e.g., :billing_api).
|
|
31
|
+
# @return [String] The base URL.
|
|
32
|
+
# @raise [ArgumentError] If the service is not configured.
|
|
33
|
+
def base_url_for(target_service)
|
|
34
|
+
url = service_urls[target_service.to_sym]
|
|
35
|
+
raise ArgumentError, "Base URL for service '#{target_service}' is not configured in JwtAuthClient.service_urls." unless url
|
|
36
|
+
url
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Class method to expose the configuration object and the configuration block
|
|
41
|
+
def self.configuration
|
|
42
|
+
@configuration ||= Configuration.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.configure
|
|
46
|
+
yield(configuration)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
|
|
3
|
+
module JwtAuthClient
|
|
4
|
+
# HttpClient is a thin wrapper around Faraday that automatically issues a JWT
|
|
5
|
+
# for the given user/scopes and injects it into the Authorization header
|
|
6
|
+
# for secure inter-service communication.
|
|
7
|
+
class HttpClient
|
|
8
|
+
# The public entry point for making requests.
|
|
9
|
+
#
|
|
10
|
+
# @param user_id [String] The ID of the user to impersonate for this request.
|
|
11
|
+
# @param target_service [String] The specific backend service the token is intended for (used for 'aud' claim).
|
|
12
|
+
# @param scopes [Array<String>] The permissions required for the request.
|
|
13
|
+
# @param base_url [String] The base URL of the service to call (overrides global config if provided).
|
|
14
|
+
# @return [Faraday::Response] The response object from the HTTP request.
|
|
15
|
+
def self.call(user_id:, target_service:, scopes: [], base_url: nil)
|
|
16
|
+
new(user_id, target_service, scopes, base_url).connection
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :user_id, :target_service, :scopes, :base_url
|
|
20
|
+
|
|
21
|
+
# Initializes the client instance.
|
|
22
|
+
def initialize(user_id, target_service, scopes, base_url = nil)
|
|
23
|
+
@user_id = user_id
|
|
24
|
+
@target_service = target_service
|
|
25
|
+
@scopes = scopes
|
|
26
|
+
@base_url = base_url
|
|
27
|
+
@config = JwtAuthClient.configuration
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Builds and memoizes the Faraday connection object.
|
|
31
|
+
# The JWT is issued and included in a request header during this connection setup.
|
|
32
|
+
#
|
|
33
|
+
# @return [Faraday::Connection] The pre-configured Faraday connection.
|
|
34
|
+
def connection
|
|
35
|
+
@connection ||= Faraday.new(url: determined_base_url) do |conn|
|
|
36
|
+
# Inject the Authorization header with the JWT before every request
|
|
37
|
+
conn.request :authorization, 'Bearer', jwt_token
|
|
38
|
+
|
|
39
|
+
# Other standard middleware
|
|
40
|
+
conn.request :json
|
|
41
|
+
conn.response :json, content_type: /\bjson$/
|
|
42
|
+
conn.response :raise_error # Raise exceptions on 4xx/5xx responses
|
|
43
|
+
conn.adapter Faraday.default_adapter
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# Determines the base URL, preferring the local override if provided.
|
|
50
|
+
def determined_base_url
|
|
51
|
+
base_url || @config.base_url_for(target_service)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Uses the TokenIssuer service to generate the authenticated token.
|
|
55
|
+
def jwt_token
|
|
56
|
+
@jwt_token ||= TokenIssuer.call(
|
|
57
|
+
user_id: user_id,
|
|
58
|
+
target_service: target_service,
|
|
59
|
+
scopes: scopes
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'jwt'
|
|
2
|
+
require 'securerandom'
|
|
3
|
+
|
|
4
|
+
module JwtAuthClient
|
|
5
|
+
class TokenIssuer
|
|
6
|
+
# This is the ONLY public class method API. It initializes and calls the public instance method.
|
|
7
|
+
def self.call(user_id:, target_service: nil, scopes: [])
|
|
8
|
+
new(user_id, target_service, scopes).issue
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# initialize is public by default.
|
|
12
|
+
def initialize(user_id, target_service, scopes)
|
|
13
|
+
@user_id = user_id
|
|
14
|
+
@target_service = target_service
|
|
15
|
+
@scopes = scopes
|
|
16
|
+
@config = JwtAuthClient.configuration
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# --- Public Instance Method (Called by self.call) ---
|
|
20
|
+
def issue
|
|
21
|
+
encode
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# --- Private Helper Methods ---
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def encode
|
|
28
|
+
payload = build_payload
|
|
29
|
+
|
|
30
|
+
# Use the JWT gem to encode the token
|
|
31
|
+
JWT.encode(payload, @config.shared_secret, @config.algorithm)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Uses conditional merging to ensure keys for optional claims (aud, scopes)
|
|
35
|
+
# are only included if they have a non-nil/non-empty value.
|
|
36
|
+
def build_payload
|
|
37
|
+
# Standard JWT Claims (Must include these for security/verification)
|
|
38
|
+
issued_at = Time.now.to_i
|
|
39
|
+
expiry = issued_at + @config.default_expiry_seconds
|
|
40
|
+
|
|
41
|
+
# 1. Start with Required Claims
|
|
42
|
+
payload = {
|
|
43
|
+
iss: @config.issuer, # Issuer (e.g., main_sso_app)
|
|
44
|
+
sub: @user_id, # Subject (The user being impersonated)
|
|
45
|
+
iat: issued_at, # Issued At Time
|
|
46
|
+
exp: expiry, # Expiration Time (CRITICAL: short-lived)
|
|
47
|
+
jti: SecureRandom.uuid, # JWT ID (For optional replay attack prevention)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# 2. Conditionally merge Optional Claims
|
|
51
|
+
# Audience is nil by default
|
|
52
|
+
payload[:aud] = @target_service if @target_service
|
|
53
|
+
|
|
54
|
+
# Scopes are [] by default, so check if the array is not empty
|
|
55
|
+
payload[:scopes] = @scopes unless @scopes.empty?
|
|
56
|
+
|
|
57
|
+
payload
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'jwt' # Core dependency
|
|
2
|
+
require 'active_support/core_ext/numeric/time' # For N.minutes.from_now logic
|
|
3
|
+
require 'securerandom' # Added for SecureRandom.uuid dependency
|
|
4
|
+
require 'jwt_auth_client/version'
|
|
5
|
+
require 'jwt_auth_client/configuration'
|
|
6
|
+
require 'jwt_auth_client/token_issuer'
|
|
7
|
+
require 'jwt_auth_client/http_client'
|
|
8
|
+
require 'jwt_auth_client/billing_client'
|
|
9
|
+
|
|
10
|
+
module JwtAuthClient
|
|
11
|
+
# Main module definition
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module JwtAuthClient
|
|
15
|
+
# Main module definition
|
|
16
|
+
end
|
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jwt_auth_client
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Daniele Frisanco
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-10-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: jwt
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.8'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.8'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: faraday
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.9'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.9'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: activesupport
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '6.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '6.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: bundler
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '2.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rake
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '13.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '13.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rspec
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '3.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '3.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: pry
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
description: Generates short-lived, signed JWTs for internal API calls authenticated
|
|
112
|
+
via a shared secret.
|
|
113
|
+
email:
|
|
114
|
+
- daniele.frisanco@gmail.com
|
|
115
|
+
executables: []
|
|
116
|
+
extensions: []
|
|
117
|
+
extra_rdoc_files: []
|
|
118
|
+
files:
|
|
119
|
+
- Gemfile
|
|
120
|
+
- Gemfile.lock
|
|
121
|
+
- README.md
|
|
122
|
+
- Rakefile
|
|
123
|
+
- lib/jwt_auth_client.rb
|
|
124
|
+
- lib/jwt_auth_client/billing_client.rb
|
|
125
|
+
- lib/jwt_auth_client/configuration.rb
|
|
126
|
+
- lib/jwt_auth_client/http_client.rb
|
|
127
|
+
- lib/jwt_auth_client/token_issuer.rb
|
|
128
|
+
- lib/jwt_auth_client/version.rb
|
|
129
|
+
- pkg/jwt_auth_client-0.1.0.gem
|
|
130
|
+
homepage: https://github.com/danielefrisanco/jwt_auth_client
|
|
131
|
+
licenses:
|
|
132
|
+
- MIT
|
|
133
|
+
metadata: {}
|
|
134
|
+
post_install_message:
|
|
135
|
+
rdoc_options: []
|
|
136
|
+
require_paths:
|
|
137
|
+
- lib
|
|
138
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - ">="
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: '0'
|
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
144
|
+
requirements:
|
|
145
|
+
- - ">="
|
|
146
|
+
- !ruby/object:Gem::Version
|
|
147
|
+
version: '0'
|
|
148
|
+
requirements: []
|
|
149
|
+
rubygems_version: 3.4.10
|
|
150
|
+
signing_key:
|
|
151
|
+
specification_version: 4
|
|
152
|
+
summary: Secure client for generating and sending internal service-to-service JWTs.
|
|
153
|
+
test_files: []
|