omniauth_openid_federation 1.2.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.

Potentially problematic release.


This version of omniauth_openid_federation might be problematic. Click here for more details.

Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +36 -0
  3. data/LICENSE.md +22 -0
  4. data/README.md +922 -0
  5. data/SECURITY.md +28 -0
  6. data/examples/README_INTEGRATION_TESTING.md +399 -0
  7. data/examples/README_MOCK_OP.md +243 -0
  8. data/examples/app/controllers/users/omniauth_callbacks_controller.rb.example +37 -0
  9. data/examples/app/jobs/jwks_rotation_job.rb.example +60 -0
  10. data/examples/app/models/user.rb.example +39 -0
  11. data/examples/config/initializers/devise.rb.example +131 -0
  12. data/examples/config/initializers/federation_endpoint.rb.example +206 -0
  13. data/examples/config/mock_op.yml.example +83 -0
  14. data/examples/config/open_id_connect_config.rb.example +210 -0
  15. data/examples/config/routes.rb.example +12 -0
  16. data/examples/db/migrate/add_omniauth_to_users.rb.example +16 -0
  17. data/examples/integration_test_flow.rb +1334 -0
  18. data/examples/jobs/README.md +194 -0
  19. data/examples/jobs/federation_cache_refresh_job.rb.example +78 -0
  20. data/examples/jobs/federation_files_generation_job.rb.example +87 -0
  21. data/examples/mock_op_server.rb +775 -0
  22. data/examples/mock_rp_server.rb +435 -0
  23. data/lib/omniauth_openid_federation/access_token.rb +504 -0
  24. data/lib/omniauth_openid_federation/cache.rb +39 -0
  25. data/lib/omniauth_openid_federation/cache_adapter.rb +173 -0
  26. data/lib/omniauth_openid_federation/configuration.rb +135 -0
  27. data/lib/omniauth_openid_federation/constants.rb +13 -0
  28. data/lib/omniauth_openid_federation/endpoint_resolver.rb +168 -0
  29. data/lib/omniauth_openid_federation/engine.rb +21 -0
  30. data/lib/omniauth_openid_federation/entity_statement_reader.rb +129 -0
  31. data/lib/omniauth_openid_federation/errors.rb +52 -0
  32. data/lib/omniauth_openid_federation/federation/entity_statement.rb +331 -0
  33. data/lib/omniauth_openid_federation/federation/entity_statement_builder.rb +188 -0
  34. data/lib/omniauth_openid_federation/federation/entity_statement_fetcher.rb +142 -0
  35. data/lib/omniauth_openid_federation/federation/entity_statement_helper.rb +87 -0
  36. data/lib/omniauth_openid_federation/federation/entity_statement_parser.rb +198 -0
  37. data/lib/omniauth_openid_federation/federation/entity_statement_validator.rb +502 -0
  38. data/lib/omniauth_openid_federation/federation/metadata_policy_merger.rb +276 -0
  39. data/lib/omniauth_openid_federation/federation/signed_jwks.rb +210 -0
  40. data/lib/omniauth_openid_federation/federation/trust_chain_resolver.rb +225 -0
  41. data/lib/omniauth_openid_federation/federation_endpoint.rb +949 -0
  42. data/lib/omniauth_openid_federation/http_client.rb +70 -0
  43. data/lib/omniauth_openid_federation/instrumentation.rb +399 -0
  44. data/lib/omniauth_openid_federation/jwks/cache.rb +76 -0
  45. data/lib/omniauth_openid_federation/jwks/decode.rb +175 -0
  46. data/lib/omniauth_openid_federation/jwks/fetch.rb +153 -0
  47. data/lib/omniauth_openid_federation/jwks/normalizer.rb +49 -0
  48. data/lib/omniauth_openid_federation/jwks/rotate.rb +97 -0
  49. data/lib/omniauth_openid_federation/jwks/selector.rb +101 -0
  50. data/lib/omniauth_openid_federation/jws.rb +410 -0
  51. data/lib/omniauth_openid_federation/key_extractor.rb +173 -0
  52. data/lib/omniauth_openid_federation/logger.rb +99 -0
  53. data/lib/omniauth_openid_federation/rack_endpoint.rb +187 -0
  54. data/lib/omniauth_openid_federation/railtie.rb +15 -0
  55. data/lib/omniauth_openid_federation/rate_limiter.rb +55 -0
  56. data/lib/omniauth_openid_federation/strategy.rb +2114 -0
  57. data/lib/omniauth_openid_federation/string_helpers.rb +30 -0
  58. data/lib/omniauth_openid_federation/tasks_helper.rb +428 -0
  59. data/lib/omniauth_openid_federation/utils.rb +168 -0
  60. data/lib/omniauth_openid_federation/validators.rb +126 -0
  61. data/lib/omniauth_openid_federation/version.rb +3 -0
  62. data/lib/omniauth_openid_federation.rb +99 -0
  63. data/lib/tasks/omniauth_openid_federation.rake +376 -0
  64. data/sig/federation.rbs +218 -0
  65. data/sig/jwks.rbs +63 -0
  66. data/sig/omniauth_openid_federation.rbs +254 -0
  67. data/sig/strategy.rbs +60 -0
  68. metadata +359 -0
data/SECURITY.md ADDED
@@ -0,0 +1,28 @@
1
+ # SECURITY
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ **Do NOT** open a public GitHub issue for security vulnerabilities.
6
+
7
+ Email security details to: **security@kiskolabs.com**
8
+
9
+ Include: description, steps to reproduce, potential impact, and suggested fix (if available).
10
+
11
+ ### Response Timeline
12
+
13
+ - We will acknowledge receipt of your report
14
+ - We will provide an initial assessment
15
+ - We will keep you informed of our progress and resolution timeline
16
+
17
+ ### Disclosure Policy
18
+
19
+ - We will work with you to understand and resolve the issue
20
+ - We will credit you for the discovery (unless you prefer to remain anonymous)
21
+ - We will publish a security advisory after the vulnerability is patched
22
+ - We will coordinate public disclosure with you
23
+
24
+ ## Automation Security
25
+
26
+ * **Context Isolation:** It is strictly forbidden to include production credentials, API keys, or Personally Identifiable Information (PII) in prompts sent to third-party LLMs or automation services.
27
+
28
+ * **Supply Chain:** All automated dependencies must be verified.
@@ -0,0 +1,399 @@
1
+ # OpenID Federation Integration Testing
2
+
3
+ This directory contains comprehensive mock servers and integration tests for the complete OpenID Federation flow.
4
+
5
+ ## Architecture
6
+
7
+ The integration test setup consists of:
8
+
9
+ 1. **Mock OP Server** (`mock_op_server.rb`) - Simulates an OpenID Provider
10
+ 2. **Mock RP Server** (`mock_rp_server.rb`) - Simulates a Relying Party (Client)
11
+ 3. **Integration Test Flow** (`integration_test_flow.rb`) - Automated tests for the complete flow
12
+
13
+ ## Complete OpenID Federation Flow
14
+
15
+ ### 1. Provider Exposes Entity Statement with JWKS
16
+
17
+ The OP server exposes its entity configuration at:
18
+ ```
19
+ GET /.well-known/openid-federation
20
+ ```
21
+
22
+ Returns a signed JWT containing:
23
+ - Provider metadata (authorization_endpoint, token_endpoint, etc.)
24
+ - JWKS (public keys for signing/encryption)
25
+ - Authority hints (if subordinate to a Trust Anchor)
26
+
27
+ ### 2. Client Exposes Entity Statement with JWKS
28
+
29
+ The RP server exposes its entity configuration at:
30
+ ```
31
+ GET /.well-known/openid-federation
32
+ ```
33
+
34
+ Returns a signed JWT containing:
35
+ - RP metadata (redirect_uris, client_name, etc.)
36
+ - JWKS (public keys for signing request objects)
37
+ - Authority hints (if subordinate to a Trust Anchor)
38
+
39
+ ### 3. Client Fetches Provider Statement with Keys
40
+
41
+ When the RP initiates login:
42
+ 1. RP fetches OP's entity statement from `/.well-known/openid-federation`
43
+ 2. RP validates the entity statement signature
44
+ 3. RP extracts OP's JWKS for future token validation
45
+ 4. RP extracts OP's metadata (endpoints, capabilities)
46
+
47
+ ### 4. Client Sends Login Request
48
+
49
+ RP generates a signed request object (JWT) containing:
50
+ - `client_id`: RP's Entity ID
51
+ - `redirect_uri`: Callback URL
52
+ - `scope`: Requested scopes
53
+ - `state`: CSRF protection
54
+ - `nonce`: Replay protection
55
+ - `aud`: OP's Entity ID
56
+
57
+ The request object is:
58
+ - **Always signed** with RP's private key (RFC 9101 requirement)
59
+ - **Optionally encrypted** if OP requires encryption
60
+
61
+ RP redirects to OP's authorization endpoint:
62
+ ```
63
+ GET /auth?request=<signed_jwt>
64
+ ```
65
+
66
+ ### 5. Provider Fetches Client Statement and Keys
67
+
68
+ When OP receives the authorization request:
69
+ 1. OP extracts `client_id` from the request object
70
+ 2. OP fetches RP's entity statement from `/.well-known/openid-federation`
71
+ 3. OP validates RP's entity statement signature
72
+ 4. OP extracts RP's JWKS from the entity statement
73
+ 5. OP validates the request object signature using RP's public key
74
+ 6. OP resolves RP's trust chain (if trust anchors configured)
75
+ 7. OP applies metadata policies from trust chain
76
+ 8. OP validates `redirect_uri` against RP's allowed redirect URIs
77
+
78
+ ### 6. Exchange and Authenticated Login
79
+
80
+ After user authorization:
81
+ 1. OP redirects back to RP with authorization code
82
+ 2. RP exchanges code for tokens at OP's token endpoint
83
+ 3. OP returns ID token (signed with OP's private key)
84
+ 4. RP validates ID token signature using OP's JWKS
85
+ 5. RP validates ID token claims (iss, aud, exp, nonce)
86
+ 6. User is authenticated
87
+
88
+ ## Error Scenarios Supported
89
+
90
+ The mock servers support error injection via `?error_mode=<mode>` parameter:
91
+
92
+ ### Invalid Statement
93
+ ```
94
+ GET /.well-known/openid-federation?error_mode=invalid_statement
95
+ ```
96
+ Returns malformed JWT to test error handling.
97
+
98
+ ### Wrong Keys
99
+ ```
100
+ GET /.well-known/jwks.json?error_mode=wrong_keys
101
+ GET /.well-known/openid-federation?error_mode=wrong_keys
102
+ ```
103
+ Returns JWKS with keys that don't match the signing key.
104
+
105
+ ### Invalid Request
106
+ ```
107
+ GET /auth?error_mode=invalid_request
108
+ ```
109
+ Rejects request object validation to test error handling.
110
+
111
+ ### Invalid Signature
112
+ ```
113
+ GET /.well-known/signed-jwks.json?error_mode=invalid_signature
114
+ ```
115
+ Returns signed JWKS with invalid signature.
116
+
117
+ ### Expired Statement
118
+ ```
119
+ GET /.well-known/openid-federation?error_mode=expired_statement
120
+ ```
121
+ Returns expired entity statement to test expiration handling.
122
+
123
+ ### Missing Metadata
124
+ ```
125
+ GET /.well-known/openid-federation?error_mode=missing_metadata
126
+ ```
127
+ Returns entity statement without metadata to test validation.
128
+
129
+ ## Usage
130
+
131
+ ### Quick Start (Automated - Recommended)
132
+
133
+ The integration test flow can automatically start servers, generate keys, and run all tests:
134
+
135
+ ```bash
136
+ # Single command - fully automated
137
+ ruby examples/integration_test_flow.rb
138
+ ```
139
+
140
+ This will:
141
+ 1. Create temporary directories for keys and configs
142
+ 2. Generate RSA keys for both OP and RP (using rake task logic)
143
+ 3. Configure and start both servers automatically
144
+ 4. Wait for servers to be ready
145
+ 5. Run all integration tests
146
+ 6. Clean up (kill servers, remove tmp dirs) on exit
147
+
148
+ ### Manual Start (For Debugging)
149
+
150
+ If you want to start servers manually for debugging:
151
+
152
+ **Terminal 1 - OP Server:**
153
+ ```bash
154
+ ruby examples/mock_op_server.rb
155
+ # Server runs on http://localhost:9292
156
+ ```
157
+
158
+ **Terminal 2 - RP Server:**
159
+ ```bash
160
+ ruby examples/mock_rp_server.rb
161
+ # Server runs on http://localhost:9293
162
+ ```
163
+
164
+ **Terminal 3 - Integration Tests:**
165
+ ```bash
166
+ # Disable auto-start to use manually started servers
167
+ AUTO_START_SERVERS=false ruby examples/integration_test_flow.rb
168
+ ```
169
+
170
+ ### Environment Variables
171
+
172
+ The integration test flow supports the following environment variables:
173
+
174
+ ```bash
175
+ # Server URLs
176
+ OP_URL=http://localhost:9292 # OP server URL
177
+ RP_URL=http://localhost:9293 # RP server URL
178
+
179
+ # Server Ports
180
+ OP_PORT=9292 # OP server port
181
+ RP_PORT=9293 # RP server port
182
+
183
+ # Entity IDs
184
+ OP_ENTITY_ID=https://op.example.com # OP entity identifier
185
+ RP_ENTITY_ID=https://rp.example.com # RP entity identifier
186
+
187
+ # Temporary Directory
188
+ TMP_DIR=tmp/integration_test # Directory for keys/configs (default: tmp/integration_test)
189
+
190
+ # Auto-start servers (true/false)
191
+ AUTO_START_SERVERS=true # Auto-start servers (default: true)
192
+
193
+ # Cleanup on exit (true/false)
194
+ CLEANUP_ON_EXIT=true # Clean up tmp dirs on exit (default: true)
195
+
196
+ # Key Type
197
+ KEY_TYPE=separate # 'single' or 'separate' (default: separate)
198
+ ```
199
+
200
+ **Example with custom configuration:**
201
+ ```bash
202
+ KEY_TYPE=separate \
203
+ TMP_DIR=/tmp/my_test \
204
+ ruby examples/integration_test_flow.rb
205
+ ```
206
+
207
+ **Note**: By default, the integration test uses localhost URLs for complete isolation:
208
+ - No DNS resolution required
209
+ - No external network dependencies
210
+ - All communication happens on localhost
211
+ - Entity IDs default to `http://localhost:9292` (OP) and `http://localhost:9293` (RP)
212
+
213
+ This ensures the tests work in any environment without network configuration.
214
+
215
+ ### Manual Testing
216
+
217
+ **1. Test Provider Entity Statement:**
218
+ ```bash
219
+ curl http://localhost:9292/.well-known/openid-federation
220
+ ```
221
+
222
+ **2. Test Client Entity Statement:**
223
+ ```bash
224
+ curl http://localhost:9293/.well-known/openid-federation
225
+ ```
226
+
227
+ **3. Test Login Flow:**
228
+ ```bash
229
+ # Initiate login (will redirect to OP)
230
+ curl -L "http://localhost:9293/login?provider=https://op.example.com"
231
+ ```
232
+
233
+ **4. Test Error Scenarios:**
234
+ ```bash
235
+ # Invalid statement
236
+ curl "http://localhost:9292/.well-known/openid-federation?error_mode=invalid_statement"
237
+
238
+ # Wrong keys
239
+ curl "http://localhost:9292/.well-known/jwks.json?error_mode=wrong_keys"
240
+
241
+ # Expired statement
242
+ curl "http://localhost:9292/.well-known/openid-federation?error_mode=expired_statement"
243
+ ```
244
+
245
+ ## Configuration
246
+
247
+ ### OP Server Configuration
248
+
249
+ Create `examples/config/mock_op.yml`:
250
+
251
+ ```yaml
252
+ entity_id: "https://op.example.com"
253
+ server_host: "localhost:9292"
254
+ signing_key: |
255
+ -----BEGIN RSA PRIVATE KEY-----
256
+ ...
257
+ -----END RSA PRIVATE KEY-----
258
+ encryption_key: | # Optional, defaults to signing_key
259
+ -----BEGIN RSA PRIVATE KEY-----
260
+ ...
261
+ -----END RSA PRIVATE KEY-----
262
+ trust_anchors:
263
+ - entity_id: "https://ta.example.com"
264
+ jwks:
265
+ keys:
266
+ - kty: "RSA"
267
+ kid: "ta-key-1"
268
+ use: "sig"
269
+ n: "..."
270
+ e: "AQAB"
271
+ authority_hints: # Optional, if OP is subordinate
272
+ - "https://ta.example.com"
273
+ op_metadata:
274
+ issuer: "https://op.example.com"
275
+ authorization_endpoint: "https://op.example.com/auth"
276
+ token_endpoint: "https://op.example.com/token"
277
+ request_object_encryption_alg_values_supported: ["RSA-OAEP"]
278
+ request_object_encryption_enc_values_supported: ["A128CBC-HS256"]
279
+ require_request_encryption: false # Set to true to require encryption
280
+ validate_request_objects: true # Set to false to skip validation
281
+ ```
282
+
283
+ ### RP Server Configuration
284
+
285
+ Create `examples/config/mock_rp.yml`:
286
+
287
+ ```yaml
288
+ entity_id: "https://rp.example.com"
289
+ server_host: "localhost:9293"
290
+ signing_key: |
291
+ -----BEGIN RSA PRIVATE KEY-----
292
+ ...
293
+ -----END RSA PRIVATE KEY-----
294
+ encryption_key: | # Optional, for decrypting encrypted ID tokens
295
+ -----BEGIN RSA PRIVATE KEY-----
296
+ ...
297
+ -----END RSA PRIVATE KEY-----
298
+ trust_anchors:
299
+ - entity_id: "https://ta.example.com"
300
+ jwks:
301
+ keys: [...]
302
+ authority_hints: # Optional, if RP is subordinate
303
+ - "https://ta.example.com"
304
+ redirect_uris:
305
+ - "https://rp.example.com/callback"
306
+ ```
307
+
308
+ ## Testing Scenarios
309
+
310
+ ### Happy Path
311
+
312
+ 1. Start both servers
313
+ 2. Run integration tests: `ruby examples/integration_test_flow.rb`
314
+ 3. All tests should pass
315
+
316
+ ### Error Scenarios
317
+
318
+ 1. Test invalid entity statement:
319
+ ```bash
320
+ curl "http://localhost:9292/.well-known/openid-federation?error_mode=invalid_statement"
321
+ ```
322
+
323
+ 2. Test wrong keys:
324
+ ```bash
325
+ curl "http://localhost:9292/.well-known/jwks.json?error_mode=wrong_keys"
326
+ ```
327
+
328
+ 3. Test expired statement:
329
+ ```bash
330
+ curl "http://localhost:9292/.well-known/openid-federation?error_mode=expired_statement"
331
+ ```
332
+
333
+ ### Request Object Validation
334
+
335
+ 1. Test with valid signed request object:
336
+ ```bash
337
+ curl "http://localhost:9293/login?provider=https://op.example.com"
338
+ ```
339
+
340
+ 2. Test with invalid request object:
341
+ ```bash
342
+ curl "http://localhost:9292/auth?error_mode=invalid_request&request=invalid.jwt"
343
+ ```
344
+
345
+ ### Trust Chain Resolution
346
+
347
+ 1. Configure trust anchors in both servers
348
+ 2. Ensure RP has authority_hints pointing to trust anchor
349
+ 3. OP will resolve RP's trust chain during authorization
350
+ 4. OP will apply metadata policies from trust chain
351
+
352
+ ## Security Testing
353
+
354
+ The mock servers support comprehensive security testing:
355
+
356
+ - **Algorithm Confusion**: Test rejection of non-RS256 algorithms
357
+ - **Key Confusion**: Test rejection of wrong keys
358
+ - **Signature Verification**: Test rejection of tampered signatures
359
+ - **Path Traversal**: Test rejection of malicious paths
360
+ - **Replay Attacks**: Test nonce validation
361
+ - **Request Object Validation**: Test signature and encryption validation
362
+ - **Trust Chain Validation**: Test authority hints and metadata policy enforcement
363
+
364
+ ## Troubleshooting
365
+
366
+ ### Server Won't Start
367
+
368
+ - Check if ports 9292 (OP) and 9293 (RP) are available
369
+ - Verify all dependencies are installed: `bundle install`
370
+ - Check configuration files exist and are valid YAML
371
+
372
+ ### Entity Statements Not Validating
373
+
374
+ - Verify keys are correctly configured
375
+ - Check entity IDs match between servers
376
+ - Ensure trust anchors are configured if using trust chains
377
+
378
+ ### Request Objects Rejected
379
+
380
+ - Verify RP's entity statement is accessible
381
+ - Check RP's JWKS contains the signing key
382
+ - Ensure request object is properly signed
383
+ - Verify encryption if required by OP
384
+
385
+ ### Trust Chain Resolution Fails
386
+
387
+ - Verify trust anchors are configured
388
+ - Check authority_hints are correct
389
+ - Ensure all entity statements in chain are valid
390
+ - Verify subordinate statements are available
391
+
392
+ ## Next Steps
393
+
394
+ 1. **Add More Error Scenarios**: Extend error injection modes
395
+ 2. **Performance Testing**: Add load testing scenarios
396
+ 3. **Security Testing**: Add fuzzing and penetration tests
397
+ 4. **Trust Mark Testing**: Add trust mark validation
398
+ 5. **Metadata Policy Testing**: Add comprehensive policy merging tests
399
+
@@ -0,0 +1,243 @@
1
+ # Mock OpenID Provider (OP) Server
2
+
3
+ A standalone Rack/Sinatra application for testing OpenID Federation flows.
4
+
5
+ ## Features
6
+
7
+ - ✅ Entity Configuration endpoint (`/.well-known/openid-federation`)
8
+ - ✅ Fetch Endpoint (`/.well-known/openid-federation/fetch`) for Subordinate Statements
9
+ - ✅ Authorization Endpoint (`/auth`) with trust chain resolution
10
+ - ✅ Token Endpoint (`/token`) with ID Token signing
11
+ - ✅ JWKS endpoints (standard and signed)
12
+ - ✅ UserInfo endpoint (mock)
13
+
14
+ ## Quick Start
15
+
16
+ ### 1. Install Dependencies
17
+
18
+ ```bash
19
+ bundle install
20
+ ```
21
+
22
+ ### 2. Configure
23
+
24
+ Copy the example configuration:
25
+
26
+ ```bash
27
+ cp examples/config/mock_op.yml.example examples/config/mock_op.yml
28
+ ```
29
+
30
+ Edit `examples/config/mock_op.yml` with your settings:
31
+
32
+ ```yaml
33
+ entity_id: "https://op.example.com"
34
+ server_host: "localhost:9292"
35
+ signing_key: |
36
+ -----BEGIN RSA PRIVATE KEY-----
37
+ ...
38
+ -----END RSA PRIVATE KEY-----
39
+ trust_anchors:
40
+ - entity_id: "https://ta.example.com"
41
+ jwks:
42
+ keys: [...]
43
+ ```
44
+
45
+ ### 3. Generate Keys (if needed)
46
+
47
+ ```bash
48
+ # Generate OP signing key
49
+ openssl genrsa -out op-private-key.pem 2048
50
+ openssl rsa -in op-private-key.pem -pubout -out op-public-key.pem
51
+
52
+ # Extract JWKS from public key (or use the rake task)
53
+ rake openid_federation:prepare_client_keys
54
+ ```
55
+
56
+ ### 4. Run Server
57
+
58
+ ```bash
59
+ # Option 1: Direct Ruby execution
60
+ ruby examples/mock_op_server.rb
61
+
62
+ # Option 2: Using Rack
63
+ rackup examples/mock_op_server.ru
64
+
65
+ # Option 3: With specific port
66
+ rackup -p 9292 examples/mock_op_server.ru
67
+ ```
68
+
69
+ ## Configuration Options
70
+
71
+ ### Environment Variables
72
+
73
+ Instead of YAML, you can use environment variables:
74
+
75
+ ```bash
76
+ export OP_ENTITY_ID="https://op.example.com"
77
+ export OP_SERVER_HOST="localhost:9292"
78
+ export OP_SIGNING_KEY="$(cat op-private-key.pem)"
79
+ export OP_TRUST_ANCHORS='[{"entity_id":"https://ta.example.com","jwks":{"keys":[...]}}]'
80
+ export OP_AUTHORITY_HINTS="https://federation.example.com"
81
+ ```
82
+
83
+ ### YAML Configuration
84
+
85
+ See `examples/config/mock_op.yml.example` for full configuration options.
86
+
87
+ ## Testing Scenarios
88
+
89
+ ### 1. Direct Entity Statement (No Trust Chain)
90
+
91
+ ```bash
92
+ # Fetch OP's Entity Configuration
93
+ curl http://localhost:9292/.well-known/openid-federation
94
+
95
+ # Use in RP configuration
96
+ config.omniauth :openid_federation,
97
+ issuer: "https://op.example.com",
98
+ entity_statement_url: "http://localhost:9292/.well-known/openid-federation",
99
+ client_options: { ... }
100
+ ```
101
+
102
+ ### 2. Trust Chain Resolution
103
+
104
+ ```bash
105
+ # Configure trust anchors in mock_op.yml
106
+ trust_anchors:
107
+ - entity_id: "https://ta.example.com"
108
+ jwks: {...}
109
+
110
+ # RP with trust chain
111
+ # The OP will resolve the RP's trust chain automatically
112
+ curl "http://localhost:9292/auth?client_id=https://rp.example.com&redirect_uri=https://rp.example.com/callback"
113
+ ```
114
+
115
+ ### 3. Subordinate Statements
116
+
117
+ ```bash
118
+ # Configure subordinate statements in mock_op.yml
119
+ subordinate_statements:
120
+ "https://rp.example.com":
121
+ metadata: {...}
122
+ metadata_policy: {...}
123
+
124
+ # Fetch Subordinate Statement
125
+ curl "http://localhost:9292/.well-known/openid-federation/fetch?sub=https://rp.example.com"
126
+ ```
127
+
128
+ ## Endpoints
129
+
130
+ | Endpoint | Method | Description |
131
+ |----------|--------|-------------|
132
+ | `/.well-known/openid-federation` | GET | Entity Configuration (JWT) |
133
+ | `/.well-known/openid-federation/fetch` | GET | Fetch Subordinate Statement (requires `sub` parameter) |
134
+ | `/.well-known/jwks.json` | GET | Standard JWKS (JSON) |
135
+ | `/.well-known/signed-jwks.json` | GET | Signed JWKS (JWT) |
136
+ | `/auth` | GET | Authorization Endpoint (requires `client_id`, `redirect_uri`) |
137
+ | `/token` | POST | Token Endpoint (requires `code`, `grant_type=authorization_code`) |
138
+ | `/userinfo` | GET | UserInfo Endpoint (mock data) |
139
+ | `/` | GET | Health check and endpoint list |
140
+
141
+ ## Example Flow
142
+
143
+ ### 1. RP Discovers OP
144
+
145
+ ```bash
146
+ # RP fetches OP's Entity Configuration
147
+ curl http://localhost:9292/.well-known/openid-federation
148
+ ```
149
+
150
+ ### 2. RP Initiates Authentication
151
+
152
+ ```bash
153
+ # RP redirects user to authorization endpoint
154
+ # client_id is the RP's Entity ID (for automatic registration)
155
+ curl "http://localhost:9292/auth?client_id=https://rp.example.com&redirect_uri=https://rp.example.com/callback&state=xyz&nonce=abc"
156
+ ```
157
+
158
+ ### 3. OP Resolves RP's Trust Chain
159
+
160
+ The OP automatically:
161
+ - Resolves RP's trust chain using `TrustChainResolver`
162
+ - Merges metadata policies using `MetadataPolicyMerger`
163
+ - Validates RP's effective metadata
164
+ - Redirects back to RP with authorization code
165
+
166
+ ### 4. RP Exchanges Code for Tokens
167
+
168
+ ```bash
169
+ curl -X POST http://localhost:9292/token \
170
+ -d "grant_type=authorization_code" \
171
+ -d "code=<authorization_code>" \
172
+ -d "redirect_uri=https://rp.example.com/callback" \
173
+ -d "client_id=https://rp.example.com"
174
+ ```
175
+
176
+ ### 5. RP Validates ID Token
177
+
178
+ The RP validates the ID Token using the OP's JWKS from the effective metadata.
179
+
180
+ ## Integration with Real RP
181
+
182
+ To test with a real RP application:
183
+
184
+ ```ruby
185
+ # In RP's config/initializers/devise.rb
186
+ config.omniauth :openid_federation,
187
+ issuer: "http://localhost:9292",
188
+ entity_statement_url: "http://localhost:9292/.well-known/openid-federation",
189
+ trust_anchors: [
190
+ {
191
+ entity_id: "https://ta.example.com",
192
+ jwks: trust_anchor_jwks
193
+ }
194
+ ],
195
+ client_options: {
196
+ identifier: "https://rp.example.com", # RP's Entity ID
197
+ redirect_uri: "http://localhost:3000/users/auth/openid_federation/callback",
198
+ private_key: rp_private_key
199
+ }
200
+ ```
201
+
202
+ ## Limitations
203
+
204
+ This is a **mock server for testing only**:
205
+
206
+ - ⚠️ No real user authentication (always returns mock user)
207
+ - ⚠️ Authorization codes stored in memory (lost on restart)
208
+ - ⚠️ No database persistence
209
+ - ⚠️ No production security hardening
210
+ - ⚠️ ID Tokens contain mock user data
211
+
212
+ ## Production Considerations
213
+
214
+ For production use, you would need:
215
+
216
+ - Real user authentication system
217
+ - Database for authorization codes and tokens
218
+ - Proper session management
219
+ - Security hardening (rate limiting, CSRF protection, etc.)
220
+ - Real user data in ID Tokens
221
+ - Proper error handling and logging
222
+
223
+ ## Troubleshooting
224
+
225
+ **"Federation endpoint not configured"**
226
+ - Ensure `signing_key` is provided in config
227
+ - Check that `entity_id` is set
228
+
229
+ **"Trust chain resolution failed"**
230
+ - Verify `trust_anchors` are correctly configured
231
+ - Ensure trust anchor JWKS are valid
232
+ - Check that RP's Entity ID is resolvable
233
+
234
+ **"Subordinate Statement not found"**
235
+ - Configure `subordinate_statements` in `mock_op.yml`
236
+ - Ensure subject Entity ID matches exactly
237
+
238
+ ## See Also
239
+
240
+ - [OpenID Federation 1.0 Specification](https://openid.net/specs/openid-federation-1_0.html)
241
+ - [Main README](../README.md)
242
+ - [Federation Endpoint Documentation](../README.md#publishing-federation-endpoint)
243
+
@@ -0,0 +1,37 @@
1
+ # Example OmniAuth callback controller for Devise
2
+ # Copy this to app/controllers/users/omniauth_callbacks_controller.rb
3
+
4
+ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
5
+ # Skip Rails CSRF protection for OAuth callbacks
6
+ # OAuth callbacks from external providers cannot include Rails CSRF tokens
7
+ # CSRF protection is handled by OAuth state parameter validation in the strategy
8
+ skip_before_action :verify_authenticity_token, only: [:openid_federation, :failure]
9
+ skip_before_action :authenticate_user!, only: [:openid_federation, :failure]
10
+
11
+ def openid_federation
12
+ auth = request.env["omniauth.auth"]
13
+
14
+ @user = User.find_or_create_from_omniauth(auth)
15
+
16
+ if @user&.persisted?
17
+ sign_in_and_redirect @user, event: :authentication
18
+ else
19
+ redirect_to root_path, alert: "Authentication failed"
20
+ end
21
+ end
22
+
23
+ def failure
24
+ error_type = request.env["omniauth.error.type"] || :unknown
25
+ error_message = request.env["omniauth.error"]&.message || "Authentication failed"
26
+
27
+ Rails.logger.error({
28
+ message: "OmniAuth authentication failure",
29
+ error_type: error_type,
30
+ error_message: error_message,
31
+ strategy: request.env["omniauth.strategy"]&.name
32
+ })
33
+
34
+ redirect_to root_path, alert: "Authentication failed: #{error_type}"
35
+ end
36
+ end
37
+