Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smaller implementation #8

Open
stoivo opened this issue Jun 16, 2023 · 3 comments
Open

Smaller implementation #8

stoivo opened this issue Jun 16, 2023 · 3 comments

Comments

@stoivo
Copy link

stoivo commented Jun 16, 2023

Hi, I wanted to really understand how PKCE worked. While doing it I changed the code. I want to submit my proposal here. I don't think you want to change the projectet as it seam "old". Just want to leave it here in case someone want a newer take on it.

Thanks for the initial project. Great inspiration

# https://github.com/samuelralak/pkce-challenge-ruby/
  module PkceChallenge
    # Generate code challenge and verfiier
    #
    # Example:
    #   >> PkceChallenge.challenge
    #   => #<PkceChallenge::Challenge:0x00007f894f810378 @char_length=48, @code_verifier="QbS08cDO9pce~HVCKe9-UDiJoBMG8xwql4FI.Y3CIdpyJtPU", @code_challenge="HT90mmypkXgneRUVK-Ja009VvnoL-flydbEgRcTp5Yw">
    #
    # == Parameters:
    # options::
    #   A Hash containing optional arguments. Supported options
    #   can include `:length`
    #
    # == Returns:
    # An instance of PkceChallenge::Challenge
    #
    def self.challenge(options = {})
      PkceChallenge::Challenge.new(options)
    end

    module Types
      include Dry::Types()
    end

    class Challenge
      attr_reader :verifier, :code_challenge

      Schema = Types::Hash.schema(length?: Dry::Types['integer'].constrained(gteq: 43, lteq: 128).default(48))

      def initialize(options = {})
        @options = Schema.call(options)

        @verifier ||= SecureRandom.alphanumeric(@options[:length]).length
        @code_challenge ||= Digest::SHA256.digest(@verifier).then {Base64.urlsafe_encode64(_1, padding: false)}
      end
    end
  end
@equivalent
Copy link

equivalent commented Mar 14, 2024

we can go even simpler:

    class PkceChallenge
      attr_reader :verifier, :code_challenge

      def verifier
        @verifier ||= SecureRandom.alphanumeric(48)
      end

      def code_challenge
        # old code with typo (discussion bellow): # @code_challenge ||= Digest::SHA256.digest(@verifier).then {Base64.urlsafe_encode64(_1, padding: false)}
        @code_challenge ||= Digest::SHA256.digest(verifier).then {Base64.urlsafe_encode64(_1, padding: false)}
      end
    end
pkce_challenge = PkceChallenge.new
pkce_challenge.verifier
pkce_challenge.code_challenge

I've implemented this in my app

thx both for the idea 🙂

@stoivo
Copy link
Author

stoivo commented Mar 15, 2024

@equivalent. I'm not sure I agree its simpler, but it doesn't matter.

I think you should set verifier and code_challenge in initialize, the current implementation would fail if you call code_challenge first. Alternatively use the verifier method instead of '@Verifier in code_challenge.

@equivalent
Copy link

@stoivo

the current implementation would fail if you call code_challenge first Alternatively use the verifier method instead of '@Verifier in code_challenge.

yes there is a typo, instead of Digest::SHA256.digest(@verifier) it should be Digest::SHA256.digest(verifier) I'll update code in my comment ☝️

Anyway this is what I use in prod:

class PkceChallenge
  attr_reader :verifier

  def initialize(verifier: nil)
    @verifier ||= verifier || SecureRandom.alphanumeric(48)
  end

  def code_challenge
    @code_challenge ||= Digest::SHA256.digest(verifier).then {Base64.urlsafe_encode64(_1, padding: false)}
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants