class RackOidcApi::Middleware
Public Class Methods
new(app, opts)
click to toggle source
# File lib/rack-oidc-api/middleware.rb, line 18 def initialize(app, opts) @app = app raise "provider must be specified" if !opts[:provider] raise "audience must be specified" if !opts[:audience] @provider = opts[:provider].gsub(/\/\z/, '') @audience = opts[:audience] @pinned_cert = opts[:pinned_provider_ssl] @lock = Mutex.new reload_options end
Public Instance Methods
call(env)
click to toggle source
# File lib/rack-oidc-api/middleware.rb, line 94 def call(env) header = env['HTTP_AUTHORIZATION'] if !header return mkerror("Missing Authorization Header") end if !header.match(BEARER_TOKEN_REGEX) return mkerror("Invalid Bearer token") end _, token = header.split(/\s/, 2) check_reload if !@avail return mkerror("OIDC provider unavailable") end jwk_loader = proc do |options| @jwks end begin jwt = JWT.decode(token, nil, true, { algorithms: @algorithms, jwks: jwk_loader, aud: @audience, verify_aud: true, nbf_leeway: 30, exp_leeway: 30 }) env[:identity_token] = jwt rescue JWT::JWKError => e # Handle problems with the provided JWKs return mkerror("Invalid Bearer token") rescue JWT::DecodeError => e # Handle other decode related issues e.g. no kid in header, no matching public key found etc. return mkerror(e.message) end @app.call(env) end
check_reload()
click to toggle source
# File lib/rack-oidc-api/middleware.rb, line 82 def check_reload locked = @lock.try_lock return unless locked # Only have one reload checking thread at once begin if @valid < Time.now reload_options end ensure @lock.unlock end end
reload_options()
click to toggle source
# File lib/rack-oidc-api/middleware.rb, line 32 def reload_options begin oidc_config_uri = URI("#{@provider}/.well-known/openid-configuration") oidc_config_raw = http_get(oidc_config_uri) raise "Failed to retrieve OIDC Discovery Data" unless oidc_config_raw oidc_config = JSON.parse(oidc_config_raw) raise "Invalid or missing OIDC Discovery Data" unless oidc_config jwks_uri = oidc_config['jwks_uri'] raise "No JWKS URI in OIDC Discovery" unless jwks_uri # Do not allow JWKS from a different origin (scheme, host, port) jwks_uri = URI(jwks_uri) jwks_uri.scheme = oidc_config_uri.scheme jwks_uri.host = oidc_config_uri.host jwks_uri.port = oidc_config_uri.port jwks_raw = http_get(jwks_uri) raise "Failed to retrieve JWKS File" unless jwks_raw jwks = JSON.parse(jwks_raw) algorithms = ALGORITHMS - (ALGORITHMS - oidc_config['id_token_signing_alg_values_supported'] || []) keys = [] jwks['keys'].each do |key| rec = {} key.each do |k, v| rec[k.to_sym] = v end keys << rec end @jwks = {keys: keys} @algorithms = algorithms @valid = Time.now + 300 @avail = true rescue JSON::JSONError @avail = false @valid = Time.now + 60 rescue StandardError => e STDERR.puts(e.message) @avail = false @valid = Time.now + 60 rescue URI::InvalidURIError => e STDERR.puts(e.message) @avail = false @valid = Time.now + 60 end end
Private Instance Methods
http_get(uri)
click to toggle source
# File lib/rack-oidc-api/middleware.rb, line 145 def http_get(uri) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme != 'http' http.verify_mode = OpenSSL::SSL::VERIFY_PEER # Make sure we're verifying if @pinned_cert http.verify_callback = lambda do | preverify_ok, cert_store | return false unless preverify_ok # We only want to verify once, and fail the first time the callback # is invoked (as opposed to checking only the last time it's called). # Therefore we get at the whole authorization chain. # The end certificate is at the beginning of the chain (the certificate # for the host we are talking to) end_cert = cert_store.chain[0] # Only perform the checks if the current cert is the end certificate # in the chain. We can compare using the DER representation # (OpenSSL::X509::Certificate objects are not comparable, and for # a good reason). If we don't we are going to perform the verification # many times - once per certificate in the chain of trust, which is wasteful return true unless end_cert.to_der == cert_store.current_cert.to_der public_key = end_cert.public_key.to_pem @pinned_cert == public_key || @pinned_cert == OpenSSL::Digest::SHA256.hexdigest(public_key) end end end
mkerror(message)
click to toggle source
# File lib/rack-oidc-api/middleware.rb, line 138 def mkerror(message) body = { error: message }.to_json headers = { 'Content-Type' => 'application/json' } [401, headers, [body]] end