samuelralak / pkce-challenge-ruby

Code Verifier and Code Challenge generator for use in OAuth2 PKCE authorization flow

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Smaller implementation

stoivo opened this issue · comments

commented

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

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 🙂

@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.

@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