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를 별도로 쓴다.



72
73
74
# File 'lib/keycloak_sdk/auth_client.rb', line 72

def access_token
  client_credentials_token.access_token
end

#client_credentials_tokenObject



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

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: SecureRandom.urlsafe_base64(24)) ⇒ Object



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

def create_authorization_request(redirect_uri:, scopes: nil, state: SecureRandom.urlsafe_base64(24),
                                 nonce: SecureRandom.urlsafe_base64(24))
  verifier = SecureRandom.urlsafe_base64(64)
  challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
  params = {
    scope: (scopes || @config.scopes).join(" "),
    state: state,
    nonce: nonce,
    code_challenge: challenge,
    code_challenge_method: :S256
  }
  url = oauth_client(redirect_uri: redirect_uri).authorization_uri(params)
  AuthorizationRequest.new(url: url.to_s, state: state, code_verifier: verifier, nonce: nonce)
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 검증을 건너뛴다 (여덟 언어 공통 — exchange에서 nonce를 필수로 만들지 않는다).



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

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



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

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



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

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



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

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



99
100
101
# File 'lib/keycloak_sdk/auth_client.rb', line 99

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