omniauth_openid_federation 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -1
- data/README.md +120 -14
- data/app/controllers/omniauth_openid_federation/federation_controller.rb +3 -3
- data/config/polyrun_coverage.yml +15 -0
- data/config/polyrun_spec_quality.yml +29 -0
- data/config/routes.rb +20 -10
- data/examples/README_INTEGRATION_TESTING.md +3 -0
- data/examples/integration_test_flow.rb +10 -8
- data/examples/jobs/federation_cache_refresh_job.rb.example +7 -7
- data/examples/jobs/federation_files_generation_job.rb.example +3 -2
- data/examples/mock_op_server.rb +4 -5
- data/examples/mock_rp_server.rb +3 -3
- data/examples/standalone_multiple_endpoints_example.rb +494 -0
- data/lib/omniauth_openid_federation/access_token.rb +91 -450
- data/lib/omniauth_openid_federation/cache.rb +16 -0
- data/lib/omniauth_openid_federation/cache_adapter.rb +6 -3
- data/lib/omniauth_openid_federation/constants.rb +3 -0
- data/lib/omniauth_openid_federation/entity_statement_reader.rb +39 -14
- data/lib/omniauth_openid_federation/federation/entity_statement.rb +0 -4
- data/lib/omniauth_openid_federation/federation/entity_statement_builder.rb +7 -14
- data/lib/omniauth_openid_federation/federation/entity_statement_helper.rb +40 -11
- data/lib/omniauth_openid_federation/federation/entity_statement_validator.rb +8 -91
- data/lib/omniauth_openid_federation/federation/signed_jwks.rb +0 -1
- data/lib/omniauth_openid_federation/federation/trust_chain_resolver.rb +54 -21
- data/lib/omniauth_openid_federation/federation_endpoint.rb +40 -172
- data/lib/omniauth_openid_federation/http_client.rb +145 -32
- data/lib/omniauth_openid_federation/http_errors.rb +14 -0
- data/lib/omniauth_openid_federation/id_token.rb +19 -0
- data/lib/omniauth_openid_federation/jwe.rb +26 -0
- data/lib/omniauth_openid_federation/jwks/decode.rb +0 -13
- data/lib/omniauth_openid_federation/jwks/fetch.rb +16 -39
- data/lib/omniauth_openid_federation/jwks/rotate.rb +45 -20
- data/lib/omniauth_openid_federation/jws.rb +3 -11
- data/lib/omniauth_openid_federation/jwt_response_decoder.rb +290 -0
- data/lib/omniauth_openid_federation/oidc_client.rb +101 -0
- data/lib/omniauth_openid_federation/rack.rb +2 -0
- data/lib/omniauth_openid_federation/rack_endpoint.rb +21 -9
- data/lib/omniauth_openid_federation/rate_limiter.rb +10 -12
- data/lib/omniauth_openid_federation/secure_compare.rb +15 -0
- data/lib/omniauth_openid_federation/strategy/authorization_request.rb +220 -0
- data/lib/omniauth_openid_federation/strategy/callback_handling.rb +135 -0
- data/lib/omniauth_openid_federation/strategy/client_construction.rb +101 -0
- data/lib/omniauth_openid_federation/strategy/client_entity_statement.rb +211 -0
- data/lib/omniauth_openid_federation/strategy/endpoint_resolution.rb +433 -0
- data/lib/omniauth_openid_federation/strategy/failure_handling.rb +75 -0
- data/lib/omniauth_openid_federation/strategy/id_token_decoding.rb +268 -0
- data/lib/omniauth_openid_federation/strategy/jwks_resolution.rb +268 -0
- data/lib/omniauth_openid_federation/strategy/provider_entity_statement.rb +134 -0
- data/lib/omniauth_openid_federation/strategy/userinfo_decoding.rb +61 -0
- data/lib/omniauth_openid_federation/strategy.rb +27 -1910
- data/lib/omniauth_openid_federation/tasks/authentication_flow_tester.rb +237 -0
- data/lib/omniauth_openid_federation/tasks/callback_processor.rb +235 -0
- data/lib/omniauth_openid_federation/tasks/client_keys_preparer.rb +96 -0
- data/lib/omniauth_openid_federation/tasks/local_endpoint_tester.rb +189 -0
- data/lib/omniauth_openid_federation/tasks/path_resolver.rb +20 -0
- data/lib/omniauth_openid_federation/tasks_helper.rb +28 -790
- data/lib/omniauth_openid_federation/time_helpers.rb +67 -0
- data/lib/omniauth_openid_federation/user_info.rb +15 -0
- data/lib/omniauth_openid_federation/utils.rb +14 -9
- data/lib/omniauth_openid_federation/validators.rb +55 -44
- data/lib/omniauth_openid_federation/version.rb +1 -1
- data/lib/omniauth_openid_federation.rb +10 -3
- data/lib/tasks/omniauth_openid_federation.rake +5 -5
- data/sig/omniauth_openid_federation.rbs +8 -1
- data/sig/strategy.rbs +6 -6
- metadata +252 -43
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Standalone Multiple Auth Endpoints Example with Sinatra
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates how to use omniauth_openid_federation
|
|
7
|
+
# in a standalone Sinatra application with multiple authentication
|
|
8
|
+
# endpoints that differentiate entrance points (portal, admin, mobile).
|
|
9
|
+
#
|
|
10
|
+
# Features:
|
|
11
|
+
# - Multiple auth endpoints with different parameters
|
|
12
|
+
# - Entrance point tracking
|
|
13
|
+
# - Different redirects based on entrance point after callback
|
|
14
|
+
# - No Rails/Devise dependency - pure Sinatra
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# ruby examples/standalone_multiple_endpoints_example.rb
|
|
18
|
+
#
|
|
19
|
+
# Access:
|
|
20
|
+
# http://localhost:9294/ - Home page
|
|
21
|
+
# http://localhost:9294/auth/openid_federation_portal - Portal login
|
|
22
|
+
# http://localhost:9294/auth/openid_federation_admin - Admin login
|
|
23
|
+
# http://localhost:9294/auth/openid_federation_mobile - Mobile login
|
|
24
|
+
|
|
25
|
+
require "bundler/setup"
|
|
26
|
+
require "sinatra"
|
|
27
|
+
require "omniauth"
|
|
28
|
+
require "omniauth_openid_federation"
|
|
29
|
+
require "json"
|
|
30
|
+
require "securerandom"
|
|
31
|
+
|
|
32
|
+
# Add the gem to the load path
|
|
33
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
34
|
+
|
|
35
|
+
# Configuration
|
|
36
|
+
OP_ENTITY_STATEMENT_URL = ENV["OP_ENTITY_STATEMENT_URL"] || "https://provider.example.com/.well-known/openid-federation"
|
|
37
|
+
OP_ENTITY_STATEMENT_FINGERPRINT = ENV["OP_ENTITY_STATEMENT_FINGERPRINT"] # Optional: for verification
|
|
38
|
+
CLIENT_ID = ENV["OPENID_CLIENT_ID"] || "your-client-id"
|
|
39
|
+
REDIRECT_URI_BASE = ENV["OPENID_REDIRECT_URI_BASE"] || "http://localhost:9294"
|
|
40
|
+
|
|
41
|
+
# Load private key (auto-provisioned from environment or auto-generated)
|
|
42
|
+
def load_private_key
|
|
43
|
+
if ENV["OPENID_CLIENT_PRIVATE_KEY_BASE64"]
|
|
44
|
+
require "base64"
|
|
45
|
+
require "openssl"
|
|
46
|
+
OpenSSL::PKey::RSA.new(Base64.decode64(ENV["OPENID_CLIENT_PRIVATE_KEY_BASE64"]))
|
|
47
|
+
else
|
|
48
|
+
# Auto-generate keys if not provided (for development/testing)
|
|
49
|
+
# In production, always provide OPENID_CLIENT_PRIVATE_KEY_BASE64
|
|
50
|
+
require "openssl"
|
|
51
|
+
@auto_generated_key ||= OpenSSL::PKey::RSA.new(2048)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Fetch and refresh OP entity statement (called on each request)
|
|
56
|
+
def fetch_op_entity_statement
|
|
57
|
+
return nil unless OP_ENTITY_STATEMENT_URL
|
|
58
|
+
|
|
59
|
+
begin
|
|
60
|
+
OmniauthOpenidFederation::Federation::EntityStatement.fetch!(
|
|
61
|
+
OP_ENTITY_STATEMENT_URL,
|
|
62
|
+
fingerprint: OP_ENTITY_STATEMENT_FINGERPRINT,
|
|
63
|
+
timeout: 10
|
|
64
|
+
)
|
|
65
|
+
rescue => e
|
|
66
|
+
puts "Warning: Failed to fetch OP entity statement: #{e.message}"
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Configure session
|
|
72
|
+
set :session_secret, ENV.fetch("SESSION_SECRET") { SecureRandom.hex(32) }
|
|
73
|
+
enable :sessions
|
|
74
|
+
|
|
75
|
+
# Fetch/refresh OP entity statement on each request
|
|
76
|
+
before do
|
|
77
|
+
# Fetch OP entity statement to ensure we have latest metadata and keys
|
|
78
|
+
# This happens automatically in the strategy, but we can also do it here
|
|
79
|
+
# to warm up the cache and verify connectivity
|
|
80
|
+
fetch_op_entity_statement if request.path.start_with?("/auth/")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Configure OmniAuth
|
|
84
|
+
OmniAuth.config.allowed_request_methods = [:get, :post]
|
|
85
|
+
OmniAuth.config.silence_get_warning = true
|
|
86
|
+
|
|
87
|
+
# Configure CSRF protection for request phase
|
|
88
|
+
OmniAuth.config.request_validation_phase = lambda do |env|
|
|
89
|
+
request = Rack::Request.new(env)
|
|
90
|
+
return true if request.path.end_with?("/callback")
|
|
91
|
+
|
|
92
|
+
session = env["rack.session"] || {}
|
|
93
|
+
token = request.params["authenticity_token"] || request.get_header("X-CSRF-Token")
|
|
94
|
+
expected_token = session[:_csrf_token] || session["_csrf_token"]
|
|
95
|
+
|
|
96
|
+
if token && expected_token
|
|
97
|
+
Rack::Utils.secure_compare(token.to_s, expected_token.to_s)
|
|
98
|
+
else
|
|
99
|
+
false
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Configure multiple OpenID Federation strategies
|
|
104
|
+
use OmniAuth::Builder do
|
|
105
|
+
# Strategy 1: Portal entrance point
|
|
106
|
+
provider(:openid_federation_portal,
|
|
107
|
+
strategy_class: OmniAuth::Strategies::OpenIDFederation,
|
|
108
|
+
name: :openid_federation_portal,
|
|
109
|
+
scope: [:openid, :profile, :email],
|
|
110
|
+
response_type: "code",
|
|
111
|
+
discovery: true,
|
|
112
|
+
client_auth_method: :jwt_bearer,
|
|
113
|
+
client_signing_alg: :RS256,
|
|
114
|
+
entity_statement_url: OP_ENTITY_STATEMENT_URL,
|
|
115
|
+
entity_statement_fingerprint: OP_ENTITY_STATEMENT_FINGERPRINT,
|
|
116
|
+
always_encrypt_request_object: false,
|
|
117
|
+
request_object_params: ["entrance_point", "portal_id", "acr_values"],
|
|
118
|
+
prepare_request_object_params: proc do |params|
|
|
119
|
+
# Always set entrance_point for portal
|
|
120
|
+
params["entrance_point"] = "portal"
|
|
121
|
+
|
|
122
|
+
# Combine config acr_values with form acr_values
|
|
123
|
+
form_acr_values = params["acr_values"]&.to_s&.strip
|
|
124
|
+
config_acr_values = (ENV["OPENID_ACR_VALUES_PORTAL"] || "").to_s.strip
|
|
125
|
+
|
|
126
|
+
if !config_acr_values.empty? && !form_acr_values.to_s.empty?
|
|
127
|
+
params["acr_values"] = "#{config_acr_values} #{form_acr_values}".strip
|
|
128
|
+
elsif !config_acr_values.empty?
|
|
129
|
+
params["acr_values"] = config_acr_values
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Add portal-specific parameter if provided
|
|
133
|
+
portal_id = ENV["OPENID_PORTAL_ID"]
|
|
134
|
+
params["portal_id"] = portal_id if portal_id && !portal_id.empty?
|
|
135
|
+
|
|
136
|
+
params
|
|
137
|
+
end,
|
|
138
|
+
client_options: {
|
|
139
|
+
identifier: CLIENT_ID,
|
|
140
|
+
redirect_uri: "#{REDIRECT_URI_BASE}/auth/openid_federation_portal/callback",
|
|
141
|
+
private_key: load_private_key
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
# Strategy 2: Admin entrance point
|
|
145
|
+
provider(:openid_federation_admin,
|
|
146
|
+
strategy_class: OmniAuth::Strategies::OpenIDFederation,
|
|
147
|
+
name: :openid_federation_admin,
|
|
148
|
+
scope: [:openid, :profile, :email],
|
|
149
|
+
response_type: "code",
|
|
150
|
+
discovery: true,
|
|
151
|
+
client_auth_method: :jwt_bearer,
|
|
152
|
+
client_signing_alg: :RS256,
|
|
153
|
+
entity_statement_url: OP_ENTITY_STATEMENT_URL,
|
|
154
|
+
entity_statement_fingerprint: OP_ENTITY_STATEMENT_FINGERPRINT,
|
|
155
|
+
always_encrypt_request_object: false,
|
|
156
|
+
request_object_params: ["entrance_point", "acr_values", "prompt"],
|
|
157
|
+
prepare_request_object_params: proc do |params|
|
|
158
|
+
# Always set entrance_point for admin
|
|
159
|
+
params["entrance_point"] = "admin"
|
|
160
|
+
|
|
161
|
+
# Admin requires higher ACR level
|
|
162
|
+
admin_acr = ENV["OPENID_ACR_VALUES_ADMIN"] || "urn:mace:incommon:iap:silver"
|
|
163
|
+
params["acr_values"] = admin_acr
|
|
164
|
+
|
|
165
|
+
# Force re-authentication for admin
|
|
166
|
+
params["prompt"] = "login consent"
|
|
167
|
+
|
|
168
|
+
params
|
|
169
|
+
end,
|
|
170
|
+
client_options: {
|
|
171
|
+
identifier: CLIENT_ID,
|
|
172
|
+
redirect_uri: "#{REDIRECT_URI_BASE}/auth/openid_federation_admin/callback",
|
|
173
|
+
private_key: load_private_key
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
# Strategy 3: Mobile app entrance point
|
|
177
|
+
provider(:openid_federation_mobile,
|
|
178
|
+
strategy_class: OmniAuth::Strategies::OpenIDFederation,
|
|
179
|
+
name: :openid_federation_mobile,
|
|
180
|
+
scope: [:openid, :profile, :email],
|
|
181
|
+
response_type: "code",
|
|
182
|
+
discovery: true,
|
|
183
|
+
client_auth_method: :jwt_bearer,
|
|
184
|
+
client_signing_alg: :RS256,
|
|
185
|
+
entity_statement_url: OP_ENTITY_STATEMENT_URL,
|
|
186
|
+
entity_statement_fingerprint: OP_ENTITY_STATEMENT_FINGERPRINT,
|
|
187
|
+
always_encrypt_request_object: false,
|
|
188
|
+
request_object_params: ["entrance_point", "device_id", "app_version"],
|
|
189
|
+
prepare_request_object_params: proc do |params|
|
|
190
|
+
# Always set entrance_point for mobile
|
|
191
|
+
params["entrance_point"] = "mobile"
|
|
192
|
+
|
|
193
|
+
# Add device-specific parameters if provided
|
|
194
|
+
params["device_id"] = params["device_id"] if params["device_id"] && !params["device_id"].to_s.empty?
|
|
195
|
+
params["app_version"] = params["app_version"] if params["app_version"] && !params["app_version"].to_s.empty?
|
|
196
|
+
|
|
197
|
+
params
|
|
198
|
+
end,
|
|
199
|
+
client_options: {
|
|
200
|
+
identifier: CLIENT_ID,
|
|
201
|
+
redirect_uri: "#{REDIRECT_URI_BASE}/auth/openid_federation_mobile/callback",
|
|
202
|
+
private_key: load_private_key
|
|
203
|
+
})
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Helper to generate CSRF token
|
|
207
|
+
def csrf_token
|
|
208
|
+
session[:_csrf_token] ||= SecureRandom.hex(32)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Helper to get entrance point redirect URL
|
|
212
|
+
def redirect_for_entrance_point(entrance_point, user_info = {})
|
|
213
|
+
case entrance_point
|
|
214
|
+
when "portal"
|
|
215
|
+
"/portal/dashboard"
|
|
216
|
+
when "admin"
|
|
217
|
+
"/admin/dashboard"
|
|
218
|
+
when "mobile"
|
|
219
|
+
"/mobile/home"
|
|
220
|
+
else
|
|
221
|
+
"/dashboard"
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Home page
|
|
226
|
+
get "/" do
|
|
227
|
+
erb :index
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Portal dashboard (example)
|
|
231
|
+
get "/portal/dashboard" do
|
|
232
|
+
content_type :json
|
|
233
|
+
{
|
|
234
|
+
status: "success",
|
|
235
|
+
entrance_point: "portal",
|
|
236
|
+
message: "Welcome to Portal Dashboard",
|
|
237
|
+
user: params[:user]
|
|
238
|
+
}.to_json
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Admin dashboard (example)
|
|
242
|
+
get "/admin/dashboard" do
|
|
243
|
+
content_type :json
|
|
244
|
+
{
|
|
245
|
+
status: "success",
|
|
246
|
+
entrance_point: "admin",
|
|
247
|
+
message: "Welcome to Admin Dashboard",
|
|
248
|
+
user: params[:user]
|
|
249
|
+
}.to_json
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Mobile home (example)
|
|
253
|
+
get "/mobile/home" do
|
|
254
|
+
content_type :json
|
|
255
|
+
{
|
|
256
|
+
status: "success",
|
|
257
|
+
entrance_point: "mobile",
|
|
258
|
+
message: "Welcome to Mobile App",
|
|
259
|
+
user: params[:user]
|
|
260
|
+
}.to_json
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Default dashboard (fallback)
|
|
264
|
+
get "/dashboard" do
|
|
265
|
+
content_type :json
|
|
266
|
+
{
|
|
267
|
+
status: "success",
|
|
268
|
+
message: "Welcome",
|
|
269
|
+
user: params[:user]
|
|
270
|
+
}.to_json
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Callback handlers for each entrance point
|
|
274
|
+
get "/auth/:provider/callback" do
|
|
275
|
+
auth = request.env["omniauth.auth"]
|
|
276
|
+
|
|
277
|
+
if auth
|
|
278
|
+
# Extract entrance point from strategy name or raw_info
|
|
279
|
+
provider_name = params[:provider]
|
|
280
|
+
entrance_point = case provider_name
|
|
281
|
+
when "openid_federation_portal"
|
|
282
|
+
"portal"
|
|
283
|
+
when "openid_federation_admin"
|
|
284
|
+
"admin"
|
|
285
|
+
when "openid_federation_mobile"
|
|
286
|
+
"mobile"
|
|
287
|
+
else
|
|
288
|
+
# Try to get from raw_info if provider returns it
|
|
289
|
+
raw_info = auth.extra[:raw_info] || {}
|
|
290
|
+
raw_info["entrance_point"] || "unknown"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Extract user information
|
|
294
|
+
user_info = {
|
|
295
|
+
provider: auth.provider,
|
|
296
|
+
uid: auth.uid,
|
|
297
|
+
email: auth.info.email,
|
|
298
|
+
name: auth.info.name,
|
|
299
|
+
first_name: auth.info.first_name,
|
|
300
|
+
last_name: auth.info.last_name,
|
|
301
|
+
raw_info: auth.extra[:raw_info]
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# Store user info in session
|
|
305
|
+
session[:user] = user_info
|
|
306
|
+
session[:entrance_point] = entrance_point
|
|
307
|
+
|
|
308
|
+
# Extract additional parameters from raw_info if provider returns them
|
|
309
|
+
raw_info = auth.extra[:raw_info] || {}
|
|
310
|
+
session[:portal_id] = raw_info["portal_id"] if raw_info["portal_id"]
|
|
311
|
+
session[:device_id] = raw_info["device_id"] if raw_info["device_id"]
|
|
312
|
+
|
|
313
|
+
# Redirect based on entrance point
|
|
314
|
+
redirect redirect_for_entrance_point(entrance_point, user_info)
|
|
315
|
+
else
|
|
316
|
+
redirect "/auth/failure"
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Failure handler
|
|
321
|
+
get "/auth/failure" do
|
|
322
|
+
error_type = request.env["omniauth.error.type"]
|
|
323
|
+
error_message = request.env["omniauth.error"]&.message || "Authentication failed"
|
|
324
|
+
|
|
325
|
+
content_type :json
|
|
326
|
+
{
|
|
327
|
+
status: "error",
|
|
328
|
+
error_type: error_type,
|
|
329
|
+
message: error_message
|
|
330
|
+
}.to_json
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# User info endpoint (example)
|
|
334
|
+
get "/user/info" do
|
|
335
|
+
if session[:user]
|
|
336
|
+
content_type :json
|
|
337
|
+
{
|
|
338
|
+
status: "success",
|
|
339
|
+
user: session[:user],
|
|
340
|
+
entrance_point: session[:entrance_point],
|
|
341
|
+
portal_id: session[:portal_id],
|
|
342
|
+
device_id: session[:device_id]
|
|
343
|
+
}.to_json
|
|
344
|
+
else
|
|
345
|
+
status 401
|
|
346
|
+
content_type :json
|
|
347
|
+
{ status: "error", message: "Not authenticated" }.to_json
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Run the server
|
|
352
|
+
if __FILE__ == $0
|
|
353
|
+
require "sinatra"
|
|
354
|
+
|
|
355
|
+
puts "Starting standalone multiple endpoints example server..."
|
|
356
|
+
puts "Server: http://localhost:9294"
|
|
357
|
+
puts ""
|
|
358
|
+
puts "Endpoints:"
|
|
359
|
+
puts " GET / - Home page with login links"
|
|
360
|
+
puts " POST /auth/openid_federation_portal - Portal login"
|
|
361
|
+
puts " POST /auth/openid_federation_admin - Admin login"
|
|
362
|
+
puts " POST /auth/openid_federation_mobile - Mobile login"
|
|
363
|
+
puts " GET /auth/:provider/callback - OAuth callback (redirects based on entrance point)"
|
|
364
|
+
puts " GET /auth/failure - Authentication failure handler"
|
|
365
|
+
puts " GET /user/info - Current user info (JSON)"
|
|
366
|
+
puts ""
|
|
367
|
+
puts "Environment variables:"
|
|
368
|
+
puts " OP_ENTITY_STATEMENT_URL - Provider entity statement URL (required)"
|
|
369
|
+
puts " OP_ENTITY_STATEMENT_FINGERPRINT - Expected fingerprint for verification (optional)"
|
|
370
|
+
puts " OPENID_CLIENT_ID - Your client ID (required)"
|
|
371
|
+
puts " OPENID_REDIRECT_URI_BASE - Base URL for redirect URIs (default: http://localhost:9294)"
|
|
372
|
+
puts " OPENID_CLIENT_PRIVATE_KEY_BASE64 - Base64-encoded private key (auto-generated if not set)"
|
|
373
|
+
puts " OPENID_ACR_VALUES_PORTAL - ACR values for portal (optional)"
|
|
374
|
+
puts " OPENID_ACR_VALUES_ADMIN - ACR values for admin (optional)"
|
|
375
|
+
puts " OPENID_PORTAL_ID - Portal ID (optional)"
|
|
376
|
+
puts " SESSION_SECRET - Session secret (auto-generated if not set)"
|
|
377
|
+
puts ""
|
|
378
|
+
puts "Note: OP entity statement and keys are automatically fetched/refreshed on each request."
|
|
379
|
+
puts ""
|
|
380
|
+
|
|
381
|
+
set :port, ENV.fetch("PORT", 9294).to_i
|
|
382
|
+
set :bind, ENV.fetch("HOST", "localhost")
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# View templates
|
|
386
|
+
__END__
|
|
387
|
+
|
|
388
|
+
@@index
|
|
389
|
+
<!DOCTYPE html>
|
|
390
|
+
<html>
|
|
391
|
+
<head>
|
|
392
|
+
<title>Multiple Auth Endpoints Example</title>
|
|
393
|
+
<style>
|
|
394
|
+
body {
|
|
395
|
+
font-family: Arial, sans-serif;
|
|
396
|
+
max-width: 800px;
|
|
397
|
+
margin: 50px auto;
|
|
398
|
+
padding: 20px;
|
|
399
|
+
}
|
|
400
|
+
h1 {
|
|
401
|
+
color: #333;
|
|
402
|
+
}
|
|
403
|
+
.login-section {
|
|
404
|
+
margin: 30px 0;
|
|
405
|
+
padding: 20px;
|
|
406
|
+
border: 1px solid #ddd;
|
|
407
|
+
border-radius: 5px;
|
|
408
|
+
}
|
|
409
|
+
.login-section h2 {
|
|
410
|
+
margin-top: 0;
|
|
411
|
+
color: #555;
|
|
412
|
+
}
|
|
413
|
+
.login-button {
|
|
414
|
+
display: inline-block;
|
|
415
|
+
padding: 10px 20px;
|
|
416
|
+
background-color: #007bff;
|
|
417
|
+
color: white;
|
|
418
|
+
text-decoration: none;
|
|
419
|
+
border-radius: 4px;
|
|
420
|
+
margin: 5px 0;
|
|
421
|
+
}
|
|
422
|
+
.login-button:hover {
|
|
423
|
+
background-color: #0056b3;
|
|
424
|
+
}
|
|
425
|
+
.info {
|
|
426
|
+
background-color: #f0f0f0;
|
|
427
|
+
padding: 15px;
|
|
428
|
+
border-radius: 4px;
|
|
429
|
+
margin: 20px 0;
|
|
430
|
+
}
|
|
431
|
+
</style>
|
|
432
|
+
</head>
|
|
433
|
+
<body>
|
|
434
|
+
<h1>OpenID Federation - Multiple Auth Endpoints Example</h1>
|
|
435
|
+
|
|
436
|
+
<div class="info">
|
|
437
|
+
<p>This example demonstrates multiple authentication endpoints with different entrance points.</p>
|
|
438
|
+
<p>Each endpoint sends different parameters and redirects to different destinations after authentication.</p>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<div class="login-section">
|
|
442
|
+
<h2>Portal Login</h2>
|
|
443
|
+
<p>Login via portal entrance point. Will redirect to <code>/portal/dashboard</code> after authentication.</p>
|
|
444
|
+
<form method="post" action="/auth/openid_federation_portal">
|
|
445
|
+
<input type="hidden" name="authenticity_token" value="<%= csrf_token %>">
|
|
446
|
+
<input type="hidden" name="portal_id" value="main_portal">
|
|
447
|
+
<input type="hidden" name="acr_values" value="urn:mace:incommon:iap:bronze">
|
|
448
|
+
<button type="submit" class="login-button">Login via Portal</button>
|
|
449
|
+
</form>
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<div class="login-section">
|
|
453
|
+
<h2>Admin Login</h2>
|
|
454
|
+
<p>Login via admin entrance point. Will redirect to <code>/admin/dashboard</code> after authentication.</p>
|
|
455
|
+
<p><strong>Note:</strong> Admin login requires higher ACR level and forces re-authentication.</p>
|
|
456
|
+
<form method="post" action="/auth/openid_federation_admin">
|
|
457
|
+
<input type="hidden" name="authenticity_token" value="<%= csrf_token %>">
|
|
458
|
+
<button type="submit" class="login-button">Login as Admin</button>
|
|
459
|
+
</form>
|
|
460
|
+
</div>
|
|
461
|
+
|
|
462
|
+
<div class="login-section">
|
|
463
|
+
<h2>Mobile Login</h2>
|
|
464
|
+
<p>Login via mobile entrance point. Will redirect to <code>/mobile/home</code> after authentication.</p>
|
|
465
|
+
<form method="post" action="/auth/openid_federation_mobile">
|
|
466
|
+
<input type="hidden" name="authenticity_token" value="<%= csrf_token %>">
|
|
467
|
+
<input type="hidden" name="device_id" value="device_123">
|
|
468
|
+
<input type="hidden" name="app_version" value="1.0.0">
|
|
469
|
+
<button type="submit" class="login-button">Login via Mobile</button>
|
|
470
|
+
</form>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
473
|
+
<div class="info">
|
|
474
|
+
<h3>How it works:</h3>
|
|
475
|
+
<ul>
|
|
476
|
+
<li>Each endpoint has a different <code>name</code> (portal, admin, mobile)</li>
|
|
477
|
+
<li>Each endpoint sends different custom parameters in the signed request object</li>
|
|
478
|
+
<li>The <code>prepare_request_object_params</code> proc customizes parameters per endpoint</li>
|
|
479
|
+
<li>After callback, users are redirected based on their entrance point</li>
|
|
480
|
+
<li>User info and entrance point are stored in session</li>
|
|
481
|
+
</ul>
|
|
482
|
+
</div>
|
|
483
|
+
|
|
484
|
+
<% if session[:user] %>
|
|
485
|
+
<div class="info">
|
|
486
|
+
<h3>Current Session:</h3>
|
|
487
|
+
<p><strong>Entrance Point:</strong> <%= session[:entrance_point] %></p>
|
|
488
|
+
<p><strong>User:</strong> <%= session[:user][:email] || session[:user][:uid] %></p>
|
|
489
|
+
<p><a href="/user/info">View full user info (JSON)</a></p>
|
|
490
|
+
</div>
|
|
491
|
+
<% end %>
|
|
492
|
+
</body>
|
|
493
|
+
</html>
|
|
494
|
+
|