Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/workos/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ def initialize(user_management:, client_id:, session_data:, cookie_password:)

# Authenticates the user based on the session data
# @param include_expired [Boolean] If true, returns decoded token data even when expired (default: false)
# @param block [Proc] Optional block to call to extract additional claims from the decoded JWT
# @return [Hash] A hash containing the authentication response and a reason if the authentication failed
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
def authenticate(include_expired: false)
def authenticate(include_expired: false, &claim_extractor)
return { authenticated: false, reason: 'NO_SESSION_COOKIE_PROVIDED' } if @session_data.nil?

begin
Expand All @@ -59,7 +60,7 @@ def authenticate(include_expired: false)
return { authenticated: false, reason: 'INVALID_JWT' } if expired && !include_expired

# Return full data for valid tokens or when include_expired is true
{
result = {
authenticated: !expired,
session_id: decoded['sid'],
organization_id: decoded['org_id'],
Expand All @@ -72,6 +73,8 @@ def authenticate(include_expired: false)
impersonator: session[:impersonator],
reason: expired ? 'INVALID_JWT' : nil,
}
result.merge!(claim_extractor.call(decoded)) if block_given?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P0] merge! will raise if the block returns a non-Hash (including nil), which then gets caught by the generic rescue StandardError and turned into { authenticated: false, reason: e.message }. This makes the new block API brittle and can unexpectedly fail authentication for otherwise-valid sessions if a caller forgets to return a hash.

Consider treating nil as “no extra claims” and validating the return type before merging.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/workos/session.rb
Line: 76:76

Comment:
[P0] `merge!` will raise if the block returns a non-Hash (including `nil`), which then gets caught by the generic `rescue StandardError` and turned into `{ authenticated: false, reason: e.message }`. This makes the new block API brittle and can unexpectedly fail authentication for otherwise-valid sessions if a caller forgets to return a hash.

Consider treating `nil` as “no extra claims” and validating the return type before merging.

How can I resolve this? If you propose a fix, please make it concise.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] The block’s returned hash can overwrite core fields like :authenticated, :session_id, :organization_id, and :reason because merge! prefers the block’s keys. If consumers pass through untrusted logic or make a mistake, this can produce inconsistent or misleading auth results.

A safer pattern is to merge under a dedicated key (e.g. :custom_claims) or explicitly reject/ignore collisions with reserved keys.

Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/workos/session.rb
Line: 76:76

Comment:
[P1] The block’s returned hash can overwrite core fields like `:authenticated`, `:session_id`, `:organization_id`, and `:reason` because `merge!` prefers the block’s keys. If consumers pass through untrusted logic or make a mistake, this can produce inconsistent or misleading auth results.

A safer pattern is to merge under a dedicated key (e.g. `:custom_claims`) or explicitly reject/ignore collisions with reserved keys.

How can I resolve this? If you propose a fix, please make it concise.

result
rescue JWT::DecodeError
{ authenticated: false, reason: 'INVALID_JWT' }
rescue StandardError => e
Expand Down
23 changes: 23 additions & 0 deletions spec/lib/workos/session_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,29 @@
})
end

it 'merges custom claims from claim_extractor block' do
custom_payload = payload.merge(custom_claim: 'custom_value', another_claim: 123)
custom_access_token = JWT.encode(custom_payload, jwk.signing_key, jwk[:alg], { kid: jwk[:kid] })
custom_session_data = WorkOS::Session.seal_data({
access_token: custom_access_token,
user: 'user',
impersonator: 'impersonator',
}, cookie_password,)
session = WorkOS::Session.new(
user_management: user_management,
client_id: client_id,
session_data: custom_session_data,
cookie_password: cookie_password,
)
allow_any_instance_of(JWT::Decode).to receive(:verify_signature).and_return(true)
result = session.authenticate do |jwt|
{ my_custom_claim: jwt['custom_claim'], my_other_claim: jwt['another_claim'] }
end
expect(result[:authenticated]).to be true
expect(result[:my_custom_claim]).to eq('custom_value')
expect(result[:my_other_claim]).to eq(123)
end

describe 'with entitlements' do
let(:payload) do
{
Expand Down