Skip to content

feat: Add optional jwt_issuer option for session token validation - #725

Open
m0tzy wants to merge 2 commits into
mainfrom
devin/1788638290-optional-issuer
Open

feat: Add optional jwt_issuer option for session token validation#725
m0tzy wants to merge 2 commits into
mainfrom
devin/1788638290-optional-issuer

Conversation

@m0tzy

@m0tzy m0tzy commented Sep 5, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in iss claim check to Session / AsyncSession access-token validation (both the authenticate() and refresh() jwt.decode calls). When jwt_issuer is configured on the client it is passed to PyJWT; when unset, behavior is unchanged (no iss check — PyJWT skips _validate_iss when issuer is None).

WorkOSClient(..., jwt_issuer: Optional[Union[str, Sequence[str]]] = None)
AsyncWorkOSClient(..., jwt_issuer=...)
# falls back to WORKOS_ISSUER, comma-separated for a list

jwt.decode(access_token, key, algorithms=..., options={"verify_aud": False},
           leeway=self._client._jwt_leeway,
           issuer=self._client._jwt_issuer)   # None -> not validated

jwt_issuer accepts a single issuer or a list so an app can accept tokens from more than one issuer (e.g. during an issuer migration); PyJWT's issuer parameter already accepts str | Container[str]. A WORKOS_ISSUER env value is split on commas (trimmed, empties dropped; a single value stays a str). Named jwt_issuer to sit alongside the existing jwt_leeway option.

Only hand-maintained (@oagen-ignore-file) files are touched: _base_client.py, session.py.

Opt-in rather than defaulted because the API does not mint a single issuer shape — it varies by environment (https://api.workos.com for legacy environments, https://api.workos.com/user_management/<clientId> for environments created since mid-2025, custom auth domains, and flag-gated path variants). Mirrors workos/workos-node#1694.

Link to Devin session: https://app.devin.ai/sessions/0ee38e859a9849658a7cdb2d215d89a6
Open in Devin Desktop: https://app.devin.ai/desktop/session/0ee38e859a9849658a7cdb2d215d89a6?variant=devin
Requested by: @m0tzy

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@m0tzy
m0tzy requested review from a team as code owners September 5, 2026 19:58
@m0tzy
m0tzy requested a review from dandorman September 5, 2026 19:58
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Original prompt from madison.packer

can we patch this SDK so that the issuer can be either by default (if not passed) or passed a specific issuer?

const issuer = opts.issuer ?? https://${getConfig('apiHostname')}

workos/authkit-react-router#83

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Devin Review

Comment thread src/workos/_base_client.py Outdated
@greptile-apps

greptile-apps Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds optional issuer validation for session access tokens while preserving existing behavior when no issuer is configured.

  • Exposes jwt_issuer on synchronous and asynchronous clients, with support for one issuer or a sequence.
  • Parses WORKOS_ISSUER as a trimmed comma-separated fallback.
  • Applies issuer checks consistently during authentication and token refresh.
  • Documents the configuration and tests authentication and refresh behavior for both client implementations.

Confidence Score: 5/5

The PR appears safe to merge; issuer validation is consistently applied and the previously missing refresh coverage has been added.

No actionable new failures or repository-rule violations remain. The previous refresh-testing finding is fully addressed with matching and mismatched issuer tests for both synchronous and asynchronous refresh paths.

Important Files Changed

Filename Overview
src/workos/_base_client.py Adds client-level issuer configuration and environment-variable parsing for synchronous and asynchronous clients.
src/workos/session.py Passes the configured issuer constraint to all four session-token decode paths.
tests/test_session.py Covers configured, absent, multiple, mismatched, empty, environment-derived, synchronous, asynchronous, authentication, and refresh cases.
README.md Documents the optional WORKOS_ISSUER environment configuration and comma-separated format.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Client configuration] --> B{jwt_issuer provided?}
    B -->|Yes| C[Use configured issuer or sequence]
    B -->|No| D{WORKOS_ISSUER set?}
    D -->|Yes| E[Parse comma-separated issuers]
    D -->|No| F[Disable issuer validation]
    C --> G[Authenticate or refresh]
    E --> G
    F --> G
    G --> H[Verify signature, lifetime, and optional issuer]
    H -->|Valid| I[Return session success]
    H -->|Invalid| J[Return invalid JWT failure]
Loading

Reviews (2): Last reviewed commit: "Keep empty jwt_issuer lists and cover re..." | Re-trigger Greptile

Comment thread src/workos/session.py
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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