Skip to content

feat: Add optional issuer check to SessionManager authenticate - #440

Merged
gjtorikian merged 2 commits into
mainfrom
devin/1788639144-optional-issuer
Sep 17, 2026
Merged

gjtorikian merged 2 commits into
mainfrom
devin/1788639144-optional-issuer

Conversation

@m0tzy

@m0tzy m0tzy commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Lets apps opt in to validating the iss claim of session access tokens. Part of the cross-SDK rollout started in workos/authkit-react-router#83 (see also workos/workos-node#1694, workos/workos-python#725, workos/workos-ruby#552). Default behavior is unchanged: with no issuer passed, iss is not checked, exactly as before.

SessionManager already takes its session-verification inputs per call (clientId, baseUrl) rather than from client config, so the option follows that shape:

public function authenticate(string $sessionData, string $cookiePassword, string $clientId,
    string $baseUrl = 'https://api.workos.com/',
    string|array|null $issuer = null): array
public function getLogoutUrl(..., string $baseUrl = 'https://api.workos.com/',
    string|array|null $issuer = null): string

decodeAccessToken() gets the same $issuer and, after signature + exp checks, requires $decoded['iss'] to be a string in is_array($issuer) ? $issuer : [$issuer] (strict in_array). A mismatch or missing claim surfaces as the existing ['authenticated' => false, 'reason' => 'invalid_jwt']. An explicit [] fails closed (rejects every token) rather than disabling the check. refresh() does not decode a JWT, so it is untouched.

$workos->sessionManager()->authenticate(
    sessionData: $cookie,
    cookiePassword: $password,
    clientId: $clientId,
    issuer: ["https://api.workos.com/user_management/{$clientId}", 'https://auth.example.com'],
);

Why not a client-level option: WorkOS::__construct and the HttpClient wiring live in generated lib/WorkOS.php (oagen), so a constructor/env option (WORKOS_ISSUER) would need an emitter change upstream; this PR stays inside the hand-maintained SessionManager. The existing finding-#60 TODO about enforcing iss/aud by default is kept and reworded.

Tests: ./script/ci (php-cs-fixer, PHPStan, PHPUnit) passes — 357 tests. New cases cover unset, matching, mismatched, missing iss, list, and empty list.

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 20:12
@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@greptile-apps

greptile-apps Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the optional issuer check is fail-closed when configured and preserves prior behavior by default.

Summary

Adds optional issuer validation to session authentication while preserving existing behavior when no issuer is configured.

  • Accepts either one issuer or a list of permitted issuers.
  • Rejects missing, non-string, mismatched, and explicitly disallowed issuer claims.
  • Propagates issuer validation through logout URL generation.
  • Adds focused coverage for disabled, matching, mismatched, missing, multiple, and empty issuer configurations.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[authenticate session] --> B[Unseal session cookie]
    B --> C[Decode and verify access-token signature]
    C --> D[Validate expiration]
    D --> E{Issuer configured?}
    E -- No --> F[Authentication succeeds]
    E -- Yes --> G{iss is a strictly accepted string?}
    G -- Yes --> F
    G -- No --> H[Return invalid_jwt]
Loading

Reviews (2) · Last reviewed commit: "Merge branch 'main' into devin/178863914..."

@gjtorikian gjtorikian 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.

Thanks!

@gjtorikian
gjtorikian merged commit 47e8606 into main Sep 17, 2026
9 checks passed
@gjtorikian
gjtorikian deleted the devin/1788639144-optional-issuer branch September 17, 2026 16:42
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.

2 participants