Module: KindeSdk

Extended by:
Logging
Defined in:
lib/kinde_sdk.rb,
lib/kinde_sdk/client.rb,
lib/kinde_sdk/engine.rb,
lib/kinde_sdk/errors.rb,
lib/kinde_sdk/current.rb,
lib/kinde_sdk/logging.rb,
lib/kinde_sdk/version.rb,
lib/kinde_sdk/middleware.rb,
lib/kinde_sdk/token_hash.rb,
lib/kinde_sdk/token_store.rb,
lib/kinde_sdk/client/roles.rb,
lib/kinde_sdk/configuration.rb,
lib/kinde_sdk/token_manager.rb,
lib/kinde_sdk/client/permissions.rb,
lib/kinde_sdk/client/entitlements.rb,
lib/kinde_sdk/client/feature_flags.rb,
lib/kinde_sdk/internal/frontend_client.rb,
app/controllers/kinde_sdk/auth_controller.rb

Defined Under Namespace

Modules: Internal, Logging, PortalPage, TokenHash Classes: APIError, AuthController, AuthenticationError, AuthorizationError, Client, Configuration, Current, Engine, Error, Middleware, RateLimitError, TokenManager, TokenStore

Constant Summary collapse

VERSION =
"1.8.0"

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from Logging

log_debug, log_error, log_info, log_warning, resolve_logger, write_log

Class Attribute Details

.configObject

Returns the value of attribute config.



38
39
40
# File 'lib/kinde_sdk.rb', line 38

def config
  @config
end

Class Method Details

.api_client(bearer_token) ⇒ KindeApi::ApiClient

init sdk api client by bearer token

Returns:

  • (KindeApi::ApiClient)


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/kinde_sdk.rb', line 231

def api_client(bearer_token)
  config = KindeApi::Configuration.new  # Create a new instance instead of using default
  config.configure do |c|
    c.access_token = bearer_token
    c.host = URI.parse(@config.domain).host
    c.scheme = url_scheme(c.scheme)
    c.base_path = ''  # Set empty base path since we're using the root
    c.debugging = @config.debugging
    c.logger = @config.logger
    # Set server configuration
    c.server_index = nil  # Force direct URL construction
    c.server_variables = { 'subdomain' => URI.parse(@config.domain).host.split('.').first }
  end
  KindeApi::ApiClient.new(config)
end

.auth_url(client_id: @config.client_id, client_secret: @config.client_secret, domain: @config.domain, redirect_uri: @config.callback_url, **kwargs) ⇒ Hash

receive url for authorization in Kinde itself

Returns:

  • (Hash)


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/kinde_sdk.rb', line 57

def auth_url(
  client_id: @config.client_id,
  client_secret: @config.client_secret,
  domain: @config.domain,
  redirect_uri: @config.callback_url,
   **kwargs)
  params = {
    redirect_uri: redirect_uri,
    state: SecureRandom.hex,
    scope: @config.scope,
    supports_reauth: "true"
  }.merge(**kwargs)

  if params[:invitation_code].is_a?(String) && !params[:invitation_code].strip.empty?
    params[:invitation_code] = params[:invitation_code].strip
    params[:is_invitation] = "true"
  else
    params.delete(:invitation_code)
  end
  return { url: @config.oauth_client(
    client_id: client_id,
    client_secret: client_secret,
    domain: domain,
    authorize_url: "#{domain}/oauth2/auth",
    token_url: "#{domain}/oauth2/token").auth_code.authorize_url(params) } unless @config.pkce_enabled

  pkce_challenge = PkceChallenge.challenge(char_length: 128)
  params.merge!(code_challenge_method: 'S256', code_challenge: pkce_challenge.code_challenge)
  {
    url: @config.oauth_client(
      client_id: client_id,
      client_secret: client_secret,
      domain: domain,
      authorize_url: "#{domain}/oauth2/auth",
      token_url: "#{domain}/oauth2/token").auth_code.authorize_url(params),
    code_verifier: pkce_challenge.code_verifier
  }
end

.client(tokens_hash, auto_refresh_tokens = @config.auto_refresh_tokens, force_api = @config.force_api) ⇒ KindeSdk::Client

tokens_hash #=> "expires_in"=>86399, "id_token"=>"eyJhbGciOiJSUz", "refresh_token"=>"eyJhbGciOiJSUz", "scope"=>"openid offline email profile", "token_type"=>"bearer"

Returns:



131
132
133
134
135
136
137
138
139
140
# File 'lib/kinde_sdk.rb', line 131

def client(tokens_hash, auto_refresh_tokens = @config.auto_refresh_tokens, force_api = @config.force_api)
  normalized_tokens = TokenHash.normalize(tokens_hash)
  bearer_token = normalized_tokens[:access_token]
  if bearer_token.nil? || bearer_token.empty?
    raise AuthenticationError, "Missing access_token in token hash"
  end

  sdk_api_client = api_client(bearer_token)
  KindeSdk::Client.new(sdk_api_client, normalized_tokens, auto_refresh_tokens, force_api)
end

.client_credentials_access(client_id: @config.client_id, client_secret: @config.client_secret, audience: "#{@config.domain}/api", domain: @config.domain) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/kinde_sdk.rb', line 174

def client_credentials_access(
  client_id: @config.client_id,
  client_secret: @config.client_secret,
  audience: "#{@config.domain}/api",
  domain: @config.domain
)
  Faraday.new(url: domain) do |faraday|
    faraday.response :json
    faraday.use Faraday::FollowRedirects::Middleware
  end
    .post("#{domain}/oauth2/token") do |req|
    req.headers[:content_type] = 'application/x-www-form-urlencoded'
    req.body =
      "grant_type=client_credentials&client_id=#{client_id}&client_secret=#{client_secret}&audience=#{audience}"
  end.body
end

.configureObject



44
45
46
47
48
49
50
51
52
# File 'lib/kinde_sdk.rb', line 44

def configure
  if block_given?
    yield(Configuration.default)
  else
    Configuration.default
  end

  @config = Configuration.default
end

.fetch_tokens(params_or_code, client_id: @config.client_id, client_secret: @config.client_secret, domain: @config.domain, code_verifier: nil, redirect_uri: @config.callback_url) ⇒ Hash

when callback processor receives code, it needs to be used for fetching bearer token

Returns:

  • (Hash)


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/kinde_sdk.rb', line 99

def fetch_tokens(
  params_or_code, 
  client_id: @config.client_id,
  client_secret: @config.client_secret,
  domain: @config.domain,
  code_verifier: nil,
  redirect_uri: @config.callback_url)
  code = params_or_code.kind_of?(Hash) ? params_or_code.fetch("code") : params_or_code
  params = {
    redirect_uri: redirect_uri,
    headers: { 'User-Agent' => "Kinde-SDK: Ruby/#{KindeSdk::VERSION}" }
  }
  params[:code_verifier] = code_verifier if code_verifier
  token = @config.oauth_client(
    client_id: client_id,
    client_secret: client_secret,
    domain: domain,
    authorize_url: "#{domain}/oauth2/auth",
    token_url: "#{domain}/oauth2/token").auth_code.get_token(code.to_s, params)

  TokenHash.from_access_token(token)
end

.logout_url(logout_url: @config.logout_url, domain: @config.domain) ⇒ Object

Raises:

  • (ArgumentError)


142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/kinde_sdk.rb', line 142

def logout_url(logout_url: @config.logout_url, domain: @config.domain)
  raise ArgumentError, "domain is required for logout_url" if domain.nil? || domain.to_s.strip.empty?
  
  query = logout_url ? URI.encode_www_form(redirect: logout_url) : nil
  normalized_domain = domain.to_s.strip
  
  # Only allow http/https schemes; prepend https:// if no scheme present
  if normalized_domain.match?(%r{\Ahttps?://}i)
    # Valid http/https scheme - use as-is
  elsif normalized_domain.match?(%r{\A\w+://})
    # Invalid scheme (ftp://, file://, etc.)
    raise ArgumentError, "invalid scheme in domain: #{domain} (only http/https allowed)"
  else
    # No scheme - default to https
    normalized_domain = "https://#{normalized_domain}"
  end
  
  begin
    parsed = URI.parse(normalized_domain)
  rescue URI::InvalidURIError
    raise ArgumentError, "invalid domain format: #{domain}"
  end
  raise ArgumentError, "invalid domain format: #{domain}" if parsed.host.nil? || parsed.host.empty?
  
  scheme = parsed.scheme || 'https'
  # Only omit port if it matches the default for the scheme
  default_port = scheme == 'https' ? 443 : 80
  host_with_port = parsed.port && parsed.port != default_port ? "#{parsed.host}:#{parsed.port}" : parsed.host
  
  "#{scheme}://#{host_with_port}/logout#{query ? "?#{query}" : ''}"
end

.refresh_token(hash, client_id: @config.client_id, client_secret: @config.client_secret, audience: "#{@config.domain}/api", domain: @config.domain) ⇒ Hash

Returns:

  • (Hash)


212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/kinde_sdk.rb', line 212

def refresh_token(hash,
  client_id: @config.client_id,
  client_secret: @config.client_secret,
  audience: "#{@config.domain}/api",
  domain: @config.domain
)
  oauth_client = @config.oauth_client(
    client_id: client_id,
    client_secret: client_secret,
    domain: domain,
    authorize_url: "#{domain}/oauth2/auth",
    token_url: "#{domain}/oauth2/token")
  refreshed = OAuth2::AccessToken.from_hash(oauth_client, TokenHash.normalize(hash)).refresh
  TokenHash.for_refresh_response(refreshed.to_hash)
end

.token_expired?(hash, client_id: @config.client_id, client_secret: @config.client_secret, audience: "#{@config.domain}/api", domain: @config.domain) ⇒ Boolean

Returns:

  • (Boolean)


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/kinde_sdk.rb', line 191

def token_expired?(hash,
  client_id: @config.client_id,
  client_secret: @config.client_secret,
  audience: "#{@config.domain}/api",
  domain: @config.domain
)
  begin
    validate_jwt_token(hash)
    OAuth2::AccessToken.from_hash(@config.oauth_client(
      client_id: client_id,
      client_secret: client_secret,
      domain: domain,
      authorize_url: "#{domain}/oauth2/auth",
      token_url: "#{domain}/oauth2/token"), TokenHash.normalize(hash)).expired?
  rescue JWT::DecodeError, OAuth2::Error => e
    log_error("Error checking token expiration: #{e.message}")
    true
  end
end

.validate_jwt_token(token_hash) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
# File 'lib/kinde_sdk.rb', line 247

def validate_jwt_token(token_hash)
  token_hash.each do |key, token|
    next unless %w[access_token id_token].include?(key.to_s.downcase)
    begin
      jwt_validation(token, "#{@config.domain}#{@config.jwks_url}", @config.expected_issuer, @config.expected_audience)
    rescue JWT::DecodeError
      log_error("Invalid JWT token: #{key}")
      raise JWT::DecodeError, "Invalid #{key.to_s.capitalize.gsub('_', ' ')}"
    end
  end
end