Switch Access / Refresh tokens / Auth Code from bcrypt ( and plain ) to hmac. BCrypt is for low entropy passwords and prevents dictionary attacks - HMAC is suitable for 256-bit random data.
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
Dan Milne
2025-12-31 15:48:32 +11:00
parent 7c6ae7ab7e
commit 3db466f5a2
6 changed files with 57 additions and 86 deletions

View File

@@ -1,15 +1,12 @@
class OidcAccessToken < ApplicationRecord
include TokenPrefixable
belongs_to :application
belongs_to :user
has_many :oidc_refresh_tokens, dependent: :destroy
before_validation :generate_token_with_prefix, on: :create
before_validation :generate_token, on: :create
before_validation :set_expiry, on: :create
validates :token_digest, presence: true
validates :token_prefix, presence: true
validates :token_hmac, presence: true, uniqueness: true
scope :valid, -> { where("expires_at > ?", Time.current).where(revoked_at: nil) }
scope :expired, -> { where("expires_at <= ?", Time.current) }
@@ -18,6 +15,19 @@ class OidcAccessToken < ApplicationRecord
attr_accessor :plaintext_token # Store plaintext temporarily for returning to client
# Find access token by plaintext token using HMAC verification
def self.find_by_token(plaintext_token)
return nil if plaintext_token.blank?
token_hmac = compute_token_hmac(plaintext_token)
find_by(token_hmac: token_hmac)
end
# Compute HMAC for token lookup
def self.compute_token_hmac(plaintext_token)
OpenSSL::HMAC.hexdigest('SHA256', TokenHmac::KEY, plaintext_token)
end
def expired?
expires_at <= Time.current
end
@@ -36,11 +46,15 @@ class OidcAccessToken < ApplicationRecord
oidc_refresh_tokens.each(&:revoke!)
end
# find_by_token, token_matches?, and generate_token_with_prefix
# are now provided by TokenPrefixable concern
private
def generate_token
# Generate random plaintext token
self.plaintext_token ||= SecureRandom.urlsafe_base64(48)
# Store HMAC in database (not plaintext)
self.token_hmac ||= self.class.compute_token_hmac(plaintext_token)
end
def set_expiry
self.expires_at ||= application.access_token_expiry
end