Smaller implementation
stoivo opened this issue · comments
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.
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