OmniAuth Strong Auth OIDC
An OmniAuth strategy for implementing strong authentication with Finnish Identification Broker Services via OpenID Connect (OIDC).
Overview
This gem provides an OmniAuth strategy that implements the OpenID Connect Federation protocol for strong authentication with Finnish identification providers. It supports:
- Private Key JWT authentication for client authentication
- JWE (JSON Web Encryption) for encrypted ID tokens
- Entity Statements for federation metadata discovery
- Key rotation with multiple storage backends
- Signed authorization requests using JWT
Installation
Add this line to your application's Gemfile:
gem 'omniauth_strong_auth_oidc'And then execute:
bundle installConfiguration
Environment Variables
Configure the following environment variables:
# Required
OIDC_CLIENT_ID=your_client_id
OIDC_ACR_VALUES="your_acr_value1 your_acr_value2"
# Optional - For production with static keys
OIDC_SIGNING_KEY_BASE64=base64_encoded_signing_key
OIDC_ENCRYPTION_KEY_BASE64=base64_encoded_encryption_key
# Optional - For key rotation
OIDC_KEY_ROTATION_ENABLED=true
# Optional - Federation metadata URL
OAUTH_ISSUER_URL=https://your-issuer-url.comUsage with Devise
Enable OmniAuth in User Model
Add the OmniAuth provider to your User model:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:omniauthable, omniauth_providers: [:strong_auth_oidc]
endAdd Required Fields to User
Generate a migration to add OmniAuth fields:
rails generate migration AddOmniauthToUsers provider:string uid:string identity_number:string
rails db:migrateConfigure Devise Initializer
In config/initializers/devise.rb, configure the OmniAuth provider:
Devise.setup do |config|
# ... other Devise configuration ...
# Configure entity statement fetcher
provider_entity_statement_fetcher = OmniauthStrongAuthOidc::EntityStatementFetcher::FileFetcher.new(
path_to_file: Rails.root.join("config", "oidc_test_entity_statement").to_s
)
if ENV["OAUTH_ISSUER_URL"].present?
provider_entity_statement_fetcher = OmniauthStrongAuthOidc::EntityStatementFetcher::FederationUrlFetcher.new(
issuer_url: ENV.fetch("OAUTH_ISSUER_URL")
)
end
# Configure JWKS storage
if ENV['OIDC_SIGNING_KEY_BASE64'].present? && ENV['OIDC_ENCRYPTION_KEY_BASE64'].present?
relying_party_jwks_storage = OmniauthStrongAuthOidc::RelyingPartyJwksStorage::EnvStorage.new
elsif ENV['OIDC_KEY_ROTATION_ENABLED'] == 'true'
cache_store = Rails.cache
# Use in-memory store in non-production environments if cache is NullStore
# this is useful for development and testing
# Use a more robust cache store in production (e.g., Memcached, Redis)
if Rails.cache.is_a?(ActiveSupport::Cache::NullStore) && !Rails.env.production?
cache_store = ActiveSupport::Cache::MemoryStore.new
end
relying_party_jwks_storage = OmniauthStrongAuthOidc::RelyingPartyJwksStorage::CacheStorage.new(
cache_store: cache_store
)
else
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."
end
OmniauthStrongAuthOidc::RelyingPartyJwksStorage::Base.instance ||= relying_party_jwks_storage
provider_jwks_fetcher = OmniauthStrongAuthOidc::JwksFetcher.new(
entity_statement_fetcher: provider_entity_statement_fetcher
)
config.omniauth :strong_auth_oidc,
client_id: ENV.fetch("OIDC_CLIENT_ID"),
relying_party_jwks_storage: relying_party_jwks_storage,
provider_jwks_loader: OmniauthStrongAuthOidc::JwksCache.new(provider_jwks_fetcher),
provider_entity_statement_fetcher: provider_entity_statement_fetcher,
authorize_params: {
acr_values: ENV.fetch("OIDC_ACR_VALUES").split(' '),
response_type: 'code',
scope: 'openid'
},
client_options: {
auth_scheme: :private_key_jwt
}
endCreate OmniAuth Callbacks Controller
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def strong_auth_oidc
@user = User.from_omniauth(request.env['omniauth.auth'])
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: 'Strong Auth') if is_navigational_format?
else
session['devise.strong_auth_oidc_data'] = request.env['omniauth.auth'].except(:extra)
redirect_to new_user_registration_url
end
end
def failure
redirect_to root_path, alert: "Authentication failed: #{failure_message}"
end
endAdd Class Method to User Model
class User < ApplicationRecord
# ... devise configuration ...
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = "#{auth.uid}@strong-auth.local" # or handle email differently
user.password = Devise.friendly_token[0, 20]
user.identity_number = auth.info.identity_number
# user.first_name = auth.info.first_name
# user.last_name = auth.info.last_name
end
end
endConfigure Routes
Rails.application.routes.draw do
devise_for :users, controllers: {
omniauth_callbacks: 'users/omniauth_callbacks'
}
endAdd Login Link to View
<%= link_to "Sign in with Finnish Strong Authentication", user_strong_auth_oidc_omniauth_authorize_path %>Federation Endpoints Controller
To expose the required OIDC federation endpoints (entity statement, JWKS), use the Rails generator:
rails generate omniauth_strong_auth_oidc:install \
--redirect_uris="https://example.com/users/auth/strong_auth_oidc/callback" \
--org_name="Your Organization Name" \
--iss="https://example.com"Generator options:
| Option | Required | Description |
|---|---|---|
--redirect_uris |
Yes | Comma-separated list of OAuth callback URLs |
--org_name |
Yes | Your organization name for the entity statement |
--iss |
Yes | Issuer URL (your application's base URL) |
Example with multiple redirect URIs:
rails generate omniauth_strong_auth_oidc:install \
--redirect_uris="https://example.com/users/auth/strong_auth_oidc/callback,https://staging.example.com/users/auth/strong_auth_oidc/callback" \
--org_name="Acme Corporation" \
--iss="https://example.com"This will:
- Create
app/controllers/relying_party_entity_statement_controller.rb - Add the following routes to
config/routes.rb:
# OIDC Federation endpoints
get '/.well-known/openid-federation', to: 'relying_party_entity_statement#entity_statement', as: :openid_federation
get '/.well-known/jwks.json', to: 'relying_party_entity_statement#jwks', as: :jwks
get '/.well-known/signed-jwks.jwt', to: 'relying_party_entity_statement#signed_jwks', as: :signed_jwksThe generated controller provides the following endpoints:
| Endpoint | Content-Type | Description |
|---|---|---|
/.well-known/openid-federation |
application/entity-statement+jwt |
Signed entity statement JWT |
/.well-known/jwks.json |
application/json |
Public JWKS for token encryption |
/.well-known/signed-jwks.jwt |
application/jwks+jwt |
Signed JWKS JWT |
Additional environment variable required:
OIDC_CONFIGURATION_SIGNING_KEY_BASE64=base64_encoded_configuration_signing_keyThis 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.
Key Storage Options
Environment Storage (EnvStorage)
Store static keys in environment variables:
OIDC_SIGNING_KEY_BASE64=<base64_encoded_private_key>
OIDC_ENCRYPTION_KEY_BASE64=<base64_encoded_private_key>Cache Storage (CacheStorage)
Enable automatic key rotation using Rails cache:
OIDC_KEY_ROTATION_ENABLED=trueKeys are automatically generated and rotated based on cache TTL.
Entity Statement Fetchers
File Fetcher
For testing and development, load entity statements from a local file:
OmniauthStrongAuthOidc::EntityStatementFetcher::FileFetcher.new(
path_to_file: Rails.root.join("config", "oidc_entity_statement.json").to_s
)Federation URL Fetcher
For production, fetch entity statements from the federation URL:
OmniauthStrongAuthOidc::EntityStatementFetcher::FederationUrlFetcher.new(
issuer_url: ENV.fetch("OAUTH_ISSUER_URL")
)User Attributes
The strategy returns the following user attributes:
-
uid: Unique identifier (subject) -
identity_number: Finnish personal identity code (urn:oid:1.2.246.21) -
first_name: Given name (urn:oid:1.2.246.575.1.14) -
last_name: Family name (urn:oid:2.5.4.4)
Raw claims are available in auth['extra']['raw_info'].
Development
After checking out the repo, run:
bundle install
bundle exec rspecContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/kiskolabs/omniauth_strong_auth_oidc.
License
The gem is available as open source under the terms of the MIT License.