module Rodauth

Constants

ACCESS_TYPES
APPLICATION_REQUIRED_PARAMS

Application

APPROVAL_PROMPTS
JWKS
OIDC_SCOPES_MAP

openid.net/specs/openid-connect-core-1_0.html#StandardClaims

PROTECTED_APPLICATION_ATTRIBUTES
REQUIRED_METADATA_KEYS
SCOPES
SERVER_METADATA

Resource server mode

VALID_METADATA_KEYS

Public Instance Methods

_do_authorize_code() click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 144
def _do_authorize_code
  { "code" => create_oauth_grant(oauth_grants_account_id_column => account_id) }
end
_do_authorize_id_token() click to toggle source
# File lib/rodauth/features/oidc.rb, line 448
def _do_authorize_id_token
  create_params = {
    oauth_tokens_account_id_column => account_id,
    oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
    oauth_tokens_scopes_column => scopes
  }
  oauth_token = generate_oauth_token(create_params, false)
  generate_id_token(oauth_token)
  params = json_access_token_payload(oauth_token)
  params.delete("access_token")
  params
end
_do_authorize_token() click to toggle source
# File lib/rodauth/features/oauth_implicit_grant.rb, line 22
def _do_authorize_token
  create_params = {
    oauth_tokens_account_id_column => account_id,
    oauth_tokens_oauth_application_id_column => oauth_application[oauth_applications_id_column],
    oauth_tokens_scopes_column => scopes
  }
  oauth_token = generate_oauth_token(create_params, false)

  json_access_token_payload(oauth_token)
end
_generate_oauth_token(params = {}) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 452
def _generate_oauth_token(params = {})
  ds = db[oauth_tokens_table]

  if __one_oauth_token_per_account

    token = __insert_or_update_and_return__(
      ds,
      oauth_tokens_id_column,
      oauth_tokens_unique_columns,
      params,
      Sequel.expr(Sequel[oauth_tokens_table][oauth_tokens_expires_in_column]) > Sequel::CURRENT_TIMESTAMP,
      ([oauth_tokens_token_column, oauth_tokens_refresh_token_column] if oauth_reuse_access_token)
    )

    # if the previous operation didn't return a row, it means that the conditions
    # invalidated the update, and the existing token is still valid.
    token || ds.where(
      oauth_tokens_account_id_column => params[oauth_tokens_account_id_column],
      oauth_tokens_oauth_application_id_column => params[oauth_tokens_oauth_application_id_column]
    ).first
  else
    if oauth_reuse_access_token
      unique_conds = Hash[oauth_tokens_unique_columns.map { |column| [column, params[column]] }]
      valid_token = ds.where(Sequel.expr(Sequel[oauth_tokens_table][oauth_tokens_expires_in_column]) > Sequel::CURRENT_TIMESTAMP)
                      .where(unique_conds).first
      return valid_token if valid_token
    end
    __insert_and_return__(ds, oauth_tokens_id_column, params)
  end
end
_json_response_body(hash) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 711
def _json_response_body(hash)
  if request.respond_to?(:convert_to_json)
    request.send(:convert_to_json, hash)
  else
    JSON.dump(hash)
  end
end
_jwt_key() click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 283
def _jwt_key
  @_jwt_key ||= oauth_jwt_key || begin
    if oauth_application

      if (jwks = oauth_application_jwks)
        jwks = JSON.parse(jwks, symbolize_names: true) if jwks && jwks.is_a?(String)
        jwks
      else
        oauth_application[oauth_applications_jwt_public_key_column]
      end
    end
  end
end
_jwt_public_key() click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 297
def _jwt_public_key
  @_jwt_public_key ||= oauth_jwt_public_key || begin
    if oauth_application
      jwks || oauth_application[oauth_applications_jwt_public_key_column]
    else
      _jwt_key
    end
  end
end
accepts_json?() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 168
def accepts_json?
  return true if only_json?

  (accept = request.env["HTTP_ACCEPT"]) && accept =~ json_request_regexp
end
account_from_bearer_assertion_subject(subject) click to toggle source
# File lib/rodauth/features/oauth_assertion_base.rb, line 48
def account_from_bearer_assertion_subject(subject)
  __insert_or_do_nothing_and_return__(
    db[accounts_table],
    account_id_column,
    [login_column],
    login_column => subject
  )
end
account_from_jwt_bearer_assertion(assertion) click to toggle source
# File lib/rodauth/features/oauth_jwt_bearer_grant.rb, line 37
def account_from_jwt_bearer_assertion(assertion)
  claims = jwt_assertion(assertion)

  return unless claims

  account_from_bearer_assertion_subject(claims["sub"])
end
account_from_saml2_bearer_assertion(assertion) click to toggle source
# File lib/rodauth/features/oauth_saml_bearer_grant.rb, line 47
def account_from_saml2_bearer_assertion(assertion)
  saml = saml_assertion(assertion)

  return unless saml

  account_from_bearer_assertion_subject(saml.nameid)
end
allow_cors(request) click to toggle source
# File lib/rodauth/features/oidc.rb, line 529
def allow_cors(request)
  return unless request.request_method == "OPTIONS"

  response["Access-Control-Allow-Origin"] = "*"
  response["Access-Control-Allow-Methods"] = "GET, OPTIONS"
  response["Access-Control-Max-Age"] = "3600"
  response.status = 200
  request.halt
end
assertion_grant_type(grant_type = param("grant_type")) click to toggle source
# File lib/rodauth/features/oauth_assertion_base.rb, line 88
def assertion_grant_type(grant_type = param("grant_type"))
  grant_type.delete_prefix("urn:ietf:params:oauth:grant-type:").tr("-", "_")
end
assertion_grant_type?(grant_type = param("grant_type")) click to toggle source
# File lib/rodauth/features/oauth_assertion_base.rb, line 80
def assertion_grant_type?(grant_type = param("grant_type"))
  grant_type.start_with?("urn:ietf:params:oauth:grant-type:")
end
auth_server_jwks_set() click to toggle source

Resource Server only!

returns the jwks set from the authorization server.

# File lib/rodauth/features/oauth_jwt.rb, line 310
def auth_server_jwks_set
  metadata = authorization_server_metadata

  return unless metadata && (jwks_uri = metadata[:jwks_uri])

  jwks_uri = URI(jwks_uri)

  jwks = JWKS[jwks_uri]

  return jwks if jwks

  JWKS.set(jwks_uri) do
    http = Net::HTTP.new(jwks_uri.host, jwks_uri.port)
    http.use_ssl = jwks_uri.scheme == "https"

    request = Net::HTTP::Get.new(jwks_uri.request_uri)
    request["accept"] = json_response_content_type
    response = http.request(request)
    authorization_required unless response.code.to_i == 200

    # time-to-live
    ttl = if response.key?("cache-control")
            cache_control = response["cache-control"]
            cache_control[/max-age=(\d+)/, 1].to_i
          elsif response.key?("expires")
            Time.parse(response["expires"]).to_i - Time.now.to_i
          end

    [JSON.parse(response.body, symbolize_names: true), ttl]
  end
end
authorization_required() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 720
def authorization_required
  if accepts_json?
    throw_json_response_error(authorization_required_error_status, "invalid_client")
  else
    set_redirect_error_flash(require_authorization_error_flash)
    redirect(authorize_path)
  end
end
authorization_server_metadata() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 743
def authorization_server_metadata
  auth_url = URI(authorization_server_url)

  server_metadata = SERVER_METADATA[auth_url]

  return server_metadata if server_metadata

  SERVER_METADATA.set(auth_url) do
    http = Net::HTTP.new(auth_url.host, auth_url.port)
    http.use_ssl = auth_url.scheme == "https"

    request = Net::HTTP::Get.new("/.well-known/oauth-authorization-server")
    request["accept"] = json_response_content_type
    response = http.request(request)
    authorization_required unless response.code.to_i == 200

    # time-to-live
    ttl = if response.key?("cache-control")
            cache_control = response["cache-control"]
            cache_control[/max-age=(\d+)/, 1].to_i
          elsif response.key?("expires")
            Time.parse(response["expires"]).to_i - Time.now.to_i
          end

    [JSON.parse(response.body, symbolize_names: true), ttl]
  end
end
authorization_server_url() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 321
def authorization_server_url
  base_url
end
authorization_token() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 230
def authorization_token
  return @authorization_token if defined?(@authorization_token)

  # check if there is a token
  bearer_token = fetch_access_token

  return unless bearer_token

  @authorization_token = if is_authorization_server?
                           # check if token has not expired
                           # check if token has been revoked
                           oauth_token_by_token(bearer_token)
                         else
                           # where in resource server, NOT the authorization server.
                           payload = introspection_request("access_token", bearer_token)

                           return unless payload["active"]

                           payload
                         end
end
authorize_response(params, mode) click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 148
    def authorize_response(params, mode)
      redirect_url = URI.parse(redirect_uri)
      case mode
      when "query"
        params = params.map { |k, v| "#{k}=#{v}" }
        params << redirect_url.query if redirect_url.query
        redirect_url.query = params.join("&")
        redirect(redirect_url.to_s)
      when "form_post"
        scope.view layout: false, inline: <<-FORM
          <html>
            <head><title>Authorized</title></head>
            <body onload="javascript:document.forms[0].submit()">
              <form method="post" action="#{redirect_uri}">
                #{
                  params.map do |name, value|
                    "<input type=\"hidden\" name=\"#{name}\" value=\"#{scope.h(value)}\" />"
                  end.join
                }
                <input type="submit" class="btn btn-outline-primary" value="#{scope.h(oauth_authorize_post_button)}"/>
              </form>
            </body>
          </html>
        FORM
      when "none"
        redirect(redirect_url.to_s)
      end
    end
authorized_oauth_application?(oauth_application, client_secret, auth_method) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 361
def authorized_oauth_application?(oauth_application, client_secret, auth_method)
  supported_auth_methods = if oauth_application[oauth_applications_token_endpoint_auth_method_column]
                             oauth_application[oauth_applications_token_endpoint_auth_method_column].split(/ +/)
                           else
                             oauth_auth_methods_supported
                           end

  if auth_method
    supported_auth_methods.include?(auth_method) && secret_matches?(oauth_application, client_secret)
  else
    supported_auth_methods.include?("none")
  end
end
before_introspection_request(request) click to toggle source
# File lib/rodauth/features/oauth_token_introspection.rb, line 99
def before_introspection_request(request); end
check_csrf?() click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_application_management.rb, line 139
def check_csrf?
  case request.path
  when oauth_applications_path
    only_json? ? false : super
  else
    super
  end
end
check_valid_access_type?() click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 214
def check_valid_access_type?
  return true unless use_oauth_access_type?

  access_type = param_or_nil("access_type")
  !access_type || ACCESS_TYPES.include?(access_type)
end
check_valid_approval_prompt?() click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 223
def check_valid_approval_prompt?
  return true unless use_oauth_access_type?

  approval_prompt = param_or_nil("approval_prompt")
  !approval_prompt || APPROVAL_PROMPTS.include?(approval_prompt)
end
check_valid_grant_challenge?(grant, verifier) click to toggle source
# File lib/rodauth/features/oauth_pkce.rb, line 76
def check_valid_grant_challenge?(grant, verifier)
  challenge = grant[oauth_grants_code_challenge_column]

  case grant[oauth_grants_code_challenge_method_column]
  when "plain"
    challenge == verifier
  when "S256"
    generated_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier))
    generated_challenge.delete_suffix!("=") while generated_challenge.end_with?("=")

    challenge == generated_challenge
  else
    redirect_response_error("unsupported_transform_algorithm")
  end
end
check_valid_redirect_uri?() click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 236
def check_valid_redirect_uri?
  oauth_application[oauth_applications_redirect_uri_column].split(" ").include?(redirect_uri)
end
check_valid_response_type?() click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 230
def check_valid_response_type?
  response_type = param_or_nil("response_type")

  response_type.nil? || response_type == "code"
end
check_valid_scopes?() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 729
def check_valid_scopes?
  return false unless scopes

  (scopes - oauth_application[oauth_applications_scopes_column].split(oauth_scope_separator)).empty?
end
check_valid_uri?(uri) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 735
def check_valid_uri?(uri)
  URI::DEFAULT_PARSER.make_regexp(oauth_valid_uri_schemes).match?(uri)
end
client_assertion_type(assertion_type = param("client_assertion_type")) click to toggle source
# File lib/rodauth/features/oauth_assertion_base.rb, line 92
def client_assertion_type(assertion_type = param("client_assertion_type"))
  assertion_type.delete_prefix("urn:ietf:params:oauth:client-assertion-type:").tr("-", "_")
end
client_assertion_type?(client_assertion_type = param("client_assertion_type")) click to toggle source
# File lib/rodauth/features/oauth_assertion_base.rb, line 84
def client_assertion_type?(client_assertion_type = param("client_assertion_type"))
  client_assertion_type.start_with?("urn:ietf:params:oauth:client-assertion-type:")
end
create_oauth_application() click to toggle source
# File lib/rodauth/features/oauth_application_management.rb, line 189
def create_oauth_application
  create_params = {
    oauth_applications_account_id_column => account_id,
    oauth_applications_name_column => oauth_application_params[oauth_application_name_param],
    oauth_applications_description_column => oauth_application_params[oauth_application_description_param],
    oauth_applications_scopes_column => oauth_application_params[oauth_application_scopes_param],
    oauth_applications_homepage_url_column => oauth_application_params[oauth_application_homepage_url_param]
  }

  redirect_uris = oauth_application_params[oauth_application_redirect_uri_param]
  redirect_uris = redirect_uris.to_a.reject(&:empty?).join(" ") if redirect_uris.respond_to?(:each)
  create_params[oauth_applications_redirect_uri_column] = redirect_uris unless redirect_uris.empty?
  # set client ID/secret pairs

  create_params.merge! \
    oauth_applications_client_secret_column => \
      secret_hash(oauth_application_params[oauth_application_client_secret_param])

  create_params[oauth_applications_scopes_column] = if create_params[oauth_applications_scopes_column]
                                                      create_params[oauth_applications_scopes_column].join(oauth_scope_separator)
                                                    else
                                                      oauth_application_default_scope
                                                    end

  rescue_from_uniqueness_error do
    create_params[oauth_applications_client_id_column] = oauth_unique_id_generator
    db[oauth_applications_table].insert(create_params)
  end
end
create_oauth_grant(create_params = {}) click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 104
def create_oauth_grant(create_params = {})
  create_params.merge!(
    oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
    oauth_grants_redirect_uri_column => redirect_uri,
    oauth_grants_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_grant_expires_in),
    oauth_grants_scopes_column => scopes.join(oauth_scope_separator)
  )

  # Access Type flow
  if use_oauth_access_type? && (access_type = param_or_nil("access_type"))
    create_params[oauth_grants_access_type_column] = access_type
  end

  ds = db[oauth_grants_table]

  rescue_from_uniqueness_error do
    create_params[oauth_grants_code_column] = oauth_unique_id_generator
    __insert_and_return__(ds, oauth_grants_id_column, create_params)
  end
  create_params[oauth_grants_code_column]
end
create_oauth_token(grant_type) click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_assertion_base.rb, line 57
def create_oauth_token(grant_type)
  return super unless assertion_grant_type?(grant_type) && supported_grant_type?(grant_type)

  account = __send__(:"account_from_#{assertion_grant_type}_assertion", param("assertion"))

  redirect_response_error("invalid_grant") unless account

  grant_scopes = if param_or_nil("scope")
                   redirect_response_error("invalid_grant") unless check_valid_scopes?
                   scopes
                 else
                   @oauth_application[oauth_applications_scopes_column]
                 end

  create_params = {
    oauth_tokens_account_id_column => account[account_id_column],
    oauth_tokens_oauth_application_id_column => @oauth_application[oauth_applications_id_column],
    oauth_tokens_scopes_column => grant_scopes
  }

  generate_oauth_token(create_params, false)
end
create_oauth_token_from_authorization_code(oauth_grant, create_params) click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 201
def create_oauth_token_from_authorization_code(oauth_grant, create_params)
  # revoke oauth grant
  db[oauth_grants_table].where(oauth_grants_id_column => oauth_grant[oauth_grants_id_column])
                        .update(oauth_grants_revoked_at_column => Sequel::CURRENT_TIMESTAMP)

  should_generate_refresh_token = !use_oauth_access_type? ||
                                  oauth_grant[oauth_grants_access_type_column] == "offline"

  generate_oauth_token(create_params, should_generate_refresh_token)
end
create_oauth_token_from_token(oauth_token, update_params) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 573
def create_oauth_token_from_token(oauth_token, update_params)
  redirect_response_error("invalid_grant") unless token_from_application?(oauth_token, oauth_application)

  rescue_from_uniqueness_error do
    oauth_tokens_ds = db[oauth_tokens_table]
    token = oauth_unique_id_generator

    if oauth_tokens_token_hash_column
      update_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
    else
      update_params[oauth_tokens_token_column] = token
    end

    oauth_token = if oauth_refresh_token_protection_policy == "rotation"
                    insert_params = {
                      **update_params,
                      oauth_tokens_oauth_token_id_column => oauth_token[oauth_tokens_id_column],
                      oauth_tokens_scopes_column => oauth_token[oauth_tokens_scopes_column]
                    }

                    refresh_token = oauth_unique_id_generator

                    if oauth_tokens_refresh_token_hash_column
                      insert_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
                    else
                      insert_params[oauth_tokens_refresh_token_column] = refresh_token
                    end

                    # revoke the refresh token
                    oauth_tokens_ds.where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
                                   .update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)

                    insert_params[oauth_tokens_oauth_token_id_column] = oauth_token[oauth_tokens_id_column]
                    __insert_and_return__(oauth_tokens_ds, oauth_tokens_id_column, insert_params)
                  else
                    # includes none
                    ds = oauth_tokens_ds.where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])
                    __update_and_return__(ds, update_params)
                  end

    oauth_token[oauth_tokens_token_column] = token
    oauth_token[oauth_tokens_refresh_token_column] = refresh_token if refresh_token
    oauth_token
  end
end
do_authorize(response_params = {}, response_mode = param_or_nil("response_mode")) click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 126
def do_authorize(response_params = {}, response_mode = param_or_nil("response_mode"))
  case param("response_type")

  when "code"
    response_mode ||= "query"
    response_params.replace(_do_authorize_code)
  when "none"
    response_mode ||= "none"
  when "", nil
    response_mode ||= oauth_response_mode
    response_params.replace(_do_authorize_code)
  end

  response_params["state"] = param("state") if param_or_nil("state")

  [response_params, response_mode]
end
do_register(return_params = request.params.dup) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 169
def do_register(return_params = request.params.dup)
  # set defaults
  create_params = @oauth_application_params
  create_params[oauth_applications_scopes_column] ||= return_params["scopes"] = oauth_application_default_scope.join(" ")
  create_params[oauth_applications_token_endpoint_auth_method_column] ||= begin
    return_params["token_endpoint_auth_method"] = "client_secret_basic"
    "client_secret_basic"
  end
  create_params[oauth_applications_grant_types_column] ||= begin
    return_params["grant_types"] = %w[authorization_code]
    "authorization_code"
  end
  create_params[oauth_applications_response_types_column] ||= begin
    return_params["response_types"] = %w[code]
    "code"
  end
  rescue_from_uniqueness_error do
    client_id = oauth_unique_id_generator
    create_params[oauth_applications_client_id_column] = client_id
    return_params["client_id"] = client_id
    return_params["client_id_issued_at"] = Time.now.utc.iso8601
    if create_params.key?(oauth_applications_client_secret_column)
      create_params[oauth_applications_client_secret_column] = secret_hash(create_params[oauth_applications_client_secret_column])
      return_params.delete("client_secret")
    else
      client_secret = oauth_unique_id_generator
      create_params[oauth_applications_client_secret_column] = secret_hash(client_secret)
      return_params["client_secret"] = client_secret
      return_params["client_secret_expires_at"] = 0
    end
    db[oauth_applications_table].insert(create_params)
  end

  return_params
end
fetch_access_token() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 216
def fetch_access_token
  value = request.env["HTTP_AUTHORIZATION"]

  return unless value && !value.empty?

  scheme, token = value.split(" ", 2)

  return unless scheme.downcase == oauth_token_type

  return if token.nil? || token.empty?

  token
end
fill_with_account_claims(claims, account, scopes) click to toggle source

aka fill_with_standard_claims

# File lib/rodauth/features/oidc.rb, line 366
def fill_with_account_claims(claims, account, scopes)
  scopes_by_claim = scopes.each_with_object({}) do |scope, by_oidc|
    next if scope == "openid"

    oidc, param = scope.split(".", 2)

    by_oidc[oidc] ||= []

    by_oidc[oidc] << param.to_sym if param
  end

  oidc_scopes, additional_scopes = scopes_by_claim.keys.partition { |key| OIDC_SCOPES_MAP.key?(key) }

  unless oidc_scopes.empty?
    if respond_to?(:get_oidc_param)
      oidc_scopes.each do |scope|
        scope_claims = claims
        params = scopes_by_claim[scope]
        params = params.empty? ? OIDC_SCOPES_MAP[scope] : (OIDC_SCOPES_MAP[scope] & params)

        scope_claims = (claims["address"] = {}) if scope == "address"
        params.each do |param|
          scope_claims[param] = __send__(:get_oidc_param, account, param)
        end
      end
    else
      warn "`get_oidc_param(account, claim)` must be implemented to use oidc scopes."
    end
  end

  return if additional_scopes.empty?

  if respond_to?(:get_additional_param)
    additional_scopes.each do |scope|
      claims[scope] = __send__(:get_additional_param, account, scope.to_sym)
    end
  else
    warn "`get_additional_param(account, claim)` must be implemented to use oidc scopes."
  end
end
generate_id_token(oauth_token) click to toggle source
# File lib/rodauth/features/oidc.rb, line 334
def generate_id_token(oauth_token)
  oauth_scopes = oauth_token[oauth_tokens_scopes_column].split(oauth_scope_separator)

  return unless oauth_scopes.include?("openid")

  id_token_claims = jwt_claims(oauth_token)
  id_token_claims[:nonce] = oauth_token[oauth_tokens_nonce_column] if oauth_token[oauth_tokens_nonce_column]

  # Time when the End-User authentication occurred.
  #
  # Sounds like the same as issued at claim.
  id_token_claims[:auth_time] = id_token_claims[:iat]

  account = db[accounts_table].where(account_id_column => oauth_token[oauth_tokens_account_id_column]).first

  # this should never happen!
  # a newly minted oauth token from a grant should have been assigned to an account
  # who just authorized its generation.
  return unless account

  fill_with_account_claims(id_token_claims, account, oauth_scopes)

  params = {
    jwks: oauth_application_jwks,
    signing_algorithm: oauth_application[oauth_applications_id_token_signed_response_alg_column] || oauth_jwt_algorithm,
    encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
    encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
  }
  oauth_token[:id_token] = jwt_encode(id_token_claims, **params)
end
generate_jti(payload) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 342
def generate_jti(payload)
  # Use the key and iat to create a unique key per request to prevent replay attacks
  jti_raw = [
    payload[:aud] || payload["aud"],
    payload[:iat] || payload["iat"]
  ].join(":").to_s
  Digest::SHA256.hexdigest(jti_raw)
end
generate_oauth_token(params = {}, should_generate_refresh_token = true) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 421
def generate_oauth_token(params = {}, should_generate_refresh_token = true)
  create_params = {
    oauth_tokens_expires_in_column => Sequel.date_add(Sequel::CURRENT_TIMESTAMP, seconds: oauth_token_expires_in)
  }.merge(params)

  rescue_from_uniqueness_error do
    token = oauth_unique_id_generator

    if oauth_tokens_token_hash_column
      create_params[oauth_tokens_token_hash_column] = generate_token_hash(token)
    else
      create_params[oauth_tokens_token_column] = token
    end

    refresh_token = nil
    if should_generate_refresh_token
      refresh_token = oauth_unique_id_generator

      if oauth_tokens_refresh_token_hash_column
        create_params[oauth_tokens_refresh_token_hash_column] = generate_token_hash(refresh_token)
      else
        create_params[oauth_tokens_refresh_token_column] = refresh_token
      end
    end
    oauth_token = _generate_oauth_token(create_params)
    oauth_token[oauth_tokens_token_column] = token
    oauth_token[oauth_tokens_refresh_token_column] = refresh_token if refresh_token
    oauth_token
  end
end
generate_token_hash(token) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 405
def generate_token_hash(token)
  Base64.urlsafe_encode64(Digest::SHA256.digest(token))
end
generate_user_code() click to toggle source
# File lib/rodauth/features/oauth_device_grant.rb, line 119
def generate_user_code
  user_code_size = oauth_device_code_grant_user_code_size
  SecureRandom.random_number(36**user_code_size)
              .to_s(36) # 0 to 9, a to z
              .upcase
              .rjust(user_code_size, "0")
end
introspection_request(token_type_hint, token) click to toggle source
# File lib/rodauth/features/oauth_token_introspection.rb, line 82
def introspection_request(token_type_hint, token)
  auth_url = URI(authorization_server_url)
  http = Net::HTTP.new(auth_url.host, auth_url.port)
  http.use_ssl = auth_url.scheme == "https"

  request = Net::HTTP::Post.new(introspect_path)
  request["content-type"] = "application/x-www-form-urlencoded"
  request["accept"] = json_response_content_type
  request.set_form_data({ "token_type_hint" => token_type_hint, "token" => token })

  before_introspection_request(request)
  response = http.request(request)
  authorization_required unless response.code.to_i == 200

  JSON.parse(response.body)
end
issuer() click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 104
def issuer
  @issuer ||= oauth_jwt_token_issuer || authorization_server_url
end
json_access_token_payload(oauth_token) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 520
def json_access_token_payload(oauth_token)
  payload = {
    "access_token" => oauth_token[oauth_tokens_token_column],
    "token_type" => oauth_token_type,
    "expires_in" => oauth_token_expires_in
  }
  payload["refresh_token"] = oauth_token[oauth_tokens_refresh_token_column] if oauth_token[oauth_tokens_refresh_token_column]
  payload
end
json_request?() click to toggle source

copied from the jwt feature

# File lib/rodauth/features/oauth_base.rb, line 176
def json_request?
  return @json_request if defined?(@json_request)

  @json_request = request.content_type =~ json_request_regexp
end
json_response_success(body, cache = false) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 678
def json_response_success(body, cache = false)
  response.status = 200
  response["Content-Type"] ||= json_response_content_type
  if cache
    # defaulting to 1-day for everyone, for now at least
    max_age = 60 * 60 * 24
    response["Cache-Control"] = "private, max-age=#{max_age}"
  else
    response["Cache-Control"] = "no-store"
    response["Pragma"] = "no-cache"
  end
  json_payload = _json_response_body(body)
  response.write(json_payload)
  request.halt
end
json_token_introspect_payload(oauth_token) click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_jwt.rb, line 254
def json_token_introspect_payload(oauth_token)
  return { active: false } unless oauth_token

  return super unless oauth_token["sub"] # naive check on whether it's a jwt token

  {
    active: true,
    scope: oauth_token["scope"],
    client_id: oauth_token["client_id"],
    # username
    token_type: "access_token",
    exp: oauth_token["exp"],
    iat: oauth_token["iat"],
    nbf: oauth_token["nbf"],
    sub: oauth_token["sub"],
    aud: oauth_token["aud"],
    iss: oauth_token["iss"],
    jti: oauth_token["jti"]
  }
end
jwk_import(data) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 414
def jwk_import(data)
  JSON::JWK.new(data)
end
jwks_set() click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 498
def jwks_set
  @jwks_set ||= [
    (JSON::JWK.new(oauth_jwt_public_key).merge(use: "sig", alg: oauth_jwt_algorithm) if oauth_jwt_public_key),
    (JSON::JWK.new(oauth_jwt_legacy_public_key).merge(use: "sig", alg: oauth_jwt_legacy_algorithm) if oauth_jwt_legacy_public_key),
    (JSON::JWK.new(oauth_jwt_jwe_public_key).merge(use: "enc", alg: oauth_jwt_jwe_algorithm) if oauth_jwt_jwe_public_key)
  ].compact
end
jwt_assertion(assertion) click to toggle source
# File lib/rodauth/features/oauth_jwt_bearer_grant.rb, line 45
def jwt_assertion(assertion)
  claims = jwt_decode(assertion, verify_iss: false, verify_aud: false)
  return unless verify_aud(token_url, claims["aud"])

  claims
end
jwt_claims(oauth_token) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 205
def jwt_claims(oauth_token)
  issued_at = Time.now.to_i

  claims = {
    iss: issuer, # issuer
    iat: issued_at, # issued at
    #
    # sub  REQUIRED - as defined in section 4.1.2 of [RFC7519].  In case of
    # access tokens obtained through grants where a resource owner is
    # involved, such as the authorization code grant, the value of "sub"
    # SHOULD correspond to the subject identifier of the resource owner.
    # In case of access tokens obtained through grants where no resource
    # owner is involved, such as the client credentials grant, the value
    # of "sub" SHOULD correspond to an identifier the authorization
    # server uses to indicate the client application.
    sub: jwt_subject(oauth_token),
    client_id: oauth_application[oauth_applications_client_id_column],

    exp: issued_at + oauth_token_expires_in,
    aud: (oauth_jwt_audience || oauth_application[oauth_applications_client_id_column])
  }

  claims[:auth_time] = last_account_login_at.to_i if last_account_login_at

  claims
end
jwt_decode( token, jwks: nil, jws_key: oauth_jwt_public_key || _jwt_key, jws_algorithm: oauth_jwt_algorithm, jwe_key: oauth_jwt_jwe_key, jws_encryption_algorithm: oauth_jwt_jwe_algorithm, jws_encryption_method: oauth_jwt_jwe_encryption_method, verify_claims: true, verify_jti: true, verify_iss: true, verify_aud: false, ** ) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 448
def jwt_decode(
  token,
  jwks: nil,
  jws_key: oauth_jwt_public_key || _jwt_key,
  jws_algorithm: oauth_jwt_algorithm,
  jwe_key: oauth_jwt_jwe_key,
  jws_encryption_algorithm: oauth_jwt_jwe_algorithm,
  jws_encryption_method: oauth_jwt_jwe_encryption_method,
  verify_claims: true,
  verify_jti: true,
  verify_iss: true,
  verify_aud: false,
  **
)
  token = JSON::JWT.decode(token, oauth_jwt_jwe_key).plain_text if jwe_key

  claims = if is_authorization_server?
             if oauth_jwt_legacy_public_key
               JSON::JWT.decode(token, JSON::JWK::Set.new({ keys: jwks_set }))
             elsif jwks
               enc_algs = [jws_encryption_algorithm].compact
               enc_meths = [jws_encryption_method].compact
               sig_algs = [jws_algorithm].compact.map(&:to_sym)
               jws = JSON::JWT.decode(token, JSON::JWK::Set.new({ keys: jwks }), enc_algs + sig_algs, enc_meths)
               jws = JSON::JWT.decode(jws.plain_text, JSON::JWK::Set.new({ keys: jwks }), sig_algs) if jws.is_a?(JSON::JWE)
               jws
             elsif jws_key
               JSON::JWT.decode(token, jws_key)
             end
           elsif (jwks = auth_server_jwks_set)
             JSON::JWT.decode(token, JSON::JWK::Set.new(jwks))
           end

  now = Time.now
  if verify_claims && (
      (!claims[:exp] || Time.at(claims[:exp]) < now) &&
      (claims[:nbf] && Time.at(claims[:nbf]) < now) &&
      (claims[:iat] && Time.at(claims[:iat]) < now) &&
      (verify_iss && claims[:iss] != issuer) &&
      (verify_aud && !verify_aud(claims[:aud], claims[:client_id])) &&
      (verify_jti && !verify_jti(claims[:jti], claims))
    )
    return
  end

  claims
rescue JSON::JWT::Exception
  nil
end
jwt_decode_with_jwe( token, jwks: nil, jwe_key: oauth_jwt_jwe_key, jws_encryption_algorithm: oauth_jwt_jwe_algorithm, jws_encryption_method: oauth_jwt_jwe_encryption_method, **args ) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 641
def jwt_decode_with_jwe(
  token,
  jwks: nil,
  jwe_key: oauth_jwt_jwe_key,
  jws_encryption_algorithm: oauth_jwt_jwe_algorithm,
  jws_encryption_method: oauth_jwt_jwe_encryption_method,
  **args
)

  token = if jwks && jwks.any? { |k| k[:use] == "enc" }
            JWE.__rodauth_oauth_decrypt_from_jwks(token, jwks, alg: jws_encryption_algorithm, enc: jws_encryption_method)
          elsif jwe_key
            JWE.decrypt(token, jwe_key)
          else
            token
          end

  jwt_decode_without_jwe(token, jwks: jwks, **args)
rescue JWE::DecodeError => e
  jwt_decode_without_jwe(token, jwks: jwks, **args) if e.message.include?("Not enough or too many segments")
end
jwt_encode(payload, jwks: nil, jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_key, signing_algorithm: oauth_jwt_algorithm, encryption_algorithm: oauth_jwt_jwe_algorithm, encryption_method: oauth_jwt_jwe_encryption_method) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 418
def jwt_encode(payload,
               jwks: nil,
               jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
               signing_algorithm: oauth_jwt_algorithm,
               encryption_algorithm: oauth_jwt_jwe_algorithm,
               encryption_method: oauth_jwt_jwe_encryption_method)
  payload[:jti] = generate_jti(payload)
  jwt = JSON::JWT.new(payload)

  key = oauth_jwt_keys[signing_algorithm] || _jwt_key
  key = key.first if key.is_a?(Array)

  jwk = JSON::JWK.new(key || "")

  jwt = jwt.sign(jwk, signing_algorithm)
  jwt.kid = jwk.thumbprint

  if jwks && (jwk = jwks.find { |k| k[:use] == "enc" && k[:alg] == encryption_algorithm && k[:enc] == encryption_method })
    jwk = JSON::JWK.new(jwk)
    jwe = jwt.encrypt(jwk, encryption_algorithm.to_sym, encryption_method.to_sym)
    jwe.to_s
  elsif jwe_key
    algorithm = encryption_algorithm.to_sym if encryption_algorithm
    meth = encryption_method.to_sym if encryption_method
    jwt.encrypt(jwe_key, algorithm, meth)
  else
    jwt.to_s
  end
end
jwt_encode_with_jwe( payload, jwks: nil, jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_key, encryption_algorithm: oauth_jwt_jwe_algorithm, encryption_method: oauth_jwt_jwe_encryption_method, **args ) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 555
def jwt_encode_with_jwe(
  payload,
  jwks: nil,
  jwe_key: oauth_jwt_jwe_public_key || oauth_jwt_jwe_key,
  encryption_algorithm: oauth_jwt_jwe_algorithm,
  encryption_method: oauth_jwt_jwe_encryption_method, **args
)

  token = jwt_encode_without_jwe(payload, **args)

  return token unless encryption_algorithm && encryption_method

  if jwks && jwks.any? { |k| k[:use] == "enc" }
    JWE.__rodauth_oauth_encrypt_from_jwks(token, jwks, alg: encryption_algorithm, enc: encryption_method)
  elsif jwe_key
    params = {
      zip: "DEF",
      copyright: oauth_jwt_jwe_copyright
    }
    params[:enc] = encryption_method if encryption_method
    params[:alg] = encryption_algorithm if encryption_algorithm
    JWE.encrypt(token, jwe_key, **params)
  else
    token
  end
end
jwt_response_success(jwt, cache = false) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 706
def jwt_response_success(jwt, cache = false)
  response.status = 200
  response["Content-Type"] ||= "application/jwt"
  if cache
    # defaulting to 1-day for everyone, for now at least
    max_age = 60 * 60 * 24
    response["Cache-Control"] = "private, max-age=#{max_age}"
  else
    response["Cache-Control"] = "no-store"
    response["Pragma"] = "no-cache"
  end
  response.write(jwt)
  request.halt
end
jwt_subject(oauth_token) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 232
def jwt_subject(oauth_token)
  subject_type = if oauth_application
                   oauth_application[oauth_applications_subject_type_column] || oauth_jwt_subject_type
                 else
                   oauth_jwt_subject_type
                 end
  case subject_type
  when "public"
    oauth_token[oauth_tokens_account_id_column]
  when "pairwise"
    id = oauth_token[oauth_tokens_account_id_column]
    application_id = oauth_token[oauth_tokens_oauth_application_id_column]
    Digest::SHA256.hexdigest("#{id}#{application_id}#{oauth_jwt_subject_secret}")
  else
    raise StandardError, "unexpected subject (#{subject_type})"
  end
end
last_account_login_at() click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 99
def last_account_login_at
  nil
end
mac_signature_matches?(oauth_token, mac_attributes) click to toggle source
# File lib/rodauth/features/oauth_http_mac.rb, line 52
def mac_signature_matches?(oauth_token, mac_attributes)
  nonce = mac_attributes["nonce"]
  uri = URI(request.url)

  request_signature = [
    nonce,
    request.request_method,
    uri.request_uri,
    uri.host,
    uri.port
  ].join("\n") + ("\n" * 3)

  mac_algorithm = case oauth_mac_algorithm
                  when "hmac-sha-256"
                    OpenSSL::Digest::SHA256
                  when "hmac-sha-1"
                    OpenSSL::Digest::SHA1
                  else
                    raise ArgumentError, "Unsupported algorithm"
                  end

  mac_signature = Base64.strict_encode64 \
    OpenSSL::HMAC.digest(mac_algorithm.new, oauth_token[oauth_tokens_mac_key_column], request_signature)

  mac_signature == mac_attributes["mac"]
end
no_auth_oauth_application?(_oauth_application) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 375
def no_auth_oauth_application?(_oauth_application)
  supported_auth_methods.include?("none")
end
oauth_application() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 204
def oauth_application
  return @oauth_application if defined?(@oauth_application)

  @oauth_application = begin
    client_id = param_or_nil("client_id")

    return unless client_id

    db[oauth_applications_table].filter(oauth_applications_client_id_column => client_id).first
  end
end
oauth_application_jwks() click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 359
def oauth_application_jwks
  jwks = oauth_application[oauth_applications_jwks_column]

  if jwks
    jwks = JSON.parse(jwks, symbolize_names: true) if jwks.is_a?(String)
    return jwks
  end

  jwks_uri = oauth_application[oauth_applications_jwks_uri_column]

  return unless jwks_uri

  jwks_uri = URI(jwks_uri)

  jwks = JWKS[jwks_uri]

  return jwks if jwks

  JWKS.set(jwks_uri) do
    http = Net::HTTP.new(jwks_uri.host, jwks_uri.port)
    http.use_ssl = jwks_uri.scheme == "https"

    request = Net::HTTP::Get.new(jwks_uri.request_uri)
    request["accept"] = json_response_content_type
    response = http.request(request)
    return unless response.code.to_i == 200

    # time-to-live
    ttl = if response.key?("cache-control")
            cache_control = response["cache-control"]
            cache_control[/max-age=(\d+)/, 1].to_i
          elsif response.key?("expires")
            Time.parse(response["expires"]).to_i - Time.now.to_i
          end

    [JSON.parse(response.body, symbolize_names: true), ttl]
  end
end
oauth_application_params() click to toggle source
# File lib/rodauth/features/oauth_application_management.rb, line 150
def oauth_application_params
  @oauth_application_params ||= oauth_application_required_params.each_with_object({}) do |param, params|
    value = request.params[__send__(:"oauth_application_#{param}_param")]
    if value && !value.empty?
      params[param] = value
    else
      set_field_error(param, null_error_message)
    end
  end
end
oauth_application_path(id) click to toggle source
# File lib/rodauth/features/oauth_application_management.rb, line 69
def oauth_application_path(id)
  "#{oauth_applications_path}/#{id}"
end
oauth_applications() click to toggle source

/oauth-applications routes

# File lib/rodauth/features/oauth_application_management.rb, line 74
def oauth_applications
  request.on(oauth_applications_route) do
    require_account

    request.get "new" do
      new_oauth_application_view
    end

    request.on(oauth_applications_id_pattern) do |id|
      oauth_application = db[oauth_applications_table]
                          .where(oauth_applications_id_column => id)
                          .where(oauth_applications_account_id_column => account_id)
                          .first
      next unless oauth_application

      scope.instance_variable_set(:@oauth_application, oauth_application)

      request.is do
        request.get do
          oauth_application_view
        end
      end

      request.on(oauth_applications_oauth_tokens_path) do
        page = Integer(param_or_nil("page") || 1)
        per_page = per_page_param(oauth_tokens_per_page)
        oauth_tokens = db[oauth_tokens_table]
                       .where(oauth_tokens_oauth_application_id_column => id)
                       .order(Sequel.desc(oauth_tokens_id_column))
        scope.instance_variable_set(:@oauth_tokens, oauth_tokens.paginate(page, per_page))
        request.get do
          oauth_application_oauth_tokens_view
        end
      end
    end

    request.get do
      page = Integer(param_or_nil("page") || 1)
      per_page = per_page_param(oauth_applications_per_page)
      scope.instance_variable_set(:@oauth_applications, db[oauth_applications_table]
        .where(oauth_applications_account_id_column => account_id)
        .order(Sequel.desc(oauth_applications_id_column))
        .paginate(page, per_page))

      oauth_applications_view
    end

    request.post do
      catch_error do
        validate_oauth_application_params

        transaction do
          before_create_oauth_application
          id = create_oauth_application
          after_create_oauth_application
          set_notice_flash create_oauth_application_notice_flash
          redirect "#{request.path}/#{id}"
        end
      end
      set_error_flash create_oauth_application_error_flash
      new_oauth_application_view
    end
  end
end
oauth_applications_path(opts = {}) click to toggle source
# File lib/rodauth/features/oauth_application_management.rb, line 58
def oauth_applications_path(opts = {})
  route_path(oauth_applications_route, opts)
end
oauth_applications_url(opts = {}) click to toggle source
# File lib/rodauth/features/oauth_application_management.rb, line 62
def oauth_applications_url(opts = {})
  route_url(oauth_applications_route, opts)
end
oauth_jwt_jwe_algorithms_supported() click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 523
def oauth_jwt_jwe_algorithms_supported
  JWE::VALID_ALG
end
oauth_jwt_jwe_encryption_methods_supported() click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 527
def oauth_jwt_jwe_encryption_methods_supported
  JWE::VALID_ENC
end
oauth_server_metadata(issuer = nil) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 140
def oauth_server_metadata(issuer = nil)
  request.on(".well-known") do
    request.on("oauth-authorization-server") do
      request.get do
        json_response_success(oauth_server_metadata_body(issuer), true)
      end
    end
  end
end
oauth_server_metadata_body(*) click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_application_management.rb, line 219
def oauth_server_metadata_body(*)
  super.tap do |data|
    data[:registration_endpoint] = oauth_applications_url
  end
end
oauth_token_by_refresh_token(token, revoked: false) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 500
def oauth_token_by_refresh_token(token, revoked: false)
  ds = db[oauth_tokens_table]
  #
  # filter expired refresh tokens out.
  # an expired refresh token is a token whose access token expired for a period longer than the
  # refresh token expiration period.
  #
  ds = ds.where(Sequel.date_add(oauth_tokens_expires_in_column, seconds: oauth_refresh_token_expires_in) >= Sequel::CURRENT_TIMESTAMP)

  ds = if oauth_tokens_refresh_token_hash_column
         ds.where(oauth_tokens_refresh_token_hash_column => generate_token_hash(token))
       else
         ds.where(oauth_tokens_refresh_token_column => token)
       end

  ds = ds.where(oauth_tokens_revoked_at_column => nil) unless revoked

  ds.first
end
oauth_token_by_token(token) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 496
def oauth_token_by_token(token)
  oauth_token_by_token_ds(token).first
end
oauth_token_by_token_ds(token) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 483
def oauth_token_by_token_ds(token)
  ds = db[oauth_tokens_table]

  ds = if oauth_tokens_token_hash_column
         ds.where(Sequel[oauth_tokens_table][oauth_tokens_token_hash_column] => generate_token_hash(token))
       else
         ds.where(Sequel[oauth_tokens_table][oauth_tokens_token_column] => token)
       end

  ds.where(Sequel[oauth_tokens_table][oauth_tokens_expires_in_column] >= Sequel::CURRENT_TIMESTAMP)
    .where(Sequel[oauth_tokens_table][oauth_tokens_revoked_at_column] => nil)
end
oauth_token_path(id) click to toggle source
# File lib/rodauth/features/oauth_token_management.rb, line 35
def oauth_token_path(id)
  "#{oauth_tokens_path}/#{id}"
end
oauth_tokens() click to toggle source
# File lib/rodauth/features/oauth_token_management.rb, line 39
def oauth_tokens
  request.on(oauth_tokens_route) do
    require_account

    request.get do
      page = Integer(param_or_nil("page") || 1)
      per_page = per_page_param(oauth_tokens_per_page)

      scope.instance_variable_set(:@oauth_tokens, db[oauth_tokens_table]
        .select(Sequel[oauth_tokens_table].*, Sequel[oauth_applications_table][oauth_applications_name_column])
        .join(oauth_applications_table, Sequel[oauth_tokens_table][oauth_tokens_oauth_application_id_column] =>
          Sequel[oauth_applications_table][oauth_applications_id_column])
        .where(Sequel[oauth_tokens_table][oauth_tokens_account_id_column] => account_id)
        .where(oauth_tokens_revoked_at_column => nil)
        .order(Sequel.desc(oauth_tokens_id_column))
        .paginate(page, per_page))
      oauth_tokens_view
    end

    request.post(oauth_tokens_id_pattern) do |id|
      db[oauth_tokens_table]
        .where(oauth_tokens_id_column => id)
        .where(oauth_tokens_account_id_column => account_id)
        .update(oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP)

      set_notice_flash revoke_oauth_token_notice_flash
      redirect oauth_tokens_path || "/"
    end
  end
end
oauth_tokens_path(opts = {}) click to toggle source
# File lib/rodauth/features/oauth_token_management.rb, line 27
def oauth_tokens_path(opts = {})
  route_path(oauth_tokens_route, opts)
end
oauth_tokens_unique_columns() click to toggle source

OAuth Token Unique/Reuse

# File lib/rodauth/features/oauth_base.rb, line 313
def oauth_tokens_unique_columns
  [
    oauth_tokens_oauth_application_id_column,
    oauth_tokens_account_id_column,
    oauth_tokens_scopes_column
  ]
end
oauth_tokens_url(opts = {}) click to toggle source
# File lib/rodauth/features/oauth_token_management.rb, line 31
def oauth_tokens_url(opts = {})
  route_url(oauth_tokens_route, opts)
end
oauth_unique_id_generator() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 401
def oauth_unique_id_generator
  SecureRandom.urlsafe_base64(32)
end
openid_configuration(alt_issuer = nil) click to toggle source
# File lib/rodauth/features/oidc.rb, line 210
def openid_configuration(alt_issuer = nil)
  request.on(".well-known/openid-configuration") do
    allow_cors(request)

    request.get do
      json_response_success(openid_configuration_body(alt_issuer), cache: true)
    end
  end
end
openid_configuration_body(path = nil) click to toggle source

Metadata

# File lib/rodauth/features/oidc.rb, line 475
def openid_configuration_body(path = nil)
  metadata = oauth_server_metadata_body(path).select do |k, _|
    VALID_METADATA_KEYS.include?(k)
  end

  scope_claims = oauth_application_scopes.each_with_object([]) do |scope, claims|
    oidc, param = scope.split(".", 2)
    if param
      claims << param
    else
      oidc_claims = OIDC_SCOPES_MAP[oidc]
      claims.concat(oidc_claims) if oidc_claims
    end
  end

  scope_claims.unshift("auth_time") if last_account_login_at

  response_types_supported = metadata[:response_types_supported]

  if metadata[:grant_types_supported].include?("implicit")
    response_types_supported += ["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"]
  end

  metadata.merge(
    userinfo_endpoint: userinfo_url,
    end_session_endpoint: (oidc_logout_url if use_rp_initiated_logout?),
    response_types_supported: response_types_supported,
    subject_types_supported: [oauth_jwt_subject_type],

    id_token_signing_alg_values_supported: metadata[:token_endpoint_auth_signing_alg_values_supported],
    id_token_encryption_alg_values_supported: [oauth_jwt_jwe_algorithm].compact,
    id_token_encryption_enc_values_supported: [oauth_jwt_jwe_encryption_method].compact,

    userinfo_signing_alg_values_supported: [],
    userinfo_encryption_alg_values_supported: [],
    userinfo_encryption_enc_values_supported: [],

    request_object_signing_alg_values_supported: [],
    request_object_encryption_alg_values_supported: [],
    request_object_encryption_enc_values_supported: [],

    # These Claim Types are described in Section 5.6 of OpenID Connect Core 1.0 [OpenID.Core].
    # Values defined by this specification are normal, aggregated, and distributed.
    # If omitted, the implementation supports only normal Claims.
    claim_types_supported: %w[normal],
    claims_supported: %w[sub iss iat exp aud] | scope_claims
  ).reject do |key, val|
    # Filter null values in optional items
    (!REQUIRED_METADATA_KEYS.include?(key.to_sym) && val.nil?) ||
      # Claims with zero elements MUST be omitted from the response
      (val.respond_to?(:empty?) && val.empty?)
  end
end
parse_mac_authorization_header_props(token) click to toggle source
# File lib/rodauth/features/oauth_http_mac.rb, line 79
def parse_mac_authorization_header_props(token)
  @mac_authorization_header_props = token.split(/ *, */).each_with_object({}) do |prop, props|
    field, value = prop.split(/ *= */, 2)
    props[field] = value.delete_prefix("\"").delete_suffix("\"")
  end
end
password_hash(password) click to toggle source

From login_requirements_base feature

# File lib/rodauth/features/oauth_base.rb, line 416
def password_hash(password)
  BCrypt::Password.create(password, cost: BCrypt::Engine::DEFAULT_COST)
end
per_page_param(default_per_page) click to toggle source
# File lib/rodauth/features/oauth_management_base.rb, line 56
def per_page_param(default_per_page)
  per_page = param_or_nil("per_page")

  return default_per_page unless per_page

  per_page = per_page.to_i

  return default_per_page if per_page <= 0

  [per_page, default_per_page].min
end
post_configure() click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_base.rb, line 275
def post_configure
  super

  # all of the extensions below involve DB changes. Resource server mode doesn't use
  # database functions for OAuth though.
  return unless is_authorization_server?

  self.class.__send__(:include, Rodauth::OAuth::ExtendDatabase(db))

  # Check whether we can reutilize db entries for the same account / application pair
  one_oauth_token_per_account = db.indexes(oauth_tokens_table).values.any? do |definition|
    definition[:unique] &&
      definition[:columns] == oauth_tokens_unique_columns
  end

  self.class.send(:define_method, :__one_oauth_token_per_account) { one_oauth_token_per_account }

  i18n_register(File.expand_path(File.join(__dir__, "..", "..", "..", "locales"))) if features.include?(:i18n)
end
redirect_response_error(error_code, redirect_url = redirect_uri || request.referer || default_redirect) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 648
def redirect_response_error(error_code, redirect_url = redirect_uri || request.referer || default_redirect)
  if accepts_json?
    status_code = if respond_to?(:"#{error_code}_response_status")
                    send(:"#{error_code}_response_status")
                  else
                    invalid_oauth_response_status
                  end

    throw_json_response_error(status_code, error_code)
  else
    redirect_url = URI.parse(redirect_url)
    query_params = []

    query_params << if respond_to?(:"#{error_code}_error_code")
                      "error=#{send(:"#{error_code}_error_code")}"
                    else
                      "error=#{error_code}"
                    end

    if respond_to?(:"#{error_code}_message")
      message = send(:"#{error_code}_message")
      query_params << ["error_description=#{CGI.escape(message)}"]
    end

    query_params << redirect_url.query if redirect_url.query
    redirect_url.query = query_params.join("&")
    redirect(redirect_url.to_s)
  end
end
redirect_uri() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 195
def redirect_uri
  param_or_nil("redirect_uri") || begin
    return unless oauth_application

    redirect_uris = oauth_application[oauth_applications_redirect_uri_column].split(" ")
    redirect_uris.size == 1 ? redirect_uris.first : nil
  end
end
register_invalid_application_type_message(application_type) click to toggle source
# File lib/rodauth/features/oidc_dynamic_client_registration.rb, line 143
def register_invalid_application_type_message(application_type)
  "The application type '#{application_type}' is not allowed."
end
register_invalid_contacts_message(contacts) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 217
def register_invalid_contacts_message(contacts)
  "The contacts '#{contacts}' are not allowed by this server."
end
register_invalid_grant_type_message(grant_type) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 233
def register_invalid_grant_type_message(grant_type)
  "The grant type #{grant_type} is not allowed by this server."
end
register_invalid_jwks_param_message(key1, key2) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 225
def register_invalid_jwks_param_message(key1, key2)
  "The param '#{key1}' cannot be accepted together with param '#{key2}'."
end
register_invalid_param_message(key) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 213
def register_invalid_param_message(key)
  "The param '#{key}' is not supported by this server."
end
register_invalid_response_type_for_grant_type_message(response_type, grant_type) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 241
def register_invalid_response_type_for_grant_type_message(response_type, grant_type)
  "The grant type '#{grant_type}' must be registered for the response " \
    "type '#{response_type}' to be allowed."
end
register_invalid_response_type_message(response_type) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 237
def register_invalid_response_type_message(response_type)
  "The response type #{response_type} is not allowed by this server."
end
register_invalid_scopes_message(scopes) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 229
def register_invalid_scopes_message(scopes)
  "The given scopes (#{scopes}) are not allowed by this server."
end
register_invalid_uri_message(uri) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 221
def register_invalid_uri_message(uri)
  "The '#{uri}' URL is not allowed by this server."
end
register_required_param_message(key) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 209
def register_required_param_message(key)
  "The param '#{key}' is required by this server."
end
register_throw_json_response_error(code, message) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 205
def register_throw_json_response_error(code, message)
  throw_json_response_error(invalid_oauth_response_status, code, message)
end
registration_metadata() click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 46
def registration_metadata
  oauth_server_metadata_body
end
require_authorizable_account() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 297
def require_authorizable_account
  require_account
end
require_oauth_application() click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_assertion_base.rb, line 26
def require_oauth_application
  if assertion_grant_type?
    @oauth_application = __send__(:"require_oauth_application_from_#{assertion_grant_type}_assertion_issuer", param("assertion"))
  elsif client_assertion_type?
    @oauth_application = __send__(:"require_oauth_application_from_#{client_assertion_type}_assertion_subject",
                                  param("client_assertion"))
  else
    return super
  end

  redirect_response_error("invalid_grant") unless @oauth_application

  if client_assertion_type? &&
     (client_id = param_or_nil("client_id")) &&
     client_id != @oauth_application[oauth_applications_client_id_column]
    # If present, the value of the
    # "client_id" parameter MUST identify the same client as is
    # identified by the client assertion.
    redirect_response_error("invalid_grant")
  end
end
require_oauth_application_from_account() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 379
def require_oauth_application_from_account
  ds = db[oauth_applications_table]
       .join(oauth_tokens_table, Sequel[oauth_tokens_table][oauth_tokens_oauth_application_id_column] =>
                                 Sequel[oauth_applications_table][oauth_applications_id_column])
       .where(oauth_token_by_token_ds(param("token")).opts.fetch(:where, true))
       .where(Sequel[oauth_applications_table][oauth_applications_account_id_column] => account_id)

  @oauth_application = ds.qualify.first
  return if @oauth_application

  set_redirect_error_flash revoke_unauthorized_account_error_flash
  redirect request.referer || "/"
end
require_oauth_application_from_jwt_bearer_assertion_issuer(assertion) click to toggle source
# File lib/rodauth/features/oauth_jwt_bearer_grant.rb, line 17
def require_oauth_application_from_jwt_bearer_assertion_issuer(assertion)
  claims = jwt_assertion(assertion)

  return unless claims

  db[oauth_applications_table].where(
    oauth_applications_client_id_column => claims["iss"]
  ).first
end
require_oauth_application_from_jwt_bearer_assertion_subject(assertion) click to toggle source
# File lib/rodauth/features/oauth_jwt_bearer_grant.rb, line 27
def require_oauth_application_from_jwt_bearer_assertion_subject(assertion)
  claims = jwt_assertion(assertion)

  return unless claims

  db[oauth_applications_table].where(
    oauth_applications_client_id_column => claims["sub"]
  ).first
end
require_oauth_application_from_saml2_bearer_assertion_issuer(assertion) click to toggle source
# File lib/rodauth/features/oauth_saml_bearer_grant.rb, line 27
def require_oauth_application_from_saml2_bearer_assertion_issuer(assertion)
  saml = saml_assertion(assertion)

  return unless saml

  db[oauth_applications_table].where(
    oauth_applications_homepage_url_column => saml.issuers
  ).first
end
require_oauth_application_from_saml2_bearer_assertion_subject(assertion) click to toggle source
# File lib/rodauth/features/oauth_saml_bearer_grant.rb, line 37
def require_oauth_application_from_saml2_bearer_assertion_subject(assertion)
  saml = saml_assertion(assertion)

  return unless saml

  db[oauth_applications_table].where(
    oauth_applications_client_id_column => saml.nameid
  ).first
end
require_oauth_authorization(*scopes) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 252
def require_oauth_authorization(*scopes)
  authorization_required unless authorization_token

  scopes << oauth_application_default_scope if scopes.empty?

  token_scopes = if is_authorization_server?
                   authorization_token[oauth_tokens_scopes_column].split(oauth_scope_separator)
                 else
                   aux_scopes = authorization_token["scope"]
                   if aux_scopes
                     aux_scopes.split(oauth_scope_separator)
                   else
                     []
                   end
                 end

  authorization_required unless scopes.any? { |scope| token_scopes.include?(scope) }
end
rescue_from_uniqueness_error(&block) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 301
def rescue_from_uniqueness_error(&block)
  retries = oauth_unique_id_generation_retries
  begin
    transaction(savepoint: :only, &block)
  rescue Sequel::UniqueConstraintViolation
    redirect_response_error("already_in_use") if retries.zero?
    retries -= 1
    retry
  end
end
revoke_oauth_token() click to toggle source
# File lib/rodauth/features/oauth_token_revocation.rb, line 71
def revoke_oauth_token
  token = param("token")

  oauth_token = if param("token_type_hint") == "refresh_token"
                  oauth_token_by_refresh_token(token)
                else
                  oauth_token_by_token(token)
                end

  redirect_response_error("invalid_request") unless oauth_token

  redirect_response_error("invalid_request") unless token_from_application?(oauth_token, oauth_application)

  update_params = { oauth_tokens_revoked_at_column => Sequel::CURRENT_TIMESTAMP }

  ds = db[oauth_tokens_table].where(oauth_tokens_id_column => oauth_token[oauth_tokens_id_column])

  oauth_token = __update_and_return__(ds, update_params)

  oauth_token[oauth_tokens_token_column] = token
  oauth_token

  # If the particular
  # token is a refresh token and the authorization server supports the
  # revocation of access tokens, then the authorization server SHOULD
  # also invalidate all access tokens based on the same authorization
  # grant
  #
  # we don't need to do anything here, as we revalidate existing tokens
end
saml_assertion(assertion) click to toggle source
# File lib/rodauth/features/oauth_saml_bearer_grant.rb, line 55
def saml_assertion(assertion)
  settings = OneLogin::RubySaml::Settings.new
  settings.idp_cert = oauth_saml_cert
  settings.idp_cert_fingerprint = oauth_saml_cert_fingerprint
  settings.idp_cert_fingerprint_algorithm = oauth_saml_cert_fingerprint_algorithm
  settings.name_identifier_format = oauth_saml_name_identifier_format
  settings.security[:authn_requests_signed] = oauth_saml_security_authn_requests_signed
  settings.security[:metadata_signed] = oauth_saml_security_metadata_signed
  settings.security[:digest_method] = oauth_saml_security_digest_method
  settings.security[:signature_method] = oauth_saml_security_signature_method

  response = OneLogin::RubySaml::Response.new(assertion, settings: settings, skip_recipient_check: true)

  # 3. he Assertion MUST have an expiry that limits the time window ...
  # 4. The Assertion MUST have an expiry that limits the time window ...
  # 5. The <Subject> element MUST contain at least one ...
  # 6. The authorization server MUST reject the entire Assertion if the ...
  # 7. If the Assertion issuer directly authenticated the subject, ...
  redirect_response_error("invalid_grant") unless response.is_valid?

  # In order to issue an access token response as described in OAuth 2.0
  # [RFC6749] or to rely on an Assertion for client authentication, the
  # authorization server MUST validate the Assertion according to the
  # criteria below.

  # 1. The Assertion's <Issuer> element MUST contain a unique identifier
  # for the entity that issued the Assertion.
  redirect_response_error("invalid_grant") unless response.issuers.size == 1

  # 2. in addition to the URI references
  # discussed there, the token endpoint URL of the authorization
  # server MAY be used as a URI that identifies the authorization
  # server as an intended audience.  The authorization server MUST
  # reject any Assertion that does not contain its own identity as
  # the intended audience.
  redirect_response_error("invalid_grant") if response.audiences && !response.audiences.include?(token_url)

  response
end
scopes() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 183
def scopes
  scope = request.params["scope"]
  case scope
  when Array
    scope
  when String
    scope.split(" ")
  when nil
    Array(oauth_application_default_scope)
  end
end
secret_hash(secret) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 397
def secret_hash(secret)
  password_hash(secret)
end
secret_matches?(oauth_application, secret) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 393
def secret_matches?(oauth_application, secret)
  BCrypt::Password.new(oauth_application[oauth_applications_client_secret_column]) == secret
end
session_value() click to toggle source

Overrides session_value, so that a valid authorization token also authenticates a request

Calls superclass method
# File lib/rodauth/features/oauth_base.rb, line 160
def session_value
  super || begin
    return unless authorization_token

    authorization_token[oauth_tokens_account_id_column]
  end
end
supported_grant_type?(grant_type, expected_grant_type = grant_type) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 619
def supported_grant_type?(grant_type, expected_grant_type = grant_type)
  return false unless grant_type == expected_grant_type

  return true unless (grant_types_supported = oauth_application[oauth_applications_grant_types_column])

  grant_types_supported = grant_types_supported.split(/ +/)

  grant_types_supported.include?(grant_type)
end
template_path(page) click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_base.rb, line 325
def template_path(page)
  path = File.join(File.dirname(__FILE__), "../../../templates", "#{page}.str")
  return super unless File.exist?(path)

  path
end
throw_json_response_error(status, error_code, message = nil) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 694
def throw_json_response_error(status, error_code, message = nil)
  set_response_error_status(status)
  code = if respond_to?(:"#{error_code}_error_code")
           send(:"#{error_code}_error_code")
         else
           error_code
         end
  payload = { "error" => code }
  payload["error_description"] = message || (send(:"#{error_code}_message") if respond_to?(:"#{error_code}_message"))
  json_payload = _json_response_body(payload)
  response["Content-Type"] ||= json_response_content_type
  response["WWW-Authenticate"] = oauth_token_type.upcase if status == 401
  response.write(json_payload)
  request.halt
end
token_from_application?(oauth_token, oauth_application) click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 409
def token_from_application?(oauth_token, oauth_application)
  oauth_token[oauth_tokens_oauth_application_id_column] == oauth_application[oauth_applications_id_column]
end
try_approval_prompt() click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 87
def try_approval_prompt
  approval_prompt = param_or_nil("approval_prompt")

  return unless approval_prompt && approval_prompt == "auto"

  return if db[oauth_grants_table].where(
    oauth_grants_account_id_column => account_id,
    oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
    oauth_grants_redirect_uri_column => redirect_uri,
    oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
    oauth_grants_access_type_column => "online"
  ).count.zero?

  # if there's a previous oauth grant for the params combo, it means that this user has approved before.
  request.env["REQUEST_METHOD"] = "POST"
end
try_prompt() click to toggle source

this executes before checking for a logged in account

# File lib/rodauth/features/oidc.rb, line 260
def try_prompt
  prompt = param_or_nil("prompt")

  case prompt
  when "none"
    redirect_response_error("login_required") unless logged_in?

    require_account

    if db[oauth_grants_table].where(
      oauth_grants_account_id_column => account_id,
      oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
      oauth_grants_redirect_uri_column => redirect_uri,
      oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
      oauth_grants_access_type_column => "online"
    ).count.zero?
      redirect_response_error("consent_required")
    end

    request.env["REQUEST_METHOD"] = "POST"
  when "login"
    if logged_in? && request.cookies[oauth_prompt_login_cookie_key] == "login"
      ::Rack::Utils.delete_cookie_header!(response.headers, oauth_prompt_login_cookie_key, oauth_prompt_login_cookie_options)
      return
    end

    # logging out
    clear_session
    set_session_value(login_redirect_session_key, request.fullpath)

    login_cookie_opts = Hash[oauth_prompt_login_cookie_options]
    login_cookie_opts[:value] = "login"
    login_cookie_opts[:expires] = convert_timestamp(Time.now + oauth_prompt_login_interval) # 15 minutes
    ::Rack::Utils.set_cookie_header!(response.headers, oauth_prompt_login_cookie_key, login_cookie_opts)

    redirect require_login_redirect
  when "consent"
    require_account

    if db[oauth_grants_table].where(
      oauth_grants_account_id_column => account_id,
      oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
      oauth_grants_redirect_uri_column => redirect_uri,
      oauth_grants_scopes_column => scopes.join(oauth_scope_separator),
      oauth_grants_access_type_column => "online"
    ).count.zero?
      redirect_response_error("consent_required")
    end
  when "select-account"
    # only works if select_account plugin is available
    require_select_account if respond_to?(:require_select_account)
  else
    redirect_response_error("invalid_request")
  end
end
use_date_arithmetic?() click to toggle source
# File lib/rodauth/features/oauth_base.rb, line 271
def use_date_arithmetic?
  true
end
validate_client_registration_params() click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 50
def validate_client_registration_params
  oauth_client_registration_required_params.each do |required_param|
    unless request.params.key?(required_param)
      register_throw_json_response_error("invalid_client_metadata", register_required_param_message(required_param))
    end
  end
  metadata = registration_metadata

  @oauth_application_params = request.params.each_with_object({}) do |(key, value), params|
    case key
    when "redirect_uris"
      if value.is_a?(Array)
        value = value.each do |uri|
          register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(uri)) unless check_valid_uri?(uri)
        end.join(" ")
      else
        register_throw_json_response_error("invalid_redirect_uri", register_invalid_uri_message(value))
      end
      key = oauth_applications_redirect_uri_column
    when "token_endpoint_auth_method"
      unless oauth_auth_methods_supported.include?(value)
        register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
      end
      # verify if in range
      key = oauth_applications_token_endpoint_auth_method_column
    when "grant_types"
      if value.is_a?(Array)
        value = value.each do |grant_type|
          unless metadata[:grant_types_supported].include?(grant_type)
            register_throw_json_response_error("invalid_client_metadata", register_invalid_grant_type_message(grant_type))
          end
        end.join(" ")
      else
        set_field_error(key, invalid_client_metadata_message)
      end
      key = oauth_applications_grant_types_column
    when "response_types"
      if value.is_a?(Array)
        grant_types = request.params["grant_types"] || metadata[:grant_types_supported]
        value = value.each do |response_type|
          unless metadata[:response_types_supported].include?(response_type)
            register_throw_json_response_error("invalid_client_metadata",
                                               register_invalid_response_type_message(response_type))
          end

          validate_client_registration_response_type(response_type, grant_types)
        end.join(" ")
      else
        set_field_error(key, invalid_client_metadata_message)
      end
      key = oauth_applications_response_types_column
      # verify if in range and match grant type
    when "client_uri", "logo_uri", "tos_uri", "policy_uri", "jwks_uri"
      register_throw_json_response_error("invalid_client_metadata", register_invalid_uri_message(value)) unless check_valid_uri?(value)
      case key
      when "client_uri"
        key = "homepage_url"
      when "jwks_uri"
        if request.params.key?("jwks")
          register_throw_json_response_error("invalid_client_metadata",
                                             register_invalid_jwks_param_message(key, "jwks"))
        end
      end
      key = __send__(:"oauth_applications_#{key}_column")
    when "jwks"
      register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(value)) unless value.is_a?(Hash)
      if request.params.key?("jwks_uri")
        register_throw_json_response_error("invalid_client_metadata",
                                           register_invalid_jwks_param_message(key, "jwks_uri"))
      end

      key = oauth_applications_jwks_column
      value = JSON.dump(value)
    when "scope"
      scopes = value.split(" ") - oauth_application_scopes
      register_throw_json_response_error("invalid_client_metadata", register_invalid_scopes_message(value)) unless scopes.empty?
      key = oauth_applications_scopes_column
      # verify if in range
    when "contacts"
      register_throw_json_response_error("invalid_client_metadata", register_invalid_contacts_message(value)) unless value.is_a?(Array)
      value = value.join(" ")
      key = oauth_applications_contacts_column
    when "client_name"
      key = oauth_applications_name_column
    else
      if respond_to?(:"oauth_applications_#{key}_column")
        property = :"oauth_applications_#{key}_column"
        if PROTECTED_APPLICATION_ATTRIBUTES.include?(property)
          register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
        end
        key = __send__(property)
      elsif !db[oauth_applications_table].columns.include?(key.to_sym)
        register_throw_json_response_error("invalid_client_metadata", register_invalid_param_message(key))
      end
    end
    params[key] = value
  end
end
validate_client_registration_response_type(response_type, grant_types) click to toggle source
# File lib/rodauth/features/oauth_dynamic_client_registration.rb, line 149
def validate_client_registration_response_type(response_type, grant_types)
  case response_type
  when "code"
    unless grant_types.include?("authorization_code")
      register_throw_json_response_error("invalid_client_metadata",
                                         register_invalid_response_type_for_grant_type_message(response_type,
                                                                                               "authorization_code"))
    end
  when "token"
    unless grant_types.include?("implicit")
      register_throw_json_response_error("invalid_client_metadata",
                                         register_invalid_response_type_for_grant_type_message(response_type, "implicit"))
    end
  when "none"
    if grant_types.include?("implicit") || grant_types.include?("authorization_code")
      register_throw_json_response_error("invalid_client_metadata", register_invalid_response_type_message(response_type))
    end
  end
end
validate_oauth_application_params() click to toggle source
# File lib/rodauth/features/oauth_application_management.rb, line 161
def validate_oauth_application_params
  oauth_application_params.each do |key, value|
    if key == oauth_application_homepage_url_param

      set_field_error(key, invalid_url_message) unless check_valid_uri?(value)

    elsif key == oauth_application_redirect_uri_param

      if value.respond_to?(:each)
        value.each do |uri|
          next if uri.empty?

          set_field_error(key, invalid_url_message) unless check_valid_uri?(uri)
        end
      else
        set_field_error(key, invalid_url_message) unless check_valid_uri?(value)
      end
    elsif key == oauth_application_scopes_param

      value.each do |scope|
        set_field_error(key, invalid_scope_message) unless oauth_application_scopes.include?(scope)
      end
    end
  end

  throw :rodauth_error if @field_errors && !@field_errors.empty?
end
validate_oauth_grant_params() click to toggle source
# File lib/rodauth/features/oauth_authorization_code_grant.rb, line 68
def validate_oauth_grant_params
  redirect_response_error("invalid_request", request.referer || default_redirect) unless oauth_application && check_valid_redirect_uri?

  unless oauth_application && check_valid_redirect_uri? && check_valid_access_type? &&
         check_valid_approval_prompt? && check_valid_response_type?
    redirect_response_error("invalid_request")
  end
  redirect_response_error("invalid_scope") unless check_valid_scopes?

  return unless (response_mode = param_or_nil("response_mode")) && response_mode != "form_post"

  redirect_response_error("invalid_request")
end
validate_oauth_introspect_params(token_hint_types = %w[access_token refresh_token].freeze) click to toggle source

Token introspect

# File lib/rodauth/features/oauth_token_introspection.rb, line 49
def validate_oauth_introspect_params(token_hint_types = %w[access_token refresh_token].freeze)
  # check if valid token hint type
  if param_or_nil("token_type_hint") && !token_hint_types.include?(param("token_type_hint"))
    redirect_response_error("unsupported_token_type")
  end

  redirect_response_error("invalid_request") unless param_or_nil("token")
end
validate_oauth_revoke_params() click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_jwt.rb, line 698
def validate_oauth_revoke_params
  token_hint = param_or_nil("token_type_hint")

  throw(:rodauth_error) if !token_hint || token_hint == "access_token"

  super
end
validate_oauth_token_params() click to toggle source
Calls superclass method
# File lib/rodauth/features/oauth_assertion_base.rb, line 20
def validate_oauth_token_params
  return super unless assertion_grant_type?

  redirect_response_error("invalid_grant") unless param_or_nil("assertion")
end
validate_oidc_logout_params() click to toggle source

Logout

# File lib/rodauth/features/oidc.rb, line 463
def validate_oidc_logout_params
  redirect_response_error("invalid_request") unless param_or_nil("id_token_hint")
  # check if valid token hint type
  return unless (redirect_uri = param_or_nil("post_logout_redirect_uri"))

  return if check_valid_uri?(redirect_uri)

  redirect_response_error("invalid_request")
end
validate_pkce_challenge_params() click to toggle source
# File lib/rodauth/features/oauth_pkce.rb, line 64
def validate_pkce_challenge_params
  if param_or_nil("code_challenge")

    challenge_method = param_or_nil("code_challenge_method")
    redirect_response_error("code_challenge_required") unless oauth_pkce_challenge_method == challenge_method
  else
    return unless oauth_require_pkce

    redirect_response_error("code_challenge_required")
  end
end
verify_aud(expected_aud, aud) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 355
def verify_aud(expected_aud, aud)
  expected_aud == aud
end
verify_jti(jti, claims) click to toggle source
# File lib/rodauth/features/oauth_jwt.rb, line 351
def verify_jti(jti, claims)
  generate_jti(claims) == jti
end
webfinger() click to toggle source
# File lib/rodauth/features/oidc.rb, line 220
def webfinger
  request.on(".well-known/webfinger") do
    request.get do
      resource = param_or_nil("resource")

      throw_json_response_error(400, "invalid_request") unless resource

      response.status = 200
      response["Content-Type"] ||= "application/jrd+json"

      json_payload = JSON.dump({
                                 subject: resource,
                                 links: [{
                                   rel: webfinger_relation,
                                   href: authorization_server_url
                                 }]
                               })
      response.write(json_payload)
      request.halt
    end
  end
end