Skip to content

Conversation

@adam-h
Copy link
Contributor

@adam-h adam-h commented Feb 4, 2026

Via an optional block param. E.g.

result = session.authenticate do |jwt|
  { my_custom_claim: jwt['custom_claim'], my_other_claim: jwt['another_claim'] }
end
puts result[:my_custom_claim]

For use with JWT Templates [1] allowing additional properties to be included in the JWT.

Alternatively we could include the whole raw decoded token if an argument is passed with something like:

  claims: include_raw_claims ? decoded : nil,
``
While simpler, it felt cleaner to allow extracting just what's needed.

1: https://workos.com/docs/authkit/jwt-templates

## Documentation

Does this require changes to the WorkOS Docs? E.g. the [API Reference](https://workos.com/docs/reference) or code snippets need updates.

[ ] Yes


If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required.

Via an optional block param. E.g.

```rb
result = session.authenticate do |jwt|
  { my_custom_claim: jwt['custom_claim'], my_other_claim: jwt['another_claim'] }
end
puts result[:my_custom_claim]
```

For use with JWT Templates [1] allowing additional properties to be included in the JWT.

Alternatively we could include the whole raw decoded token if an argument is passed with something like:
```rb
  claims: include_raw_claims ? decoded : nil,
``
While simpler, it felt cleaner to allow extracting just what's needed.

1: https://workos.com/docs/authkit/jwt-templates
@adam-h adam-h requested a review from a team as a code owner February 4, 2026 06:58
@adam-h adam-h requested review from nicknisi and removed request for a team February 4, 2026 06:58
@greptile-apps
Copy link

greptile-apps bot commented Feb 4, 2026

Greptile Overview

Greptile Summary

This PR extends WorkOS::Session#authenticate to accept an optional block that receives the decoded JWT payload and can return a hash of custom claims to merge into the authentication result. A new spec covers the happy path where a block extracts additional JWT template claims.

Main concern is that the merged hash is currently unvalidated and can (a) raise when the block returns nil/non-hash and (b) overwrite reserved fields like :authenticated/:session_id, producing misleading auth results. Tightening the merge contract (type check + collision handling or namespacing) would make the API safer and more predictable.

Confidence Score: 3/5

  • This PR is mergeable but the new block API has sharp edges that can produce misleading or failing auth results in common caller mistakes.
  • Core change is small and tests cover the happy path, but merge! on an unvalidated block return can raise (e.g., nil/non-Hash) and the block can overwrite reserved result keys like :authenticated and :session_id, which could confuse downstream authorization logic. Addressing these would make behavior safer and more predictable.
  • lib/workos/session.rb

Important Files Changed

Filename Overview
lib/workos/session.rb Adds optional claim-extractor block to authenticate and merges its result into the response; currently unvalidated merge can raise on non-Hash/nil and allows overwriting reserved result keys.
spec/lib/workos/session_spec.rb Adds a happy-path spec asserting custom claims from an authenticate block are merged into the result; does not cover non-Hash returns or key-collision behavior.

Sequence Diagram

sequenceDiagram
    participant C as Caller
    participant S as WorkOS::Session
    participant Sealer as Session.unseal_data
    participant J as JWT
    participant B as claim_extractor block

    C->>S: authenticate(include_expired:, &block)
    alt session_data nil
        S-->>C: {authenticated:false, reason:'NO_SESSION_COOKIE_PROVIDED'}
    else
        S->>Sealer: unseal_data(session_data, cookie_password)
        alt invalid cookie
            S-->>C: {authenticated:false, reason:'INVALID_SESSION_COOKIE'}
        else
            S->>J: decode(access_token, jwks, verify_expiration:false)
            alt decode fails
                S-->>C: {authenticated:false, reason:'INVALID_JWT'}
            else
                J-->>S: decoded claims
                S->>S: expired? (decoded['exp'] < now)
                alt expired && !include_expired
                    S-->>C: {authenticated:false, reason:'INVALID_JWT'}
                else
                    S->>S: build result hash
                    opt block_given?
                        S->>B: call(decoded)
                        B-->>S: custom hash
                        S->>S: result.merge!(custom hash)
                    end
                    S-->>C: result
                end
            end
        end
    end
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

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.

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.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant