omniauth_openid_federation 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 +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.md +22 -0
- data/README.md +822 -0
- data/SECURITY.md +129 -0
- data/examples/README_INTEGRATION_TESTING.md +399 -0
- data/examples/README_MOCK_OP.md +243 -0
- data/examples/app/controllers/users/omniauth_callbacks_controller.rb.example +33 -0
- data/examples/app/jobs/jwks_rotation_job.rb.example +60 -0
- data/examples/app/models/user.rb.example +39 -0
- data/examples/config/initializers/devise.rb.example +97 -0
- data/examples/config/initializers/federation_endpoint.rb.example +206 -0
- data/examples/config/mock_op.yml.example +83 -0
- data/examples/config/open_id_connect_config.rb.example +210 -0
- data/examples/config/routes.rb.example +12 -0
- data/examples/db/migrate/add_omniauth_to_users.rb.example +16 -0
- data/examples/integration_test_flow.rb +1334 -0
- data/examples/jobs/README.md +194 -0
- data/examples/jobs/federation_cache_refresh_job.rb.example +78 -0
- data/examples/jobs/federation_files_generation_job.rb.example +87 -0
- data/examples/mock_op_server.rb +775 -0
- data/examples/mock_rp_server.rb +435 -0
- data/lib/omniauth_openid_federation/access_token.rb +504 -0
- data/lib/omniauth_openid_federation/cache.rb +39 -0
- data/lib/omniauth_openid_federation/cache_adapter.rb +173 -0
- data/lib/omniauth_openid_federation/configuration.rb +135 -0
- data/lib/omniauth_openid_federation/constants.rb +13 -0
- data/lib/omniauth_openid_federation/endpoint_resolver.rb +168 -0
- data/lib/omniauth_openid_federation/entity_statement_reader.rb +122 -0
- data/lib/omniauth_openid_federation/errors.rb +52 -0
- data/lib/omniauth_openid_federation/federation/entity_statement.rb +331 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_builder.rb +188 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_fetcher.rb +142 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_helper.rb +87 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_parser.rb +198 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_validator.rb +502 -0
- data/lib/omniauth_openid_federation/federation/metadata_policy_merger.rb +276 -0
- data/lib/omniauth_openid_federation/federation/signed_jwks.rb +210 -0
- data/lib/omniauth_openid_federation/federation/trust_chain_resolver.rb +225 -0
- data/lib/omniauth_openid_federation/federation_endpoint.rb +949 -0
- data/lib/omniauth_openid_federation/http_client.rb +70 -0
- data/lib/omniauth_openid_federation/instrumentation.rb +383 -0
- data/lib/omniauth_openid_federation/jwks/cache.rb +76 -0
- data/lib/omniauth_openid_federation/jwks/decode.rb +174 -0
- data/lib/omniauth_openid_federation/jwks/fetch.rb +153 -0
- data/lib/omniauth_openid_federation/jwks/normalizer.rb +49 -0
- data/lib/omniauth_openid_federation/jwks/rotate.rb +97 -0
- data/lib/omniauth_openid_federation/jwks/selector.rb +101 -0
- data/lib/omniauth_openid_federation/jws.rb +416 -0
- data/lib/omniauth_openid_federation/key_extractor.rb +173 -0
- data/lib/omniauth_openid_federation/logger.rb +99 -0
- data/lib/omniauth_openid_federation/rack_endpoint.rb +187 -0
- data/lib/omniauth_openid_federation/railtie.rb +29 -0
- data/lib/omniauth_openid_federation/rate_limiter.rb +55 -0
- data/lib/omniauth_openid_federation/strategy.rb +2029 -0
- data/lib/omniauth_openid_federation/string_helpers.rb +30 -0
- data/lib/omniauth_openid_federation/tasks_helper.rb +428 -0
- data/lib/omniauth_openid_federation/utils.rb +166 -0
- data/lib/omniauth_openid_federation/validators.rb +126 -0
- data/lib/omniauth_openid_federation/version.rb +3 -0
- data/lib/omniauth_openid_federation.rb +98 -0
- data/lib/tasks/omniauth_openid_federation.rake +376 -0
- data/sig/federation.rbs +218 -0
- data/sig/jwks.rbs +63 -0
- data/sig/omniauth_openid_federation.rbs +254 -0
- data/sig/strategy.rbs +60 -0
- metadata +352 -0
data/README.md
ADDED
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
# omniauth_openid_federation
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/omniauth_openid_federation) [](https://github.com/amkisko/omniauth_openid_federation.rb/actions/workflows/test.yml)
|
|
4
|
+
|
|
5
|
+
OmniAuth strategy for OpenID Federation providers with comprehensive security features, supporting signed request objects, ID token encryption, and full OpenID Federation 1.0 compliance.
|
|
6
|
+
|
|
7
|
+
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
8
|
+
|
|
9
|
+
<a href="https://www.kiskolabs.com">
|
|
10
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
11
|
+
</a>
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
# Gemfile
|
|
18
|
+
gem "omniauth_openid_federation"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bundle install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- ✅ **Signed Request Objects (RFC 9101)** - RS256 signing of authorization requests (per OpenID Federation spec: "MUST be signed")
|
|
28
|
+
- ✅ **Optional Request Object Encryption** - Optional RSA-OAEP encryption when provider requires it (per spec: "MAY be encrypted")
|
|
29
|
+
- ✅ **ID Token Encryption/Decryption** - RSA-OAEP encryption and A128CBC-HS256 decryption
|
|
30
|
+
- ✅ **OpenID Federation 1.0** - Full entity statement support and federation metadata
|
|
31
|
+
- ✅ **Federation Endpoint** - Publish entity statements at `/.well-known/openid-federation`
|
|
32
|
+
- ✅ **Automatic Key Provisioning** - Automatic extraction/generation of signing and encryption keys with caching support
|
|
33
|
+
- ✅ **Separate Key Support** - Production-ready support for separate signing and encryption keys
|
|
34
|
+
- ✅ **Entity Type Support** - Full support for both `openid_relying_party` (RP) and `openid_provider` (OP) entity types
|
|
35
|
+
- ✅ **Signed JWKS Support** - Automatic validation for key rotation compliance
|
|
36
|
+
- ✅ **Automatic Provider Key Rotation** - Handles external provider key rotation automatically via Signed JWKS (client key rotation is manual)
|
|
37
|
+
- ✅ **Client Assertion (private_key_jwt)** - Secure client authentication
|
|
38
|
+
- ✅ **Security Hardened** - OWASP compliant, rate limiting, path traversal protection
|
|
39
|
+
- ✅ **Production Ready** - Thread-safe, comprehensive error handling
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
The library relies on **URLs and fingerprint verification** for security. Always fetch entity statements from provider URLs - local files are cached copies for configuration use. Everything is automated via discovery.
|
|
44
|
+
|
|
45
|
+
### Step 1: Get Provider Information
|
|
46
|
+
|
|
47
|
+
Your provider will provide:
|
|
48
|
+
- **Entity statement URL**: `https://provider.example.com/.well-known/openid-federation`
|
|
49
|
+
- **Expected fingerprint hash**: For verification (security guard)
|
|
50
|
+
|
|
51
|
+
**Always use URLs**: Fetch and cache the entity statement locally using the URL and fingerprint:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
rake openid_federation:fetch_entity_statement[
|
|
55
|
+
"https://provider.example.com/.well-known/openid-federation",
|
|
56
|
+
"expected-fingerprint-hash",
|
|
57
|
+
"config/provider-entity-statement.jwt"
|
|
58
|
+
]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This fetches from the URL, verifies the fingerprint, and stores locally. The local file is a cached copy of the URL - always use the URL as the source of truth.
|
|
62
|
+
|
|
63
|
+
### Step 2: Generate Client Keys
|
|
64
|
+
|
|
65
|
+
Generate RSA key pair for client authentication:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
rake openid_federation:prepare_client_keys
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This generates:
|
|
72
|
+
- Private key: `config/client-private-key.pem` (keep secure, never commit)
|
|
73
|
+
- Public JWKS: `config/client-jwks.json` (send to provider for explicit registration)
|
|
74
|
+
|
|
75
|
+
**Security**: Never commit private keys. Add to `.gitignore`:
|
|
76
|
+
```
|
|
77
|
+
config/*-private-key.pem
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Step 3: Register Client
|
|
81
|
+
|
|
82
|
+
**Explicit Registration** (default):
|
|
83
|
+
1. Send `config/client-jwks.json` to your provider
|
|
84
|
+
2. Receive Client ID from provider
|
|
85
|
+
|
|
86
|
+
**Automatic Registration** (if provider supports it):
|
|
87
|
+
- No pre-registration needed
|
|
88
|
+
- Client entity statement is auto-generated via `FederationEndpoint` (see Step 5)
|
|
89
|
+
- Set `client_entity_statement_url` to `https://your-app.com/.well-known/openid-federation`
|
|
90
|
+
|
|
91
|
+
### Step 4: Configure OmniAuth Strategy
|
|
92
|
+
|
|
93
|
+
#### For Devise (Rails)
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# config/initializers/devise.rb
|
|
97
|
+
require "omniauth_openid_federation"
|
|
98
|
+
|
|
99
|
+
private_key = OpenSSL::PKey::RSA.new(File.read("config/client-private-key.pem"))
|
|
100
|
+
|
|
101
|
+
# Always provide the entity statement URL
|
|
102
|
+
entity_statement_url = "https://provider.example.com/.well-known/openid-federation"
|
|
103
|
+
entity_statement_fingerprint = "expected-fingerprint-hash"
|
|
104
|
+
|
|
105
|
+
# Fetch and cache entity statement from URL (run this via rake task or in initializer)
|
|
106
|
+
# rake openid_federation:fetch_entity_statement[entity_statement_url, entity_statement_fingerprint, "config/provider-entity-statement.jwt"]
|
|
107
|
+
|
|
108
|
+
config.omniauth :openid_federation,
|
|
109
|
+
discovery: true, # Enables automatic endpoint discovery
|
|
110
|
+
# Option 1: Provide URL (recommended - library fetches and caches automatically)
|
|
111
|
+
entity_statement_url: entity_statement_url, # Always provide URL as source of truth
|
|
112
|
+
entity_statement_fingerprint: entity_statement_fingerprint, # Fingerprint for verification
|
|
113
|
+
# Option 2: Provide issuer (library builds URL from issuer + /.well-known/openid-federation)
|
|
114
|
+
# issuer: "https://provider.example.com",
|
|
115
|
+
# Option 3: Provide cached path (optional - for offline development)
|
|
116
|
+
# entity_statement_path: "config/provider-entity-statement.jwt", # Cached copy from URL
|
|
117
|
+
client_options: {
|
|
118
|
+
identifier: ENV["OPENID_CLIENT_ID"],
|
|
119
|
+
redirect_uri: "#{ENV["APP_URL"]}/users/auth/openid_federation/callback",
|
|
120
|
+
private_key: private_key
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Key Points**:
|
|
125
|
+
- `entity_statement_url` is recommended - library automatically fetches and caches
|
|
126
|
+
- `entity_statement_fingerprint` is used for verification when fetching from URL
|
|
127
|
+
- `issuer` can be used instead - library builds URL from issuer + `/.well-known/openid-federation`
|
|
128
|
+
- `entity_statement_path` is optional - only for offline development (cached copy)
|
|
129
|
+
- `discovery: true` automatically discovers all endpoints from entity statement
|
|
130
|
+
|
|
131
|
+
#### For OmniAuth (non-Rails)
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# config/initializers/omniauth.rb
|
|
135
|
+
require "omniauth_openid_federation"
|
|
136
|
+
|
|
137
|
+
entity_statement_url = "https://provider.example.com/.well-known/openid-federation"
|
|
138
|
+
entity_statement_fingerprint = "expected-fingerprint-hash"
|
|
139
|
+
|
|
140
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
|
141
|
+
provider :openid_federation,
|
|
142
|
+
discovery: true,
|
|
143
|
+
entity_statement_path: "config/provider-entity-statement.jwt", # Cached copy from URL
|
|
144
|
+
entity_statement_url: entity_statement_url, # Always provide URL as source of truth
|
|
145
|
+
entity_statement_fingerprint: entity_statement_fingerprint, # Fingerprint for verification
|
|
146
|
+
client_options: {
|
|
147
|
+
identifier: ENV["OPENID_CLIENT_ID"],
|
|
148
|
+
redirect_uri: "https://your-app.com/auth/openid_federation/callback",
|
|
149
|
+
private_key: OpenSSL::PKey::RSA.new(File.read("config/client-private-key.pem"))
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Step 5: Configure Federation Endpoint (For Automatic Registration)
|
|
155
|
+
|
|
156
|
+
If using automatic registration, publish your client entity statement:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
# config/initializers/omniauth_openid_federation.rb
|
|
160
|
+
OmniauthOpenidFederation::FederationEndpoint.auto_configure(
|
|
161
|
+
issuer: ENV["APP_URL"],
|
|
162
|
+
private_key: private_key,
|
|
163
|
+
entity_statement_path: "config/client-entity-statement.jwt", # Optional: cached copy for offline dev
|
|
164
|
+
metadata: {
|
|
165
|
+
openid_provider: {
|
|
166
|
+
issuer: ENV["APP_URL"],
|
|
167
|
+
authorization_endpoint: "#{ENV["APP_URL"]}/users/auth/openid_federation",
|
|
168
|
+
token_endpoint: "#{ENV["APP_URL"]}/users/auth/openid_federation",
|
|
169
|
+
userinfo_endpoint: "#{ENV["APP_URL"]}/users/auth/openid_federation",
|
|
170
|
+
jwks_uri: "#{ENV["APP_URL"]}/.well-known/jwks.json",
|
|
171
|
+
signed_jwks_uri: "#{ENV["APP_URL"]}/.well-known/signed-jwks.json"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
# config/routes.rb
|
|
179
|
+
OmniauthOpenidFederation::FederationEndpoint.mount_routes(self)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Key Points**:
|
|
183
|
+
- `auto_configure` automatically extracts/generates JWKS from keys
|
|
184
|
+
- Only application-specific endpoints need to be provided in metadata
|
|
185
|
+
- Well-known endpoints are auto-generated
|
|
186
|
+
|
|
187
|
+
### Step 6: Add Routes
|
|
188
|
+
|
|
189
|
+
#### For Devise
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
# config/routes.rb
|
|
193
|
+
Rails.application.routes.draw do
|
|
194
|
+
devise_for :users, controllers: {
|
|
195
|
+
omniauth_callbacks: "users/omniauth_callbacks"
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### For OmniAuth
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
# config/routes.rb
|
|
204
|
+
Rails.application.routes.draw do
|
|
205
|
+
get "/auth/:provider/callback", to: "sessions#create"
|
|
206
|
+
get "/auth/failure", to: "sessions#failure"
|
|
207
|
+
end
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Step 7: Create Callback Controller
|
|
211
|
+
|
|
212
|
+
#### For Devise
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
# app/controllers/users/omniauth_callbacks_controller.rb
|
|
216
|
+
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
217
|
+
skip_before_action :authenticate_user!, only: [:openid_federation, :failure]
|
|
218
|
+
|
|
219
|
+
def openid_federation
|
|
220
|
+
auth = request.env["omniauth.auth"]
|
|
221
|
+
user = User.find_or_create_from_omniauth(auth)
|
|
222
|
+
|
|
223
|
+
if user&.persisted?
|
|
224
|
+
sign_in_and_redirect user, event: :authentication
|
|
225
|
+
else
|
|
226
|
+
redirect_to root_path, alert: "Authentication failed"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def failure
|
|
231
|
+
redirect_to root_path, alert: "Authentication failed"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Step 8: Create User Model Method
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
# app/models/user.rb
|
|
240
|
+
class User < ApplicationRecord
|
|
241
|
+
def self.find_or_create_from_omniauth(auth)
|
|
242
|
+
user = find_by(provider: auth.provider, uid: auth.uid)
|
|
243
|
+
|
|
244
|
+
if user
|
|
245
|
+
user.update(
|
|
246
|
+
email: auth.info.email,
|
|
247
|
+
name: auth.info.name,
|
|
248
|
+
first_name: auth.info.first_name,
|
|
249
|
+
last_name: auth.info.last_name
|
|
250
|
+
)
|
|
251
|
+
else
|
|
252
|
+
user = create(
|
|
253
|
+
provider: auth.provider,
|
|
254
|
+
uid: auth.uid,
|
|
255
|
+
email: auth.info.email,
|
|
256
|
+
name: auth.info.name,
|
|
257
|
+
first_name: auth.info.first_name,
|
|
258
|
+
last_name: auth.info.last_name
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
user
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Rake Tasks
|
|
268
|
+
|
|
269
|
+
### Prepare Client Keys
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
rake openid_federation:prepare_client_keys
|
|
273
|
+
rake openid_federation:prepare_client_keys[separate,config] # Separate signing/encryption keys
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Fetch Entity Statement
|
|
277
|
+
|
|
278
|
+
Fetches entity statement from provider URL, verifies fingerprint, and caches locally:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
rake openid_federation:fetch_entity_statement[
|
|
282
|
+
"https://provider.example.com/.well-known/openid-federation",
|
|
283
|
+
"expected-fingerprint-hash",
|
|
284
|
+
"config/provider-entity-statement.jwt"
|
|
285
|
+
]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Note**: Always use the URL as the source of truth - the local file is just a cached copy.
|
|
289
|
+
|
|
290
|
+
### Parse Entity Statement
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
rake openid_federation:parse_entity_statement["config/provider-entity-statement.jwt"]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Test Local Entity Statement Endpoint
|
|
297
|
+
|
|
298
|
+
Validates your local entity statement endpoint and tests all linked endpoints. Useful for verifying your federation endpoint implementation:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# Default (localhost:3000)
|
|
302
|
+
rake openid_federation:test_local_endpoint
|
|
303
|
+
|
|
304
|
+
# Custom base URL
|
|
305
|
+
rake openid_federation:test_local_endpoint[http://localhost:3000]
|
|
306
|
+
|
|
307
|
+
# Via environment variable
|
|
308
|
+
BASE_URL=http://localhost:3000 rake openid_federation:test_local_endpoint
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
This task:
|
|
312
|
+
- Fetches and validates the entity statement from `/.well-known/openid-federation`
|
|
313
|
+
- Shows key configuration status (single vs separate keys) with recommendations
|
|
314
|
+
- Tests all endpoints mentioned in the entity statement
|
|
315
|
+
- Displays validation warnings without blocking execution
|
|
316
|
+
|
|
317
|
+
See all tasks: `rake -T openid_federation`
|
|
318
|
+
|
|
319
|
+
### Cache Configuration and Key Rotation
|
|
320
|
+
|
|
321
|
+
Configure automatic key rotation:
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
OmniauthOpenidFederation.configure do |config|
|
|
325
|
+
config.cache_ttl = 3600 # Refresh provider keys every hour
|
|
326
|
+
config.rotate_on_errors = true # Auto-handle provider key rotation
|
|
327
|
+
end
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Security Instrumentation
|
|
331
|
+
|
|
332
|
+
Configure custom instrumentation for security events, MITM attack detection, and authentication mismatches:
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
OmniauthOpenidFederation.configure do |config|
|
|
336
|
+
# Configure with Sentry
|
|
337
|
+
config.instrumentation = ->(event, data) do
|
|
338
|
+
Sentry.capture_message(
|
|
339
|
+
"OpenID Federation: #{event}",
|
|
340
|
+
level: data[:severity] == :error ? :error : :warning,
|
|
341
|
+
extra: data
|
|
342
|
+
)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**With Honeybadger**:
|
|
348
|
+
```ruby
|
|
349
|
+
OmniauthOpenidFederation.configure do |config|
|
|
350
|
+
config.instrumentation = ->(event, data) do
|
|
351
|
+
Honeybadger.notify("OpenID Federation: #{event}", context: data)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**With custom logger**:
|
|
357
|
+
```ruby
|
|
358
|
+
OmniauthOpenidFederation.configure do |config|
|
|
359
|
+
config.instrumentation = ->(event, data) do
|
|
360
|
+
Rails.logger.warn("[Security] #{event}: #{data.inspect}")
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Instrumented Events**:
|
|
366
|
+
- `csrf_detected` - CSRF attack detected (state mismatch)
|
|
367
|
+
- `signature_verification_failed` - JWT signature verification failed (possible MITM)
|
|
368
|
+
- `decryption_failed` - Token decryption failed (possible MITM or key mismatch)
|
|
369
|
+
- `token_validation_failed` - Token validation failed (possible tampering)
|
|
370
|
+
- `key_rotation_detected` - Key rotation detected (normal operation)
|
|
371
|
+
- `kid_not_found` - Key ID not found in JWKS (possible key rotation or MITM)
|
|
372
|
+
- `entity_statement_validation_failed` - Entity statement validation failed (possible MITM)
|
|
373
|
+
- `fingerprint_mismatch` - Entity statement fingerprint mismatch (possible MITM)
|
|
374
|
+
- `trust_chain_validation_failed` - Trust chain validation failed
|
|
375
|
+
- `unexpected_authentication_break` - Unexpected authentication failure
|
|
376
|
+
- `missing_required_claims` - Token missing required claims
|
|
377
|
+
|
|
378
|
+
**Security Note**: All sensitive data (tokens, keys, fingerprints) is automatically sanitized before being sent to your instrumentation callback.
|
|
379
|
+
|
|
380
|
+
**Key Rotation Types**:
|
|
381
|
+
- **Provider Keys** (from external providers): ✅ Automatic via Signed JWKS - library automatically detects and uses new provider keys
|
|
382
|
+
- **Client Keys** (your own keys): ⚠️ **Manual rotation required** - you must generate new RSA keys and update entity statement
|
|
383
|
+
|
|
384
|
+
**Client Key Rotation Process** (Manual Steps Required):
|
|
385
|
+
1. **Generate new RSA keys** (manual):
|
|
386
|
+
```bash
|
|
387
|
+
bundle exec rake omniauth_openid_federation:prepare_client_keys[key_type=separate]
|
|
388
|
+
```
|
|
389
|
+
2. **Update entity statement file** (manual): Update `entity_statement_path` with new keys, or let the library regenerate it
|
|
390
|
+
3. **Library automatically uses new keys** (automatic): Library extracts JWKS from updated entity statement file on next cache refresh
|
|
391
|
+
|
|
392
|
+
**Note**: The library automatically generates JWKS from your RSA keys, but you must manually generate new RSA keys when rotating. The library then automatically uses the new keys from the updated entity statement file. See [Automatic Key Provisioning](#automatic-key-provisioning) for details.
|
|
393
|
+
|
|
394
|
+
### Publishing Federation Endpoint
|
|
395
|
+
|
|
396
|
+
Publish your entity statement at `/.well-known/openid-federation` using `auto_configure`.
|
|
397
|
+
|
|
398
|
+
The library supports two entity types:
|
|
399
|
+
- **openid_relying_party (RP)**: For clients/relying parties (PRIMARY USE CASE)
|
|
400
|
+
- **openid_provider (OP)**: For providers/servers (secondary use case)
|
|
401
|
+
|
|
402
|
+
#### Relying Party (RP) Configuration (Primary Use Case)
|
|
403
|
+
|
|
404
|
+
**First, generate your RSA keys** (if not already generated):
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
# Generate separate signing and encryption keys (RECOMMENDED for production)
|
|
408
|
+
bundle exec rake omniauth_openid_federation:prepare_client_keys[key_type=separate]
|
|
409
|
+
|
|
410
|
+
# Or generate single key for dev/testing (NOT RECOMMENDED for production)
|
|
411
|
+
bundle exec rake omniauth_openid_federation:prepare_client_keys[key_type=single]
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
This creates:
|
|
415
|
+
- `config/client-signing-private-key.pem` and `config/client-encryption-private-key.pem` (separate keys)
|
|
416
|
+
- OR `config/client-private-key.pem` (single key for dev/testing)
|
|
417
|
+
|
|
418
|
+
**Then configure the federation endpoint** - the library automatically generates JWKS from your keys:
|
|
419
|
+
|
|
420
|
+
```ruby
|
|
421
|
+
# config/initializers/omniauth_openid_federation.rb
|
|
422
|
+
# Production Setup (RECOMMENDED): Separate signing and encryption keys
|
|
423
|
+
# The library automatically generates JWKS from these keys
|
|
424
|
+
OmniauthOpenidFederation::FederationEndpoint.auto_configure(
|
|
425
|
+
issuer: "https://your-app.com",
|
|
426
|
+
signing_key: OpenSSL::PKey::RSA.new(File.read("config/client-signing-private-key.pem")),
|
|
427
|
+
encryption_key: OpenSSL::PKey::RSA.new(File.read("config/client-encryption-private-key.pem")),
|
|
428
|
+
entity_statement_path: "config/client-entity-statement.jwt", # Cache for automatic key rotation
|
|
429
|
+
metadata: {
|
|
430
|
+
openid_relying_party: {
|
|
431
|
+
redirect_uris: ["https://your-app.com/users/auth/openid_federation/callback"],
|
|
432
|
+
client_registration_types: ["automatic"],
|
|
433
|
+
application_type: "web",
|
|
434
|
+
grant_types: ["authorization_code"],
|
|
435
|
+
response_types: ["code"],
|
|
436
|
+
token_endpoint_auth_method: "private_key_jwt",
|
|
437
|
+
token_endpoint_auth_signing_alg: "RS256",
|
|
438
|
+
request_object_signing_alg: "RS256",
|
|
439
|
+
id_token_encrypted_response_alg: "RSA-OAEP",
|
|
440
|
+
id_token_encrypted_response_enc: "A128CBC-HS256"
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
auto_provision_keys: true # Library automatically generates JWKS from provided keys
|
|
444
|
+
)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Development/Testing** (NOT RECOMMENDED FOR PRODUCTION):
|
|
448
|
+
```ruby
|
|
449
|
+
OmniauthOpenidFederation::FederationEndpoint.auto_configure(
|
|
450
|
+
issuer: "https://your-app.com",
|
|
451
|
+
private_key: private_key, # DEV/TESTING ONLY - single key for both signing and encryption
|
|
452
|
+
entity_statement_path: "config/client-entity-statement.jwt",
|
|
453
|
+
metadata: {
|
|
454
|
+
openid_relying_party: { ... }
|
|
455
|
+
},
|
|
456
|
+
auto_provision_keys: true
|
|
457
|
+
)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### OpenID Provider (OP) Configuration (Secondary Use Case)
|
|
461
|
+
|
|
462
|
+
**First, generate your RSA keys** (if not already generated):
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
# Generate separate signing and encryption keys (RECOMMENDED for production)
|
|
466
|
+
bundle exec rake omniauth_openid_federation:prepare_client_keys[key_type=separate,output_dir=config]
|
|
467
|
+
|
|
468
|
+
# Or generate single key for dev/testing (NOT RECOMMENDED for production)
|
|
469
|
+
bundle exec rake omniauth_openid_federation:prepare_client_keys[key_type=single,output_dir=config]
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**Then configure the federation endpoint** - the library automatically generates JWKS from your keys:
|
|
473
|
+
|
|
474
|
+
```ruby
|
|
475
|
+
# For provider/server applications
|
|
476
|
+
# Production Setup (RECOMMENDED): Separate signing and encryption keys
|
|
477
|
+
# The library automatically generates JWKS from these keys
|
|
478
|
+
signing_key = OpenSSL::PKey::RSA.new(File.read("config/client-signing-private-key.pem"))
|
|
479
|
+
encryption_key = OpenSSL::PKey::RSA.new(File.read("config/client-encryption-private-key.pem"))
|
|
480
|
+
|
|
481
|
+
OmniauthOpenidFederation::FederationEndpoint.auto_configure(
|
|
482
|
+
issuer: "https://provider.example.com",
|
|
483
|
+
signing_key: signing_key,
|
|
484
|
+
encryption_key: encryption_key,
|
|
485
|
+
entity_statement_path: "config/provider-entity-statement.jwt",
|
|
486
|
+
metadata: {
|
|
487
|
+
openid_provider: {
|
|
488
|
+
issuer: "https://provider.example.com",
|
|
489
|
+
authorization_endpoint: "https://provider.example.com/oauth2/authorize",
|
|
490
|
+
token_endpoint: "https://provider.example.com/oauth2/token",
|
|
491
|
+
userinfo_endpoint: "https://provider.example.com/oauth2/userinfo",
|
|
492
|
+
jwks_uri: "https://provider.example.com/.well-known/jwks.json",
|
|
493
|
+
signed_jwks_uri: "https://provider.example.com/.well-known/signed-jwks.json"
|
|
494
|
+
# federation_fetch_endpoint is automatically added for OPs
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
auto_provision_keys: true # Library automatically generates JWKS from provided keys
|
|
498
|
+
)
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Development/Testing** (NOT RECOMMENDED FOR PRODUCTION):
|
|
502
|
+
```ruby
|
|
503
|
+
# Single private key for both signing and encryption (DEV/TESTING ONLY)
|
|
504
|
+
OmniauthOpenidFederation::FederationEndpoint.auto_configure(
|
|
505
|
+
issuer: "https://provider.example.com",
|
|
506
|
+
private_key: private_key, # DEV/TESTING ONLY - not recommended for production
|
|
507
|
+
entity_statement_path: "config/provider-entity-statement.jwt",
|
|
508
|
+
metadata: {
|
|
509
|
+
openid_provider: {
|
|
510
|
+
issuer: "https://provider.example.com",
|
|
511
|
+
authorization_endpoint: "https://provider.example.com/oauth2/authorize",
|
|
512
|
+
token_endpoint: "https://provider.example.com/oauth2/token",
|
|
513
|
+
userinfo_endpoint: "https://provider.example.com/oauth2/userinfo",
|
|
514
|
+
jwks_uri: "https://provider.example.com/.well-known/jwks.json",
|
|
515
|
+
signed_jwks_uri: "https://provider.example.com/.well-known/signed-jwks.json"
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
auto_provision_keys: true
|
|
519
|
+
)
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
```ruby
|
|
523
|
+
# config/routes.rb
|
|
524
|
+
OmniauthOpenidFederation::FederationEndpoint.mount_routes(self)
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
**What `auto_configure` does automatically**:
|
|
528
|
+
- Extracts JWKS from entity statement file or generates from provided keys
|
|
529
|
+
- Supports separate signing/encryption keys (RECOMMENDED) or single key (dev/testing)
|
|
530
|
+
- Auto-detects entity type and generates well-known endpoints
|
|
531
|
+
- Uses `entity_statement_path` as cache for key rotation
|
|
532
|
+
|
|
533
|
+
**Manual Configuration** (advanced, not recommended):
|
|
534
|
+
|
|
535
|
+
If you need manual control, use `configure` instead of `auto_configure`:
|
|
536
|
+
|
|
537
|
+
```ruby
|
|
538
|
+
OmniauthOpenidFederation::FederationEndpoint.configure do |config|
|
|
539
|
+
config.issuer = "https://your-app.com"
|
|
540
|
+
config.subject = "https://your-app.com"
|
|
541
|
+
config.signing_key = signing_key # RECOMMENDED: Separate signing key
|
|
542
|
+
config.encryption_key = encryption_key # RECOMMENDED: Separate encryption key
|
|
543
|
+
config.jwks = jwks # Must provide manually
|
|
544
|
+
config.metadata = { ... }
|
|
545
|
+
end
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Automatic Key Provisioning
|
|
549
|
+
|
|
550
|
+
The `auto_configure` method automatically generates JWKS from your RSA keys (generate keys first using the rake task).
|
|
551
|
+
|
|
552
|
+
**Priority Order**:
|
|
553
|
+
1. Extracts JWKS from `entity_statement_path` if file exists (supports key rotation)
|
|
554
|
+
2. Generates JWKS from separate `signing_key` and `encryption_key` (RECOMMENDED)
|
|
555
|
+
3. Generates JWKS from single `private_key` (dev/testing only)
|
|
556
|
+
|
|
557
|
+
**Key Rotation** (Semi-Automatic):
|
|
558
|
+
1. **Manual**: Generate new RSA keys using `rake omniauth_openid_federation:prepare_client_keys`
|
|
559
|
+
2. **Manual**: Update entity statement file at `entity_statement_path` with new keys
|
|
560
|
+
3. **Automatic**: Library extracts and uses new keys from updated file on next cache refresh
|
|
561
|
+
|
|
562
|
+
## Configuration Options
|
|
563
|
+
|
|
564
|
+
### Required
|
|
565
|
+
|
|
566
|
+
- `client_options[:identifier]` - Client ID from provider
|
|
567
|
+
- `client_options[:redirect_uri]` - Callback URL
|
|
568
|
+
- `client_options[:private_key]` - RSA private key for signing
|
|
569
|
+
- **One of the following** (for provider entity statement):
|
|
570
|
+
- `entity_statement_url` - Provider entity statement URL (recommended - library fetches and caches automatically)
|
|
571
|
+
- `issuer` - Provider issuer URI (library builds entity statement URL from issuer + `/.well-known/openid-federation`)
|
|
572
|
+
- `entity_statement_path` - Provider entity statement path (optional - for offline development)
|
|
573
|
+
|
|
574
|
+
### Optional
|
|
575
|
+
|
|
576
|
+
- `discovery` - Enable automatic endpoint discovery (default: `true`)
|
|
577
|
+
- `entity_statement_fingerprint` - Expected SHA-256 fingerprint for verification (recommended when using `entity_statement_url` or `issuer`)
|
|
578
|
+
- `entity_statement_path` - Path to provider entity statement (optional - for offline development, cached copy)
|
|
579
|
+
- `always_encrypt_request_object` - Always encrypt request objects if encryption keys are available (default: `false`, see [Request Object Security](#request-object-security-signing-vs-encryption) below)
|
|
580
|
+
- `client_entity_statement_url` - URL to client entity statement (for automatic registration)
|
|
581
|
+
- `client_entity_statement_path` - Path to client entity statement (fallback if URL not available)
|
|
582
|
+
- `client_registration_type` - `:explicit` (default) or `:automatic` (auto-detected if client_entity_statement_url/path provided)
|
|
583
|
+
- `client_entity_identifier` - Entity identifier for automatic registration
|
|
584
|
+
- `scope` - OAuth scopes (default: `[:openid]`)
|
|
585
|
+
- `response_type` - Response type (default: `"code"`)
|
|
586
|
+
- `client_auth_method` - Client authentication (default: `:jwt_bearer`)
|
|
587
|
+
- `client_signing_alg` - Signing algorithm (default: `:RS256`)
|
|
588
|
+
- `fetch_userinfo` - Whether to fetch userinfo endpoint (default: `true`)
|
|
589
|
+
- `acr_values` - Authentication Context Class Reference values (provider-specific)
|
|
590
|
+
- `key_source` - `:local` (default) or `:federation` (advanced)
|
|
591
|
+
|
|
592
|
+
### Global Configuration
|
|
593
|
+
|
|
594
|
+
Configure global settings via `OmniauthOpenidFederation.configure`:
|
|
595
|
+
|
|
596
|
+
```ruby
|
|
597
|
+
OmniauthOpenidFederation.configure do |config|
|
|
598
|
+
# Cache configuration
|
|
599
|
+
config.cache_ttl = 3600 # JWKS cache TTL in seconds
|
|
600
|
+
config.rotate_on_errors = true # Auto-rotate on key-related errors
|
|
601
|
+
|
|
602
|
+
# Security instrumentation (Sentry, Honeybadger, etc.)
|
|
603
|
+
config.instrumentation = ->(event, data) do
|
|
604
|
+
Sentry.capture_message("OpenID Federation: #{event}", level: :warning, extra: data)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
# HTTP configuration
|
|
608
|
+
config.http_timeout = 10
|
|
609
|
+
config.max_retries = 3
|
|
610
|
+
config.verify_ssl = true
|
|
611
|
+
end
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Request Object Security (Signing vs Encryption)
|
|
615
|
+
|
|
616
|
+
**Per OpenID Federation 1.0 and RFC 9101:**
|
|
617
|
+
- **Signing (MANDATORY)**: Request objects **MUST be signed** using RS256 (always enforced, cannot be disabled)
|
|
618
|
+
- **Encryption (OPTIONAL)**: Request objects **MAY be encrypted** when provider requires it or when `always_encrypt_request_object: true`
|
|
619
|
+
|
|
620
|
+
**Encryption Behavior:**
|
|
621
|
+
- **Default** (`always_encrypt_request_object: false`): Only encrypts if provider metadata specifies `request_object_encryption_alg`
|
|
622
|
+
- **When `true`**: Encrypts even if provider doesn't require it (if encryption keys available)
|
|
623
|
+
- **Use case**: High-security deployments requiring defense-in-depth beyond minimum spec
|
|
624
|
+
|
|
625
|
+
**Note**: Signing provides authentication and integrity. Encryption adds confidentiality but is optional and adds overhead.
|
|
626
|
+
|
|
627
|
+
### Detailed Configuration Examples
|
|
628
|
+
|
|
629
|
+
#### Devise with Environment Variables (Recommended)
|
|
630
|
+
|
|
631
|
+
```ruby
|
|
632
|
+
# config/initializers/devise.rb
|
|
633
|
+
require "omniauth_openid_federation"
|
|
634
|
+
|
|
635
|
+
private_key = if ENV["OPENID_CLIENT_PRIVATE_KEY"]
|
|
636
|
+
OpenSSL::PKey::RSA.new(Base64.decode64(ENV["OPENID_CLIENT_PRIVATE_KEY"]))
|
|
637
|
+
else
|
|
638
|
+
OpenSSL::PKey::RSA.new(File.read("config/client-private-key.pem"))
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
config.omniauth :openid_federation,
|
|
642
|
+
discovery: true, # Auto-discovers endpoints from entity statement
|
|
643
|
+
entity_statement_url: ENV["OPENID_ENTITY_STATEMENT_URL"], # Always provide URL
|
|
644
|
+
entity_statement_fingerprint: ENV["OPENID_ENTITY_STATEMENT_FINGERPRINT"], # Fingerprint for verification
|
|
645
|
+
entity_statement_path: "config/provider-entity-statement.jwt", # Cached copy from URL (fetch via rake task)
|
|
646
|
+
client_entity_statement_url: "#{ENV["APP_URL"]}/.well-known/openid-federation", # For automatic registration
|
|
647
|
+
client_options: {
|
|
648
|
+
identifier: ENV["OPENID_CLIENT_ID"],
|
|
649
|
+
redirect_uri: "#{ENV["APP_URL"]}/users/auth/openid_federation/callback",
|
|
650
|
+
private_key: private_key
|
|
651
|
+
}
|
|
652
|
+
# All endpoints are auto-discovered - no manual configuration needed
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
#### OmniAuth with URL-based Entity Statement (Production)
|
|
656
|
+
|
|
657
|
+
```ruby
|
|
658
|
+
# config/initializers/omniauth.rb
|
|
659
|
+
require "omniauth_openid_federation"
|
|
660
|
+
|
|
661
|
+
entity_statement_url = "https://provider.example.com/.well-known/openid-federation"
|
|
662
|
+
entity_statement_fingerprint = "expected-fingerprint-hash"
|
|
663
|
+
|
|
664
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
|
665
|
+
provider :openid_federation,
|
|
666
|
+
discovery: true,
|
|
667
|
+
entity_statement_url: entity_statement_url, # Always provide URL
|
|
668
|
+
entity_statement_fingerprint: entity_statement_fingerprint, # Fingerprint for verification
|
|
669
|
+
entity_statement_path: "config/provider-entity-statement.jwt", # Cached copy from URL
|
|
670
|
+
client_options: {
|
|
671
|
+
identifier: ENV["OPENID_CLIENT_ID"],
|
|
672
|
+
redirect_uri: "https://your-app.com/auth/openid_federation/callback",
|
|
673
|
+
private_key: OpenSSL::PKey::RSA.new(File.read("config/client-private-key.pem"))
|
|
674
|
+
}
|
|
675
|
+
end
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Key Points**:
|
|
679
|
+
- **Always provide `entity_statement_url`** - this is the source of truth
|
|
680
|
+
- `entity_statement_fingerprint` is used for verification when fetching
|
|
681
|
+
- `entity_statement_path` points to the cached copy fetched from the URL
|
|
682
|
+
- All endpoints are automatically discovered - no manual endpoint configuration
|
|
683
|
+
|
|
684
|
+
## API Reference
|
|
685
|
+
|
|
686
|
+
### `OmniauthOpenidFederation::Jws`
|
|
687
|
+
|
|
688
|
+
Builds and signs JWT request objects:
|
|
689
|
+
|
|
690
|
+
```ruby
|
|
691
|
+
jws = OmniauthOpenidFederation::Jws.new(
|
|
692
|
+
client_id: "client-id",
|
|
693
|
+
redirect_uri: "https://example.com/callback",
|
|
694
|
+
scope: "openid",
|
|
695
|
+
issuer: "https://provider.example.com",
|
|
696
|
+
audience: "https://provider.example.com",
|
|
697
|
+
private_key: private_key
|
|
698
|
+
)
|
|
699
|
+
signed_jwt = jws.sign
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### `OmniauthOpenidFederation::Federation::EntityStatement`
|
|
703
|
+
|
|
704
|
+
Fetches and validates entity statements:
|
|
705
|
+
|
|
706
|
+
```ruby
|
|
707
|
+
statement = OmniauthOpenidFederation::Federation::EntityStatement.fetch!(
|
|
708
|
+
"https://provider.example.com/.well-known/openid-federation",
|
|
709
|
+
fingerprint: "expected-fingerprint"
|
|
710
|
+
)
|
|
711
|
+
metadata = statement.parse
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### `OmniauthOpenidFederation::Federation::SignedJWKS`
|
|
715
|
+
|
|
716
|
+
Fetches and validates signed JWKS:
|
|
717
|
+
|
|
718
|
+
```ruby
|
|
719
|
+
signed_jwks = OmniauthOpenidFederation::Federation::SignedJWKS.fetch!(
|
|
720
|
+
signed_jwks_uri,
|
|
721
|
+
entity_jwks
|
|
722
|
+
)
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
See inline code documentation for complete API reference.
|
|
726
|
+
|
|
727
|
+
## Troubleshooting
|
|
728
|
+
|
|
729
|
+
**"Private key is required"**
|
|
730
|
+
- Generate keys: `rake openid_federation:prepare_client_keys`
|
|
731
|
+
- Verify key path and format (PEM)
|
|
732
|
+
|
|
733
|
+
**"Audience is required"**
|
|
734
|
+
- Provide `entity_statement_url` and `entity_statement_path` (auto-resolves audience from entity statement)
|
|
735
|
+
|
|
736
|
+
**"Entity statement fingerprint mismatch"**
|
|
737
|
+
- Verify `entity_statement_fingerprint` with provider
|
|
738
|
+
- Fetch fresh entity statement from URL: `rake openid_federation:fetch_entity_statement[entity_statement_url, entity_statement_fingerprint, entity_statement_path]`
|
|
739
|
+
- Always use the provider URL as the source of truth
|
|
740
|
+
|
|
741
|
+
**"JWT signature verification failed"**
|
|
742
|
+
- Provider may have rotated keys (auto-handled with `rotate_on_errors: true`)
|
|
743
|
+
- Clear cache: `Rails.cache.delete_matched("openid_federation_jwks_*")`
|
|
744
|
+
|
|
745
|
+
## Security
|
|
746
|
+
|
|
747
|
+
See [SECURITY.md](SECURITY.md) for detailed security features, protections, and vulnerability reporting.
|
|
748
|
+
|
|
749
|
+
## Requirements
|
|
750
|
+
|
|
751
|
+
- Ruby >= 3.0
|
|
752
|
+
- Rails >= 6.0 (optional)
|
|
753
|
+
- `omniauth-oauth2` ~> 1.8
|
|
754
|
+
- `openid_connect` ~> 2.3
|
|
755
|
+
- `jwe` ~> 1.1
|
|
756
|
+
- `jwt` ~> 3.1
|
|
757
|
+
- `http` ~> 5.3
|
|
758
|
+
|
|
759
|
+
## Example Files
|
|
760
|
+
|
|
761
|
+
See `examples/` directory for complete configuration examples:
|
|
762
|
+
- `examples/config/initializers/devise.rb.example`
|
|
763
|
+
- `examples/app/controllers/users/omniauth_callbacks_controller.rb.example`
|
|
764
|
+
- `examples/app/models/user.rb.example`
|
|
765
|
+
|
|
766
|
+
## Development
|
|
767
|
+
|
|
768
|
+
Run release.rb script to prepare code for publishing, it has all the required checks and tests.
|
|
769
|
+
|
|
770
|
+
```bash
|
|
771
|
+
usr/bin/release.rb
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Development: Using from Local Repository
|
|
775
|
+
|
|
776
|
+
When developing the gem or testing changes in your application, you can point your Gemfile to a local path:
|
|
777
|
+
|
|
778
|
+
```ruby
|
|
779
|
+
# In your application's Gemfile
|
|
780
|
+
gem "omniauth_openid_federation", path: "../omniauth_openid_federation.rb"
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
Then run:
|
|
784
|
+
|
|
785
|
+
```bash
|
|
786
|
+
bundle install
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**Note:** When using `path:` in your Gemfile, Bundler will use the local gem directly. Changes you make to the gem code will be immediately available in your application without needing to rebuild or reinstall the gem. This is ideal for development and testing.
|
|
790
|
+
|
|
791
|
+
## Contributing
|
|
792
|
+
|
|
793
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/omniauth_openid_federation.rb
|
|
794
|
+
|
|
795
|
+
Contribution policy:
|
|
796
|
+
- New features are not necessarily added to the gem
|
|
797
|
+
- Pull request should have test coverage for affected parts
|
|
798
|
+
- Pull request should have changelog entry
|
|
799
|
+
|
|
800
|
+
Review policy:
|
|
801
|
+
- It might take up to 2 calendar weeks to review and merge critical fixes
|
|
802
|
+
- It might take up to 6 calendar months to review and merge pull request
|
|
803
|
+
- It might take up to 1 calendar year to review an issue
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
## Publishing
|
|
807
|
+
|
|
808
|
+
```sh
|
|
809
|
+
rm omniauth_openid_federation-*.gem
|
|
810
|
+
gem build omniauth_openid_federation.gemspec
|
|
811
|
+
gem push omniauth_openid_federation-*.gem
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
## References
|
|
815
|
+
|
|
816
|
+
- [OpenID Federation 1.0 Specification](https://openid.net/specs/openid-federation-1_0.html)
|
|
817
|
+
- [RFC 9101 - OAuth 2.0 Authorization Request](https://datatracker.ietf.org/doc/html/rfc9101)
|
|
818
|
+
- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
|
|
819
|
+
|
|
820
|
+
## License
|
|
821
|
+
|
|
822
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|