omniauth_strong_auth_oidc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +6 -0
- data/CONTRIBUTING.md +9 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +313 -0
- data/Rakefile +12 -0
- data/lib/generators/omniauth_strong_auth_oidc/install_generator.rb +73 -0
- data/lib/generators/omniauth_strong_auth_oidc/templates/relying_party_entity_statement_controller.rb.tt +61 -0
- data/lib/omniauth/strategies/strong_auth_oidc.rb +210 -0
- data/lib/omniauth_strong_auth_oidc/entity_statement.rb +22 -0
- data/lib/omniauth_strong_auth_oidc/entity_statement_fetcher/base.rb +37 -0
- data/lib/omniauth_strong_auth_oidc/entity_statement_fetcher/federation_url_fetcher.rb +29 -0
- data/lib/omniauth_strong_auth_oidc/entity_statement_fetcher/file_fetcher.rb +22 -0
- data/lib/omniauth_strong_auth_oidc/entity_statement_fetcher.rb +9 -0
- data/lib/omniauth_strong_auth_oidc/jwks_cache.rb +31 -0
- data/lib/omniauth_strong_auth_oidc/jwks_fetcher.rb +87 -0
- data/lib/omniauth_strong_auth_oidc/relying_party_entity_statement_generator.rb +77 -0
- data/lib/omniauth_strong_auth_oidc/relying_party_jwks_generator.rb +50 -0
- data/lib/omniauth_strong_auth_oidc/relying_party_jwks_storage/base.rb +101 -0
- data/lib/omniauth_strong_auth_oidc/relying_party_jwks_storage/cache_storage.rb +235 -0
- data/lib/omniauth_strong_auth_oidc/relying_party_jwks_storage/env_storage.rb +112 -0
- data/lib/omniauth_strong_auth_oidc/relying_party_jwks_storage.rb +10 -0
- data/lib/omniauth_strong_auth_oidc/version.rb +3 -0
- data/lib/omniauth_strong_auth_oidc.rb +15 -0
- data/omniauth_strong_auth_oidc.gemspec +32 -0
- metadata +494 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 19c27ecbd57d5babdc8c9914923c0242e23f153c74f5401f365d532619deee73
|
|
4
|
+
data.tar.gz: 4f8be4e8e78f707378f1b40c09cf8890db0bb314b9ad4c78e4869f5dac872b40
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 508bb03ae1ec63d48d724889d51120895adca15f32d4b696ea42bec2eea965c5f267266b57c42b0eedfbcb2dea64ae07ac646c1bc42e91a211a76a2b72574dfd
|
|
7
|
+
data.tar.gz: 9be502336071893f07dbe41e97c5780347999a2b3761fe05eb639e22e811c317dadf301e70805b1a50f3a201c0ca44f4847b5833a7b22d5bad365faa3728b89f
|
data/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.bundle/
|
|
2
|
+
/pkg/
|
|
3
|
+
/vendor/
|
|
4
|
+
.DS_Store
|
|
5
|
+
/.yardoc
|
|
6
|
+
/.cache
|
|
7
|
+
/coverage/
|
|
8
|
+
/*.gem
|
|
9
|
+
/.env
|
|
10
|
+
/.env.*
|
|
11
|
+
/.idea/
|
|
12
|
+
/.vscode/
|
|
13
|
+
/.rubocop-*
|
|
14
|
+
node_modules/
|
|
15
|
+
spec/.result
|
|
16
|
+
*.gem
|
|
17
|
+
*.rbc
|
|
18
|
+
.bundle
|
|
19
|
+
.config
|
|
20
|
+
.yardoc
|
|
21
|
+
Gemfile.lock
|
|
22
|
+
InstalledFiles
|
|
23
|
+
_yardoc
|
|
24
|
+
coverage
|
|
25
|
+
doc/
|
|
26
|
+
lib/bundler/man
|
|
27
|
+
pkg
|
|
28
|
+
rdoc
|
|
29
|
+
spec/reports
|
|
30
|
+
test/tmp
|
|
31
|
+
test/version_tmp
|
|
32
|
+
tmp
|
|
33
|
+
*.swp
|
|
34
|
+
.rspec_status
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kisko Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# OmniAuth Strong Auth OIDC
|
|
2
|
+
|
|
3
|
+
An OmniAuth strategy for implementing strong authentication with Finnish Identification Broker Services via OpenID Connect (OIDC).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This gem provides an OmniAuth strategy that implements the OpenID Connect Federation protocol for strong authentication with Finnish identification providers. It supports:
|
|
8
|
+
|
|
9
|
+
- **Private Key JWT authentication** for client authentication
|
|
10
|
+
- **JWE (JSON Web Encryption)** for encrypted ID tokens
|
|
11
|
+
- **Entity Statements** for federation metadata discovery
|
|
12
|
+
- **Key rotation** with multiple storage backends
|
|
13
|
+
- **Signed authorization requests** using JWT
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Add this line to your application's Gemfile:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem 'omniauth_strong_auth_oidc'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
And then execute:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bundle install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
### Environment Variables
|
|
32
|
+
|
|
33
|
+
Configure the following environment variables:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Required
|
|
37
|
+
OIDC_CLIENT_ID=your_client_id
|
|
38
|
+
OIDC_ACR_VALUES="your_acr_value1 your_acr_value2"
|
|
39
|
+
|
|
40
|
+
# Optional - For production with static keys
|
|
41
|
+
OIDC_SIGNING_KEY_BASE64=base64_encoded_signing_key
|
|
42
|
+
OIDC_ENCRYPTION_KEY_BASE64=base64_encoded_encryption_key
|
|
43
|
+
|
|
44
|
+
# Optional - For key rotation
|
|
45
|
+
OIDC_KEY_ROTATION_ENABLED=true
|
|
46
|
+
|
|
47
|
+
# Optional - Federation metadata URL
|
|
48
|
+
OAUTH_ISSUER_URL=https://your-issuer-url.com
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage with Devise
|
|
52
|
+
|
|
53
|
+
### Enable OmniAuth in User Model
|
|
54
|
+
|
|
55
|
+
Add the OmniAuth provider to your User model:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
class User < ApplicationRecord
|
|
59
|
+
devise :database_authenticatable, :registerable,
|
|
60
|
+
:recoverable, :rememberable, :validatable,
|
|
61
|
+
:omniauthable, omniauth_providers: [:strong_auth_oidc]
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Add Required Fields to User
|
|
66
|
+
|
|
67
|
+
Generate a migration to add OmniAuth fields:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
rails generate migration AddOmniauthToUsers provider:string uid:string identity_number:string
|
|
71
|
+
rails db:migrate
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Configure Devise Initializer
|
|
75
|
+
|
|
76
|
+
In `config/initializers/devise.rb`, configure the OmniAuth provider:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
Devise.setup do |config|
|
|
80
|
+
# ... other Devise configuration ...
|
|
81
|
+
|
|
82
|
+
# Configure entity statement fetcher
|
|
83
|
+
provider_entity_statement_fetcher = OmniauthStrongAuthOidc::EntityStatementFetcher::FileFetcher.new(
|
|
84
|
+
path_to_file: Rails.root.join("config", "oidc_test_entity_statement").to_s
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if ENV["OAUTH_ISSUER_URL"].present?
|
|
88
|
+
provider_entity_statement_fetcher = OmniauthStrongAuthOidc::EntityStatementFetcher::FederationUrlFetcher.new(
|
|
89
|
+
issuer_url: ENV.fetch("OAUTH_ISSUER_URL")
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Configure JWKS storage
|
|
94
|
+
if ENV['OIDC_SIGNING_KEY_BASE64'].present? && ENV['OIDC_ENCRYPTION_KEY_BASE64'].present?
|
|
95
|
+
relying_party_jwks_storage = OmniauthStrongAuthOidc::RelyingPartyJwksStorage::EnvStorage.new
|
|
96
|
+
elsif ENV['OIDC_KEY_ROTATION_ENABLED'] == 'true'
|
|
97
|
+
cache_store = Rails.cache
|
|
98
|
+
# Use in-memory store in non-production environments if cache is NullStore
|
|
99
|
+
# this is useful for development and testing
|
|
100
|
+
# Use a more robust cache store in production (e.g., Memcached, Redis)
|
|
101
|
+
if Rails.cache.is_a?(ActiveSupport::Cache::NullStore) && !Rails.env.production?
|
|
102
|
+
cache_store = ActiveSupport::Cache::MemoryStore.new
|
|
103
|
+
end
|
|
104
|
+
relying_party_jwks_storage = OmniauthStrongAuthOidc::RelyingPartyJwksStorage::CacheStorage.new(
|
|
105
|
+
cache_store: cache_store
|
|
106
|
+
)
|
|
107
|
+
else
|
|
108
|
+
raise "OIDC signing and encryption keys are not configured. Please set OIDC_SIGNING_KEY_BASE64 and OIDC_ENCRYPTION_KEY_BASE64 environment variables, or enable key rotation with OIDC_KEY_ROTATION_ENABLED."
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
OmniauthStrongAuthOidc::RelyingPartyJwksStorage::Base.instance ||= relying_party_jwks_storage
|
|
112
|
+
|
|
113
|
+
provider_jwks_fetcher = OmniauthStrongAuthOidc::JwksFetcher.new(
|
|
114
|
+
entity_statement_fetcher: provider_entity_statement_fetcher
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
config.omniauth :strong_auth_oidc,
|
|
118
|
+
client_id: ENV.fetch("OIDC_CLIENT_ID"),
|
|
119
|
+
relying_party_jwks_storage: relying_party_jwks_storage,
|
|
120
|
+
provider_jwks_loader: OmniauthStrongAuthOidc::JwksCache.new(provider_jwks_fetcher),
|
|
121
|
+
provider_entity_statement_fetcher: provider_entity_statement_fetcher,
|
|
122
|
+
authorize_params: {
|
|
123
|
+
acr_values: ENV.fetch("OIDC_ACR_VALUES").split(' '),
|
|
124
|
+
response_type: 'code',
|
|
125
|
+
scope: 'openid'
|
|
126
|
+
},
|
|
127
|
+
client_options: {
|
|
128
|
+
auth_scheme: :private_key_jwt
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Create OmniAuth Callbacks Controller
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
137
|
+
def strong_auth_oidc
|
|
138
|
+
@user = User.from_omniauth(request.env['omniauth.auth'])
|
|
139
|
+
|
|
140
|
+
if @user.persisted?
|
|
141
|
+
sign_in_and_redirect @user, event: :authentication
|
|
142
|
+
set_flash_message(:notice, :success, kind: 'Strong Auth') if is_navigational_format?
|
|
143
|
+
else
|
|
144
|
+
session['devise.strong_auth_oidc_data'] = request.env['omniauth.auth'].except(:extra)
|
|
145
|
+
redirect_to new_user_registration_url
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def failure
|
|
150
|
+
redirect_to root_path, alert: "Authentication failed: #{failure_message}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Add Class Method to User Model
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
class User < ApplicationRecord
|
|
159
|
+
# ... devise configuration ...
|
|
160
|
+
|
|
161
|
+
def self.from_omniauth(auth)
|
|
162
|
+
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
|
|
163
|
+
user.email = "#{auth.uid}@strong-auth.local" # or handle email differently
|
|
164
|
+
user.password = Devise.friendly_token[0, 20]
|
|
165
|
+
user.identity_number = auth.info.identity_number
|
|
166
|
+
# user.first_name = auth.info.first_name
|
|
167
|
+
# user.last_name = auth.info.last_name
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Configure Routes
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
Rails.application.routes.draw do
|
|
177
|
+
devise_for :users, controllers: {
|
|
178
|
+
omniauth_callbacks: 'users/omniauth_callbacks'
|
|
179
|
+
}
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Add Login Link to View
|
|
184
|
+
|
|
185
|
+
```erb
|
|
186
|
+
<%= link_to "Sign in with Finnish Strong Authentication", user_strong_auth_oidc_omniauth_authorize_path %>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Federation Endpoints Controller
|
|
190
|
+
|
|
191
|
+
To expose the required OIDC federation endpoints (entity statement, JWKS), use the Rails generator:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
rails generate omniauth_strong_auth_oidc:install \
|
|
195
|
+
--redirect_uris="https://example.com/users/auth/strong_auth_oidc/callback" \
|
|
196
|
+
--org_name="Your Organization Name" \
|
|
197
|
+
--iss="https://example.com"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Generator options:**
|
|
201
|
+
|
|
202
|
+
| Option | Required | Description |
|
|
203
|
+
|--------|----------|-------------|
|
|
204
|
+
| `--redirect_uris` | Yes | Comma-separated list of OAuth callback URLs |
|
|
205
|
+
| `--org_name` | Yes | Your organization name for the entity statement |
|
|
206
|
+
| `--iss` | Yes | Issuer URL (your application's base URL) |
|
|
207
|
+
|
|
208
|
+
**Example with multiple redirect URIs:**
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
rails generate omniauth_strong_auth_oidc:install \
|
|
212
|
+
--redirect_uris="https://example.com/users/auth/strong_auth_oidc/callback,https://staging.example.com/users/auth/strong_auth_oidc/callback" \
|
|
213
|
+
--org_name="Acme Corporation" \
|
|
214
|
+
--iss="https://example.com"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
This will:
|
|
218
|
+
1. Create `app/controllers/relying_party_entity_statement_controller.rb`
|
|
219
|
+
2. Add the following routes to `config/routes.rb`:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
# OIDC Federation endpoints
|
|
223
|
+
get '/.well-known/openid-federation', to: 'relying_party_entity_statement#entity_statement', as: :openid_federation
|
|
224
|
+
get '/.well-known/jwks.json', to: 'relying_party_entity_statement#jwks', as: :jwks
|
|
225
|
+
get '/.well-known/signed-jwks.jwt', to: 'relying_party_entity_statement#signed_jwks', as: :signed_jwks
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The generated controller provides the following endpoints:
|
|
229
|
+
|
|
230
|
+
| Endpoint | Content-Type | Description |
|
|
231
|
+
|----------|--------------|-------------|
|
|
232
|
+
| `/.well-known/openid-federation` | `application/entity-statement+jwt` | Signed entity statement JWT |
|
|
233
|
+
| `/.well-known/jwks.json` | `application/json` | Public JWKS for token encryption |
|
|
234
|
+
| `/.well-known/signed-jwks.jwt` | `application/jwks+jwt` | Signed JWKS JWT |
|
|
235
|
+
|
|
236
|
+
**Additional environment variable required:**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
OIDC_CONFIGURATION_SIGNING_KEY_BASE64=base64_encoded_configuration_signing_key
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
This key is used to sign the entity statement and JWKS. It should be a separate RSA key pair from the signing/encryption keys used for tokens.
|
|
243
|
+
|
|
244
|
+
## Key Storage Options
|
|
245
|
+
|
|
246
|
+
### Environment Storage (EnvStorage)
|
|
247
|
+
|
|
248
|
+
Store static keys in environment variables:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
OIDC_SIGNING_KEY_BASE64=<base64_encoded_private_key>
|
|
252
|
+
OIDC_ENCRYPTION_KEY_BASE64=<base64_encoded_private_key>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Cache Storage (CacheStorage)
|
|
256
|
+
|
|
257
|
+
Enable automatic key rotation using Rails cache:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
OIDC_KEY_ROTATION_ENABLED=true
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Keys are automatically generated and rotated based on cache TTL.
|
|
264
|
+
|
|
265
|
+
## Entity Statement Fetchers
|
|
266
|
+
|
|
267
|
+
### File Fetcher
|
|
268
|
+
|
|
269
|
+
For testing and development, load entity statements from a local file:
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
OmniauthStrongAuthOidc::EntityStatementFetcher::FileFetcher.new(
|
|
273
|
+
path_to_file: Rails.root.join("config", "oidc_entity_statement.json").to_s
|
|
274
|
+
)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Federation URL Fetcher
|
|
278
|
+
|
|
279
|
+
For production, fetch entity statements from the federation URL:
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
OmniauthStrongAuthOidc::EntityStatementFetcher::FederationUrlFetcher.new(
|
|
283
|
+
issuer_url: ENV.fetch("OAUTH_ISSUER_URL")
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## User Attributes
|
|
288
|
+
|
|
289
|
+
The strategy returns the following user attributes:
|
|
290
|
+
|
|
291
|
+
- `uid`: Unique identifier (subject)
|
|
292
|
+
- `identity_number`: Finnish personal identity code (urn:oid:1.2.246.21)
|
|
293
|
+
- `first_name`: Given name (urn:oid:1.2.246.575.1.14)
|
|
294
|
+
- `last_name`: Family name (urn:oid:2.5.4.4)
|
|
295
|
+
|
|
296
|
+
Raw claims are available in `auth['extra']['raw_info']`.
|
|
297
|
+
|
|
298
|
+
## Development
|
|
299
|
+
|
|
300
|
+
After checking out the repo, run:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
bundle install
|
|
304
|
+
bundle exec rspec
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Contributing
|
|
308
|
+
|
|
309
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kiskolabs/omniauth_strong_auth_oidc.
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
if defined?(Rails::Generators::Base)
|
|
4
|
+
module OmniauthStrongAuthOidc
|
|
5
|
+
module Generators
|
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
|
7
|
+
source_root File.expand_path('templates', __dir__)
|
|
8
|
+
|
|
9
|
+
class_option :redirect_uris,
|
|
10
|
+
type: :string,
|
|
11
|
+
required: true,
|
|
12
|
+
desc: 'Comma-separated list of redirect URIs (e.g., "https://example.com/users/auth/strong_auth_oidc/callback")'
|
|
13
|
+
|
|
14
|
+
class_option :org_name,
|
|
15
|
+
type: :string,
|
|
16
|
+
required: true,
|
|
17
|
+
desc: 'Organization name for the entity statement'
|
|
18
|
+
|
|
19
|
+
class_option :iss,
|
|
20
|
+
type: :string,
|
|
21
|
+
required: true,
|
|
22
|
+
desc: 'Issuer URL (e.g., "https://example.com")'
|
|
23
|
+
|
|
24
|
+
desc 'Generates the RelyingPartyEntityStatementController for OIDC federation endpoints'
|
|
25
|
+
|
|
26
|
+
def create_controller
|
|
27
|
+
template 'relying_party_entity_statement_controller.rb.tt',
|
|
28
|
+
'app/controllers/relying_party_entity_statement_controller.rb'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_routes
|
|
32
|
+
route_content = <<~ROUTES
|
|
33
|
+
# OIDC Federation endpoints
|
|
34
|
+
get '/.well-known/openid-federation', to: 'relying_party_entity_statement#entity_statement', as: :openid_federation
|
|
35
|
+
get '/.well-known/jwks.json', to: 'relying_party_entity_statement#jwks', as: :jwks
|
|
36
|
+
get '/.well-known/signed-jwks.jwt', to: 'relying_party_entity_statement#signed_jwks', as: :signed_jwks
|
|
37
|
+
ROUTES
|
|
38
|
+
|
|
39
|
+
route route_content
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def show_post_install_message
|
|
43
|
+
say ''
|
|
44
|
+
say '============================================================', :green
|
|
45
|
+
say 'OmniAuth Strong Auth OIDC controller installed successfully!', :green
|
|
46
|
+
say '============================================================', :green
|
|
47
|
+
say ''
|
|
48
|
+
say 'Next steps:', :yellow
|
|
49
|
+
say '1. Review the generated controller at app/controllers/relying_party_entity_statement_controller.rb'
|
|
50
|
+
say '2. Ensure you have the required environment variables set:'
|
|
51
|
+
say ' - OIDC_CLIENT_ID'
|
|
52
|
+
say ' - OIDC_CONFIGURATION_SIGNING_KEY_BASE64'
|
|
53
|
+
say '3. Update your Devise/OmniAuth configuration'
|
|
54
|
+
say ''
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def redirect_uris_array
|
|
60
|
+
options[:redirect_uris].split(',').map(&:strip)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def org_name
|
|
64
|
+
options[:org_name]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def iss
|
|
68
|
+
options[:iss]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Handles OIDC federation endpoints for entity statements and JWKS
|
|
4
|
+
class RelyingPartyEntityStatementController < ApplicationController
|
|
5
|
+
# Return plain JWKS (for backward compatibility)
|
|
6
|
+
def jwks
|
|
7
|
+
jwks = OmniauthStrongAuthOidc::RelyingPartyJwksStorage::Base.instance.current_jwks.export
|
|
8
|
+
render json: jwks
|
|
9
|
+
rescue StandardError => e
|
|
10
|
+
Rails.logger.error "Failed to generate JWKS: #{e.message}"
|
|
11
|
+
render json: { error: 'Failed to generate JWKS' }, status: :internal_server_error
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns signed JWKS JWT containing the client's public keys
|
|
15
|
+
def signed_jwks
|
|
16
|
+
signed_jwks = OmniauthStrongAuthOidc::RelyingPartyJwksGenerator.new(
|
|
17
|
+
relying_party_jwks_storage: OmniauthStrongAuthOidc::RelyingPartyJwksStorage::Base.instance,
|
|
18
|
+
relying_party_configuration_jwks_storage: configuration_jwks_storage,
|
|
19
|
+
issuer: ENV.fetch("OIDC_CLIENT_ID")
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
render plain: signed_jwks.generate_signed, content_type: 'application/jwks+jwt'
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
Rails.logger.error "Failed to generate signed JWKS: #{e.message}"
|
|
25
|
+
render json: { error: 'Failed to generate signed JWKS' }, status: :internal_server_error
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the OpenID Client Configuration including the entity statement
|
|
29
|
+
def entity_statement
|
|
30
|
+
entity_statement = OmniauthStrongAuthOidc::RelyingPartyEntityStatementGenerator.new(
|
|
31
|
+
iss: <%= iss.inspect %>,
|
|
32
|
+
org_name: <%= org_name.inspect %>,
|
|
33
|
+
jwks_uri: jwks_url,
|
|
34
|
+
signed_jwks_uri: signed_jwks_url,
|
|
35
|
+
redirect_uris: <%= redirect_uris_array.inspect %>,
|
|
36
|
+
configuration_jwks_storage: configuration_jwks_storage
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
render plain: entity_statement.generate_signed, content_type: 'application/entity-statement+jwt'
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
Rails.logger.error "Failed to generate entity statement: #{e.message}"
|
|
42
|
+
render json: { error: 'Failed to generate entity statement' }, status: :internal_server_error
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def configuration_jwks_storage
|
|
48
|
+
@configuration_jwks_storage ||= OmniauthStrongAuthOidc::RelyingPartyJwksStorage::EnvStorage.new(
|
|
49
|
+
signing_key_env: 'OIDC_CONFIGURATION_SIGNING_KEY_BASE64',
|
|
50
|
+
encryption_key_env: nil
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def jwks_url
|
|
55
|
+
url_for(action: :jwks, only_path: false)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def signed_jwks_url
|
|
59
|
+
url_for(action: :signed_jwks, only_path: false)
|
|
60
|
+
end
|
|
61
|
+
end
|