Class: KeycloakSdk::AuthClient

Inherits:
Object
  • Object
show all
Includes:
TokenProvider
Defined in:
lib/keycloak_sdk/auth_client.rb

Overview

인증 파사드. rack-oauth2를 래핑(그랜트·PKCE)하고 introspection(RFC7662)·logout은 Faraday로 손수 수행한다. TokenProvider를 구현하지만(직접 사용용), admin은 캐싱 ClientCredentialsTokenProvider를 별도로 쓴다(§4).

Instance Method Summary collapse

Constructor Details

#initialize(config:, http:, jwt_validator:) ⇒ AuthClient

Returns a new instance of AuthClient.



14
15
16
17
18
19
20
# File 'lib/keycloak_sdk/auth_client.rb', line 14

def initialize(config:, http:, jwt_validator:)
  @config = config
  @http = http
  @jwt_validator = jwt_validator
  @endpoints = OidcEndpoints.from_config(config)
  configure_rack_oauth2_timeouts(config)
end

Instance Method Details

#access_tokenObject

TokenProvider 계약(직접 사용용). admin은 캐싱 provider를 별도로 쓴다.



70
71
72
# File 'lib/keycloak_sdk/auth_client.rb', line 70

def access_token
  client_credentials_token.access_token
end

#client_credentials_tokenObject



61
62
63
64
65
66
67
# File 'lib/keycloak_sdk/auth_client.rb', line 61

def client_credentials_token
  to_token_set(oauth_client.access_token!(scope: @config.scopes.join(" ")))
rescue Rack::OAuth2::Client::Error => e
  raise AuthError.new("client-credentials failed: #{e.message}", oauth_error: e.response[:error].to_s)
rescue Faraday::Error => e
  raise TransportError, "token endpoint transport error: #{e.message}"
end

#create_authorization_request(redirect_uri:, scopes: nil, state: SecureRandom.urlsafe_base64(24), nonce: nil) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/keycloak_sdk/auth_client.rb', line 22

def create_authorization_request(redirect_uri:, scopes: nil, state: SecureRandom.urlsafe_base64(24), nonce: nil)
  verifier = SecureRandom.urlsafe_base64(64)
  challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
  params = {
    scope: (scopes || @config.scopes).join(" "),
    state: state,
    code_challenge: challenge,
    code_challenge_method: :S256
  }
  params[:nonce] = nonce if nonce
  url = oauth_client(redirect_uri: redirect_uri).authorization_uri(params)
  AuthorizationRequest.new(url: url.to_s, state: state, code_verifier: verifier)
end

#exchange_code(code:, code_verifier:, redirect_uri:, expected_nonce: nil) ⇒ Object

expected_nonce가 주어지면(create_authorization_request가 돌려준 nonce) 응답 id_token을 realm JWKS로 서명·iss·aud·exp까지 강화 검증한 뒤 nonce 클레임을 대조한다 — OIDC nonce 재생 방지. 불일치·부재·검증실패는 모두 거부(fail-closed). 생략 시 id_token 검증을 건너뛴다(무-nonce 흐름).



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/keycloak_sdk/auth_client.rb', line 39

def exchange_code(code:, code_verifier:, redirect_uri:, expected_nonce: nil)
  client = oauth_client(redirect_uri: redirect_uri)
  client.authorization_code = code
  token_set = to_token_set(client.access_token!(code_verifier: code_verifier))
  verify_nonce!(token_set.id_token, expected_nonce) unless expected_nonce.nil?
  token_set
rescue Rack::OAuth2::Client::Error => e
  raise AuthError.new("authorization_code exchange failed: #{e.message}", oauth_error: e.response[:error].to_s)
rescue Faraday::Error => e
  raise TransportError, "token endpoint transport error: #{e.message}"
end

#introspect(token) ⇒ Object



74
75
76
77
78
79
80
81
82
83
# File 'lib/keycloak_sdk/auth_client.rb', line 74

def introspect(token)
  resp = @http.post(@endpoints.introspection, {
                      token: token, client_id: @config.client_id, client_secret: @config.client_secret
                    })
  raise AuthError, "introspection failed: HTTP #{resp.status}" unless resp.success?

  IntrospectionResult.from_response(resp.body)
rescue Faraday::Error => e
  raise TransportError, "introspection transport error: #{e.message}"
end

#logout(refresh_token:) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/keycloak_sdk/auth_client.rb', line 85

def logout(refresh_token:)
  resp = @http.post(@endpoints.end_session, {
                      client_id: @config.client_id, client_secret: @config.client_secret,
                      refresh_token: refresh_token
                    })
  raise AuthError, "logout failed: HTTP #{resp.status}" unless resp.success?

  nil
rescue Faraday::Error => e
  raise TransportError, "logout transport error: #{e.message}"
end

#refresh(refresh_token:) ⇒ Object



51
52
53
54
55
56
57
58
59
# File 'lib/keycloak_sdk/auth_client.rb', line 51

def refresh(refresh_token:)
  client = oauth_client
  client.refresh_token = refresh_token
  to_token_set(client.access_token!)
rescue Rack::OAuth2::Client::Error => e
  raise AuthError.new("refresh failed: #{e.message}", oauth_error: e.response[:error].to_s)
rescue Faraday::Error => e
  raise TransportError, "token endpoint transport error: #{e.message}"
end

#validate(token) ⇒ Object



97
98
99
# File 'lib/keycloak_sdk/auth_client.rb', line 97

def validate(token)
  @jwt_validator.validate(token)
end