class KeycloakRack::Authenticate
The core service that handles authenticating a request from Keycloak.
@example
class ApplicationController < ActionController::API before_action :authenticate_user! # @return [void] def authenticate_user! # KeycloakRack::Session#authenticate! implements a Dry::Matcher::ResultMatcher request.env["keycloak:session"].authenticate! do |m| m.success(:authenticated) do |_, token| # this is the case when a user is successfully authenticated # token will be a KeycloakRack::DecodedToken instance, a # hash-like PORO that maps a number of values from the # decoded JWT that can be used to find or upsert a user attrs = decoded_token.slice(:keycloak_id, :email, :email_verified, :realm_access, :resource_access) result = User.upsert attrs, returning: %i[id], unique_by: %i[keycloak_id] @current_user = User.find result.first["id"] end m.success do # When allow_anonymous is true, or # a URI is skipped because of skip_paths, this # case will be reached. Requests from here on # out should be considered anonymous and treated # accordingly @current_user = AnonymousUser.new end m.failure do |code, reason| # All authentication failures are reached here, # assuming halt_on_auth_failure is set to false # This allows the application to decide how it # wants to respond render json: { errors: [{ message: "Auth Failure" }] }, status: :forbidden end end end end
Public Instance Methods
call(env) { |call| ... }
click to toggle source
@param [Hash] env the rack environment @return [Dry::Monads::Success(:authenticated, KeycloakRack::DecodedToken
)] @return [Dry::Monads::Success(:skipped, String)] @return [Dry::Monads::Success(:unauthenticated)] @return [Dry::Monads::Failure(:expired, String, String, Exception)] @return [Dry::Monads::Failure(:decoding_failed, String, String, Exception)]
# File lib/keycloak_rack/authenticate.rb, line 67 def call(env) return Success[:skipped] if yield skip_authentication.call(env) token = yield read_token.call env return Success[:unauthenticated] if token.blank? decoded_token = yield decode_and_verify token Success[:authenticated, decoded_token] end
Private Instance Methods
algorithms_for(jwks)
click to toggle source
@param [{ Symbol => <{ Symbol => String }> }] jwks @return [<String>]
# File lib/keycloak_rack/authenticate.rb, line 107 def algorithms_for(jwks) jwks.fetch(:keys, []).map do |k| k[:alg] end.uniq.compact.then do |algs| algs.present? ? Success(algs) : Failure[:no_algorithms, "Could not derive algorithms from JWKS"] end end
decode_and_verify(token) { |find_public_keys| ... }
click to toggle source
@param [String] token @return [Dry::Monads::Success(KeycloakRack::DecodedToken
)] @return [Dry::Monads::Failure(:expired, String, String, Exception)] @return [Dry::Monads::Failure(:decoding_failed, String, String, Exception)]
# File lib/keycloak_rack/authenticate.rb, line 85 def decode_and_verify(token) jwks = yield key_resolver.find_public_keys algorithms = yield algorithms_for jwks options = { algorithms: algorithms, leeway: token_leeway, jwks: jwks } payload, headers = JWT.decode token, nil, true, options rescue JWT::ExpiredSignature => e Failure[:expired, "JWT is expired", token, e] rescue JWT::DecodeError => e Failure[:decoding_failed, "Failed to decode JWT", token, e] else Success DecodedToken.new payload.merge(original_payload: payload, headers: headers) end