Skip to content

feat(auth-clerk): Add IUserProvider + ISSOProvider + ISessionProvider for Studio login#16659

Merged
rphansen91 merged 8 commits into
mainfrom
feat/studio-auth-clerk
Jun 15, 2026
Merged

feat(auth-clerk): Add IUserProvider + ISSOProvider + ISessionProvider for Studio login#16659
rphansen91 merged 8 commits into
mainfrom
feat/studio-auth-clerk

Conversation

@rphansen91

@rphansen91 rphansen91 commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Enables Clerk users to log into Mastra Studio via OAuth SSO. Previously, the Clerk auth provider only supported API token verification — Studio login was not possible because the provider lacked the required ISSOProvider and ISessionProvider interfaces.

What Changed

auth/clerk/src/index.ts

  • IUserProvider: getCurrentUser (extracts JWT from Authorization header or __session cookie, verifies via JWKS), getUser (calls Clerk API clerk.users.getUser), getUserProfileUrl
  • ISSOProvider: OAuth 2.0 Authorization Code flow via Clerk OAuth endpoints (/oauth/authorize, /oauth/token). Includes getLoginUrl, handleCallback, getLoginButtonConfig. Requires a separate Clerk OAuth Application (Client ID + Client Secret) created in the Clerk Dashboard.
  • ISessionProvider: Encrypted cookie sessions using PBKDF2 key derivation + AES-GCM encryption. Includes createSession, validateSession, destroySession, refreshSession, getSessionIdFromRequest, getSessionHeaders, getClearSessionHeaders
  • authenticateToken: Updated to check session cookie first when SSO is enabled, then falls back to JWT verification
  • authorizeUser: Accepts both user.sub (JWT) and user.id (session cookie) fields
  • SSO methods are only attached when oauthClientId + oauthClientSecret are configured, keeping the provider duck-typing safe for buildCapabilities() detection

auth/clerk/src/index.test.ts

  • 48 tests covering all interfaces (up from 13 original tests)
  • Tests for: authenticateToken, authorizeUser, getCurrentUser, getUser, getLoginUrl, handleCallback, getLoginButtonConfig, session encryption/decryption, cookie management, state validation

Configuration

import { MastraAuthClerk } from "@mastra/auth-clerk";

const auth = new MastraAuthClerk({
  publishableKey: process.env.CLERK_PUBLISHABLE_KEY!,
  // Required for Studio SSO login (create OAuth app in Clerk Dashboard)
  oauthClientId: process.env.CLERK_OAUTH_CLIENT_ID,
  oauthClientSecret: process.env.CLERK_OAUTH_CLIENT_SECRET,
  cookiePassword: process.env.CLERK_COOKIE_PASSWORD, // min 32 chars
});

Test Plan

  1. Unit tests: 48 tests passing (pnpm test in auth/clerk)
  2. Type check: Zero errors (tsc --noEmit)
  3. End-to-end: Verified Clerk OAuth login flow against live Clerk tenant
    • /api/auth/capabilities returns login.type=sso with Clerk provider
    • OAuth redirect → Clerk login → callback → session cookie set
    • /api/auth/me returns authenticated user
    • /api/agents and other protected routes return 200 with session cookie
    • Logout clears session cookie

ELI5

This PR lets Mastra Studio users sign in with Clerk via OAuth and keeps them logged in using secure, encrypted cookies, while still supporting Clerk-issued JWTs for API requests.


Overview

Converts MastraAuthClerk from JWT-only verification into a full Clerk-backed provider implementing IUserProvider and, when OAuth credentials are provided, exposing ISSOProvider and ISessionProvider for Studio SSO. Adds encrypted cookie sessions (PBKDF2 → AES‑GCM), stateless HMAC-signed short-lived OAuth state tokens, OAuth Authorization Code handling (authorize → token → userinfo/id_token), and updates auth logic to prefer session cookies when SSO is enabled.

Key changes

  • auth/clerk/src/index.ts

    • Implements IUserProvider:
      • getCurrentUser(request): prefers encrypted session cookie if SSO enabled; otherwise extracts token from Authorization header or Clerk __session cookie, verifies via JWKS, and optionally enriches via Clerk Users API with a JWT-claims fallback.
      • getUser(userId): fetches user via Clerk API (returns null on error).
      • getUserProfileUrl(user): returns profile path.
      • isSSOEnabled(), getFapiUrl().
    • Adds ISSOProvider (attached only when oauthClientId & oauthClientSecret configured):
      • Full OAuth2 Authorization Code flow against Clerk (/oauth/authorize, /oauth/token).
      • getLoginUrl: async-capable; builds authorize URL and encodes stateless signed+expiring state (HMAC).
      • handleCallback: verifies state, exchanges code for tokens (10s fetch timeout), derives user from id_token (via JWKS) or /oauth/userinfo, optionally enriches via Clerk API, returns encrypted session cookie + tokens.
      • getLoginButtonConfig: login button metadata.
    • Adds ISessionProvider (cookie-backed, attached when SSO enabled):
      • Session encryption/decryption using PBKDF2-derived AES‑GCM key; cookie encoded as base64(salt || iv || ciphertext).
      • createSession returns encrypted cookie headers; validateSession/refreshSession are effectively no-ops for this lightweight model; destroySession clears cookie.
      • getSessionIdFromRequest / getSessionHeaders / getClearSessionHeaders for cookie handling.
      • Enforces minimum 32-character cookie password; warns on auto-generated password (ephemeral across restarts).
    • Authentication behavior:
      • authenticateToken(token, request?): when SSO enabled and request provided, tries session cookie first, then falls back to JWKS verification.
      • authorizeUser accepts both JWT-style (payload.sub) and session-cookie-style (id) users.
    • Utilities:
      • deriveFapiUrl(publishableKey) for Clerk FAPI URL.
      • Timing-safe HMAC-SHA256 state signing/verifying replaces earlier approach.
  • Tests

    • auth/clerk/src/index.test.ts expanded from ~13 to 48 tests covering initialization, authenticateToken, authorizeUser (incl. custom/org-based logic), getCurrentUser/getUser, SSO flows (getLoginUrl + state, handleCallback token exchange and userinfo fallback), session encryption/decryption, cookie management, state expiry/validation, and logout cookie clearing.
  • Packaging / examples

    • .changeset adds a minor release note documenting Studio SSO, JWT validation, and encrypted sessions.
    • examples/agent: adds @mastra/auth-clerk dependency, adds initClerk() example that logs SSO vs JWT-only mode, and registers 'clerk' provider in examples.
  • Core interface updates

    • packages/core/src/auth/interfaces/sso.ts: getLoginUrl widened to return string | Promise.
    • CompositeAuth and server SSO handler updated to await/handle async getLoginUrl results.

Configuration

Dual modes supported:

  • JWT-only (API): configure jwksUri + keys.
  • Studio SSO: additionally provide oauthClientId, oauthClientSecret, and session.cookiePassword (min 32 chars) or allow auto-generation (with warning that sessions won’t survive restarts).

Example SSO config: publishableKey, jwksUri, secretKey, oauthClientId, oauthClientSecret, session.cookiePassword.

Verification

  • 48 unit tests added (author reports all passing).
  • TypeScript type-check: zero errors.
  • E2E: author verified Clerk OAuth flow against a live Clerk tenant — OAuth redirect → Clerk login → callback sets encrypted session cookie, /api/auth/me and protected routes succeed with session cookie, logout clears session cookie, and /api/auth/capabilities reports login.type=sso when configured.

Notes & highlights

  • ISSOProvider/ISessionProvider are only attached when oauthClientId and oauthClientSecret are configured (preserves duck-typing for buildCapabilities()).
  • State tokens are stateless, HMAC-signed, short-lived, and verified timing-safely (suitable for serverless/load-balanced deployments).
  • Token exchange uses fetch with a 10s timeout.
  • Session cookie encryption uses PBKDF2 (100k iterations) + AES‑GCM with salt+iv; cookie stores base64(salt||iv||ciphertext).
  • validateSession / refreshSession intentionally no-op for this cookie-backed session model.
  • Recommended deployment: run separate MastraAuthClerk instances for API (JWT-only) and Studio (SSO-enabled) when appropriate.

@changeset-bot

changeset-bot Bot commented May 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 35a315f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@mastra/auth-clerk Minor
@mastra/core Patch
@mastra/server Patch
mastracode Patch
@mastra/mcp-docs-server Patch
@internal/playground Patch
@mastra/client-js Patch
@mastra/opencode Patch
@mastra/longmemeval Patch
@mastra/deployer Patch
@mastra/express Patch
@mastra/fastify Patch
@mastra/hono Patch
@mastra/koa Patch
@mastra/nestjs Patch
mastra Patch
@mastra/deployer-cloud Patch
@mastra/react Patch
@mastra/temporal Patch
@mastra/playground-ui Patch
create-mastra Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented May 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mastra-docs-1.x Ready Ready Preview, Comment Jun 11, 2026 8:23pm
mastra-playground-ui Ready Ready Preview, Comment Jun 11, 2026 8:23pm

Request Review

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR turns the Clerk auth module into a Clerk-backed IUserProvider with optional OAuth/OIDC SSO, encrypted-cookie sessions (PBKDF2 + AES‑GCM), Clerk user enrichment, expanded tests, example wiring, and small core/server API changes to support async SSO login URL generation.

Changes

Clerk SSO and Session Cookie Provider

Layer / File(s) Summary
Documentation and Type Contracts
.changeset/clerk-iuserprovider.md
Changeset documenting new @mastra/auth-clerk release and configuration for SSO, JWT validation, and encrypted session cookies.
Example agent integration
examples/agent/package.json, examples/agent/src/mastra/auth/clerk.ts, examples/agent/src/mastra/auth/index.ts, examples/agent/src/mastra/auth/types.ts
Adds @mastra/auth-clerk dependency/override, initClerk() example module reading env, logs SSO mode, and wires clerk into auth init switch and types.
Test scaffolding and initialization assertions
auth/clerk/src/index.test.ts
Expands fixtures for SSO/non-SSO, adds global fetch mock, broadens Clerk client mocks, and asserts FAPI URL derivation, SSO toggling, and cookie-password length validation.
Authorization and user tests
auth/clerk/src/index.test.ts
Tests custom authorizeUser behavior (permission/org logic) and getCurrentUser/getUser flows including bearer/cookie tokens, JWT fallback, and enrichment failure cases.
SSO and session tests
auth/clerk/src/index.test.ts
Comprehensive SSO test suite: OAuth URL/state, callback error paths, token exchange with cookie issuance, userinfo fallback, session creation/validation, clear-session headers, logout behavior, and FAPI derivation variants.
Crypto helpers and FAPI derivation
auth/clerk/src/index.ts
Adds PBKDF2 + AES‑GCM cookie encryption/decryption helpers, stateless signed state, timing-safe compare, and publishable-key → FAPI URL derivation.
Constructor, config, and SSO wiring
auth/clerk/src/index.ts
Extends options with OAuth/session fields, derives fapiUrl, creates Clerk client, validates or auto-generates cookie password, enforces minimum length, and conditionally attaches ISSO/ISession providers.
IUserProvider implementation
auth/clerk/src/index.ts
authenticateToken supports session-cookie fallback, authorizeUser accepts JWT sub or cookie id, implements getCurrentUser (cookie → bearer → __session), getUser (Clerk API null-on-error), and related helpers.
ISSOProvider implementation
auth/clerk/src/index.ts
Implements getLoginUrl (authorize URL + signed state) and handleCallback (state validation, code→token exchange, id_token/userinfo user derivation, optional Clerk enrichment) and returns encrypted session cookie + tokens.
ISessionProvider implementation
auth/clerk/src/index.ts
Cookie-backed session creation, no-op validate/refresh (cookie-driven), session id extraction from request cookie, and getClearSessionHeaders clearing the cookie.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • abhiaiyer91
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: adding IUserProvider, ISSOProvider, and ISessionProvider for Studio login support to the Clerk auth provider.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/studio-auth-clerk

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/clerk-iuserprovider.md:
- Around line 5-10: The release note is too implementation-focused; reword it to
lead with the user-facing outcomes (what developers can now do in Studio) and
then briefly mention any required configuration, avoiding internal details and
jargon; for example, describe support for Studio login via new interfaces
(IUserProvider, ISSOProvider, ISessionProvider) and features like persistent
login and Clerk SSO, then in one short sentence note that setup requires
configuring Clerk (OAuth app and session cookie settings) rather than explaining
how getCurrentUser or getUser validate tokens or the encryption details for
session cookies.

In `@auth/clerk/src/index.ts`:
- Around line 441-445: The code may store or send an undefined redirect URI
because actualRedirectUri = redirectUri ?? self._redirectUri can be null; update
the logic around actualRedirectUri (used in stateStore.set and wherever it is
later read, e.g., the place referenced at use of actualRedirectUri!) to validate
and fail fast: if neither redirectUri nor self._redirectUri is set, throw a
clear error (or return a rejected promise) instead of coercing with !, and only
call stateStore.set(stateId, { expiresAt: ..., redirectUri: actualRedirectUri })
when actualRedirectUri is non-null; apply the same validation before the later
use at the other occurrence to avoid emitting redirect_uri=undefined.
- Around line 83-87: stateStore is a process-local Map which breaks SSO in
multi-instance deployments; replace it with a cross-instance solution: either
(A) a persistent shared store (e.g., Redis) or (B) signed state tokens. If
choosing Redis, swap the Map usage (stateStore) for async helpers (e.g.,
createState(state, redirectUri, ttl), validateAndConsumeState(state)) that set a
key like oauth:state:<state> with TTL and atomically delete on consume, and
update the /oauth/authorize and callback handlers to call these helpers instead
of reading from stateStore; if choosing signed tokens, implement
signState(payload, secret) to embed redirectUri and expiry and verify with
verifyState(token, secret) in the callback, update callers to use signState when
issuing state and verifyState when validating.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 48a44bcb-77b0-4486-9b14-a758c0bca8e0

📥 Commits

Reviewing files that changed from the base of the PR and between 9fff786 and 46132f9.

📒 Files selected for processing (7)
  • .changeset/clerk-iuserprovider.md
  • auth/clerk/src/index.test.ts
  • auth/clerk/src/index.ts
  • examples/agent/package.json
  • examples/agent/src/mastra/auth/clerk.ts
  • examples/agent/src/mastra/auth/index.ts
  • examples/agent/src/mastra/auth/types.ts

Comment thread .changeset/clerk-iuserprovider.md Outdated
Comment thread auth/clerk/src/index.ts Outdated
Comment thread auth/clerk/src/index.ts Outdated
@rphansen91

Copy link
Copy Markdown
Contributor Author

This PR fixes #15470 - "Can't access Mastra studio when using Clerk as an auth provider for the Mastra server"

The user's issue was that they had Clerk for API auth (multi-tenant JWT) but couldn't use Studio because Clerk didn't support SSO login. They tried CompositeAuth but it didn't work properly.

This PR adds ISSOProvider + ISessionProvider to the Clerk auth provider, enabling:

{
  server: {
    auth: new MastraAuthClerk({ publishableKey, jwksUri }), // API (JWT only)
  },
  studio: {
    auth: new MastraAuthClerk({ 
      publishableKey, 
      oauthClientId, 
      oauthClientSecret,
      cookiePassword 
    }), // Studio SSO login
  }
}

Combined with PR #17722 (dual auth system, merged), users can now have separate auth configs for API customers vs internal team.

@rphansen91 rphansen91 force-pushed the feat/studio-auth-clerk branch from 46132f9 to 8da391c Compare June 10, 2026 21:11
@dane-ai-mastra dane-ai-mastra Bot added the complexity: high High-complexity PR label Jun 10, 2026
@dane-ai-mastra

dane-ai-mastra Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

PR triage

Linked issue check skipped for core contributor @rphansen91.


PR complexity score

Factor Value Score impact
Files changed 10 +20
Lines changed 1283 +60
Author merged PRs 115 -20
Test files changed Yes -10
Final score 50

Applied label: complexity: high


Changed test gate

Changed tests failed against the base branch as expected.

Label: tests: green ✅

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
auth/clerk/src/index.test.ts (2)

613-619: ⚡ Quick win

Assert the clear-cookie header matches the original cookie scope.

Max-Age=0 alone does not prove logout will work. If getClearSessionHeaders() ever stops mirroring the original cookie scope/flags (at least Path, and usually the auth flags too), browsers can keep the session cookie even though this test still passes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/clerk/src/index.test.ts` around lines 613 - 619, The test for
getClearSessionHeaders in the MastraAuthClerk unit should also assert the
cleared Set-Cookie mirrors the original cookie scope/flags, not just Max-Age, so
update the spec to check that headers['Set-Cookie'] includes the original Path
and relevant auth flags (e.g., 'Path=', and at minimum the same 'Secure',
'HttpOnly' and 'SameSite' tokens your production cookies use). Locate the test
that constructs MastraAuthClerk and calls getClearSessionHeaders() and add
assertions verifying the cookie string contains the same Path and flag
substrings as the original cookie configuration used elsewhere in the class
(ensure the assertions reference MastraAuthClerk and getClearSessionHeaders to
keep them in sync).

91-111: ⚡ Quick win

Add coverage for the new session-backed auth contract.

These blocks still only exercise JWT-style inputs. This PR changes the default path to prefer the SSO session cookie in authenticateToken and to accept session users identified by id in authorizeUser; without explicit tests for those two behaviors, a Studio SSO regression can slip through while the suite stays green.

Also applies to: 113-141

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/clerk/src/index.test.ts` around lines 91 - 111, Add tests that exercise
the session-backed SSO path in addition to the JWT path: in index.test.ts extend
the authenticateToken suite to mock the session-cookie retrieval (simulate a
valid SSO session user object) and assert that MastraAuthClerk.authenticateToken
returns the session user and that verifyJwks is not called when a session
exists; likewise add tests for MastraAuthClerk.authorizeUser that pass a
session-style user object with an id field and assert authorization succeeds
(and rejects when id is missing), ensuring both session and JWT flows are
covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@auth/clerk/src/index.ts`:
- Around line 406-407: The cookie lookup uses an unescaped RegExp and can match
substrings of other cookie names; update the code that builds the regex around
this.cookieName (the line creating new RegExp(`${this.cookieName}=([^;]+)`) and
the similar occurrence later) to first escape regex metacharacters in
this.cookieName (or use a regexEscape helper), and then use cookie-boundary
anchors such as `(?:^|;\\s*)` before the name and `(?=;|$)` or capture up to the
next `;` to ensure you only match the exact cookie value for this.cookieName.
Replace both instances (the RegExp construction using this.cookieName at the
shown match and the similar one at the other occurrence) with the
escaped-and-anchored pattern or use a safe split-on-';' approach to reliably
extract the cookie.

---

Nitpick comments:
In `@auth/clerk/src/index.test.ts`:
- Around line 613-619: The test for getClearSessionHeaders in the
MastraAuthClerk unit should also assert the cleared Set-Cookie mirrors the
original cookie scope/flags, not just Max-Age, so update the spec to check that
headers['Set-Cookie'] includes the original Path and relevant auth flags (e.g.,
'Path=', and at minimum the same 'Secure', 'HttpOnly' and 'SameSite' tokens your
production cookies use). Locate the test that constructs MastraAuthClerk and
calls getClearSessionHeaders() and add assertions verifying the cookie string
contains the same Path and flag substrings as the original cookie configuration
used elsewhere in the class (ensure the assertions reference MastraAuthClerk and
getClearSessionHeaders to keep them in sync).
- Around line 91-111: Add tests that exercise the session-backed SSO path in
addition to the JWT path: in index.test.ts extend the authenticateToken suite to
mock the session-cookie retrieval (simulate a valid SSO session user object) and
assert that MastraAuthClerk.authenticateToken returns the session user and that
verifyJwks is not called when a session exists; likewise add tests for
MastraAuthClerk.authorizeUser that pass a session-style user object with an id
field and assert authorization succeeds (and rejects when id is missing),
ensuring both session and JWT flows are covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8087764f-dc78-46e5-9c49-2c590512157d

📥 Commits

Reviewing files that changed from the base of the PR and between 46132f9 and 8da391c.

📒 Files selected for processing (3)
  • .changeset/clerk-iuserprovider.md
  • auth/clerk/src/index.test.ts
  • auth/clerk/src/index.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/clerk-iuserprovider.md

Comment thread auth/clerk/src/index.ts Outdated
@dane-ai-mastra dane-ai-mastra Bot added the tests: green ✅ Changed tests failed against base as expected label Jun 10, 2026
@vercel vercel Bot temporarily deployed to Preview – mastra-playground-ui June 10, 2026 21:41 Inactive
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x June 10, 2026 21:41 Inactive

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
auth/clerk/src/index.ts (3)

397-401: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Anchor the __session cookie lookup too.

The __session fallback still uses an unanchored regex, so a header like foo__session=...; __session=real will read the first value and can shadow the real Clerk cookie. Reuse the same boundary-aware parsing you already added for this.cookieName.

Suggested fix
-      const match = cookie.match(/__session=([^;]+)/);
+      const match = cookie.match(new RegExp(`(?:^|;\\s*)${escapeRegex('__session')}=([^;]+)`));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/clerk/src/index.ts` around lines 397 - 401, The fallback cookie lookup
for the Clerk session uses an unanchored regex on the local variable cookie and
can match substrings (e.g., foo__session); update the match for "__session" to
use the same boundary-aware parsing used for this.cookieName so it only matches
either start-of-string or a semicolon+optional space before "__session" and
captures the value up to the next semicolon, i.e., replace the current
unanchored match on cookie with the anchored boundary-aware pattern used for
this.cookieName.

310-337: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require a stable cookie secret before enabling SSO.

When CLERK_COOKIE_PASSWORD is absent, this still enables SSO with a per-process random secret. That makes the signed state token and encrypted session cookie unverifiable after a restart or on another instance, so the OAuth redirect/callback pair becomes flaky in load-balanced or serverless deployments. Fail fast here, or keep the fallback strictly local-dev only. This undermines the PR’s stated stateless/load-balanced SSO behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/clerk/src/index.ts` around lines 310 - 337, The code currently enables
SSO when oauthClientId/oauthClientSecret exist even if only a per-process random
cookiePassword is used; change the constructor logic around
ssoEnabled/cookiePassword so SSO is only enabled when a stable cookie secret is
provided (either options.session.cookiePassword or
process.env.CLERK_COOKIE_PASSWORD) and that secret meets length requirements.
Specifically, update the ssoEnabled check and the block that validates
cookiePassword (referencing ssoEnabled, cookiePassword,
options?.session?.cookiePassword and process.env.CLERK_COOKIE_PASSWORD) to throw
an error when SSO credentials are present but no stable cookie secret is
configured (keep a dev-only fallback only when NODE_ENV !== 'production' if
desired), and remove/replace the current console.warn with the failure path for
production.

684-729: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

ISessionProvider does not round-trip the same session.

createSession() returns a random UUID-backed Session, getSessionHeaders() never emits a Set-Cookie, getSessionIdFromRequest() returns the encrypted cookie blob instead of that session id, and validateSession() / refreshSession() always return null. Any consumer using the generic ISessionProvider contract cannot actually establish, recover, or validate the session it just created outside handleCallback(). Either wire these methods to the same cookie payload or avoid attaching ISessionProvider until that contract is complete. This conflicts with the PR’s stated cookie-backed session support.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/clerk/src/index.ts` around lines 684 - 729, The ISessionProvider
implementation is inconsistent: createSession generates a UUID but
getSessionIdFromRequest returns the encrypted cookie blob, getSessionHeaders
never sets Set-Cookie, and validateSession/refreshSession always return null, so
sessions can't be round-tripped. Fix by making the provider use the same cookie
payload as the canonical session id: have createSession produce the cookie blob
(encrypting the session payload including id/userId/expiresAt), implement
getSessionHeaders to emit a Set-Cookie with that blob and
self.cookieName/self.cookieMaxAge, implement validateSession to decrypt/verify
the cookie blob and return the original Session, implement refreshSession to
re-issue a refreshed cookie blob if needed, and implement destroySession to
return headers that clear the cookie (Max-Age=0); alternatively, remove
attaching ISessionProvider until these methods (createSession, validateSession,
refreshSession, destroySession, getSessionIdFromRequest, getSessionHeaders) are
implemented consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@auth/clerk/src/index.ts`:
- Around line 95-125: Replace the custom hmacSign with a standard HMAC-SHA256
implementation using the Web Crypto API (crypto.subtle): import the raw secret
key via crypto.subtle.importKey('raw', ...) for algorithm { name: 'HMAC', hash:
'SHA-256' }, then compute the signature with crypto.subtle.sign('HMAC', key,
encoder.encode(data)), and base64url-encode the resulting ArrayBuffer; update
verifyStateToken to compute the HMAC-SHA256 for the incoming payload the same
way and compare signatures in a constant-time manner (or use
crypto.subtle.verify if preferred) instead of relying on the bespoke FNV-based
hmacSign and its 8-byte output so the state token uses a standard MAC primitive
(functions referenced: hmacSign and verifyStateToken).

---

Outside diff comments:
In `@auth/clerk/src/index.ts`:
- Around line 397-401: The fallback cookie lookup for the Clerk session uses an
unanchored regex on the local variable cookie and can match substrings (e.g.,
foo__session); update the match for "__session" to use the same boundary-aware
parsing used for this.cookieName so it only matches either start-of-string or a
semicolon+optional space before "__session" and captures the value up to the
next semicolon, i.e., replace the current unanchored match on cookie with the
anchored boundary-aware pattern used for this.cookieName.
- Around line 310-337: The code currently enables SSO when
oauthClientId/oauthClientSecret exist even if only a per-process random
cookiePassword is used; change the constructor logic around
ssoEnabled/cookiePassword so SSO is only enabled when a stable cookie secret is
provided (either options.session.cookiePassword or
process.env.CLERK_COOKIE_PASSWORD) and that secret meets length requirements.
Specifically, update the ssoEnabled check and the block that validates
cookiePassword (referencing ssoEnabled, cookiePassword,
options?.session?.cookiePassword and process.env.CLERK_COOKIE_PASSWORD) to throw
an error when SSO credentials are present but no stable cookie secret is
configured (keep a dev-only fallback only when NODE_ENV !== 'production' if
desired), and remove/replace the current console.warn with the failure path for
production.
- Around line 684-729: The ISessionProvider implementation is inconsistent:
createSession generates a UUID but getSessionIdFromRequest returns the encrypted
cookie blob, getSessionHeaders never sets Set-Cookie, and
validateSession/refreshSession always return null, so sessions can't be
round-tripped. Fix by making the provider use the same cookie payload as the
canonical session id: have createSession produce the cookie blob (encrypting the
session payload including id/userId/expiresAt), implement getSessionHeaders to
emit a Set-Cookie with that blob and self.cookieName/self.cookieMaxAge,
implement validateSession to decrypt/verify the cookie blob and return the
original Session, implement refreshSession to re-issue a refreshed cookie blob
if needed, and implement destroySession to return headers that clear the cookie
(Max-Age=0); alternatively, remove attaching ISessionProvider until these
methods (createSession, validateSession, refreshSession, destroySession,
getSessionIdFromRequest, getSessionHeaders) are implemented consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8391537b-8280-4a37-af8b-525e2d8001bf

📥 Commits

Reviewing files that changed from the base of the PR and between 8da391c and e3087a6.

📒 Files selected for processing (2)
  • auth/clerk/src/index.test.ts
  • auth/clerk/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • auth/clerk/src/index.test.ts

Comment thread auth/clerk/src/index.ts Outdated

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

♻️ Duplicate comments (1)
.changeset/clerk-iuserprovider.md (1)

8-14: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use plain-language release-note wording (avoid auth acronyms).

Lines 8-14 still lean on acronyms (SSO, OAuth, OIDC, JWT). Please rewrite those bullets/setup lines in plain developer-facing language per changeset style rules.

Suggested rewrite
-**What's new:**
-- **Studio SSO login** — your internal team can now sign in to Mastra Studio using their Clerk accounts via OAuth 2.0/OIDC
-- **JWT validation** — API requests with Clerk-issued JWTs are automatically validated
-- **Session persistence** — Studio sessions are maintained with encrypted cookies (no need to log in repeatedly)
+**What's new:**
+- **Studio sign-in** — your team can now sign in to Mastra Studio with Clerk
+- **API token checks** — API requests with Clerk tokens are validated automatically
+- **Session persistence** — Studio sessions stay active with secure cookies (no repeated sign-in)

 **Setup:**
-1. Create an OAuth Application in your Clerk Dashboard
+1. Create a Clerk app for Studio sign-in in your Clerk Dashboard
 2. Configure the auth provider with your Clerk credentials

As per coding guidelines: “Write short, direct sentences… Avoid technical jargon, and acronyms.” [potential_issue]

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/clerk-iuserprovider.md around lines 8 - 14, Replace the bullets
and setup steps that use acronyms with plain-language developer-facing text:
change "Studio SSO login" to something like "Team sign-in to Mastra Studio using
Clerk accounts", replace "OAuth 2.0/OIDC" with "Clerk account sign-in (standard
provider credentials)", replace "JWT validation" with "Automatic validation of
Clerk-issued API tokens", and replace "Session persistence — Studio sessions are
maintained with encrypted cookies" with a short plain sentence like "Studio
keeps users signed in using encrypted cookies." Also update the "Setup:" steps
to avoid acronyms (e.g., "Create an OAuth Application in your Clerk Dashboard"
-> "Create a Sign-in application in your Clerk Dashboard" and "Configure the
auth provider with your Clerk credentials" -> "Configure the authentication
provider using your Clerk credentials"). Ensure each bullet is a short direct
sentence and remove all occurrences of SSO, OAuth, OIDC, and JWT.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In @.changeset/clerk-iuserprovider.md:
- Around line 8-14: Replace the bullets and setup steps that use acronyms with
plain-language developer-facing text: change "Studio SSO login" to something
like "Team sign-in to Mastra Studio using Clerk accounts", replace "OAuth
2.0/OIDC" with "Clerk account sign-in (standard provider credentials)", replace
"JWT validation" with "Automatic validation of Clerk-issued API tokens", and
replace "Session persistence — Studio sessions are maintained with encrypted
cookies" with a short plain sentence like "Studio keeps users signed in using
encrypted cookies." Also update the "Setup:" steps to avoid acronyms (e.g.,
"Create an OAuth Application in your Clerk Dashboard" -> "Create a Sign-in
application in your Clerk Dashboard" and "Configure the auth provider with your
Clerk credentials" -> "Configure the authentication provider using your Clerk
credentials"). Ensure each bullet is a short direct sentence and remove all
occurrences of SSO, OAuth, OIDC, and JWT.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b293b079-2eb0-4b92-916e-09d957e134a0

📥 Commits

Reviewing files that changed from the base of the PR and between e3087a6 and bdb4672.

📒 Files selected for processing (6)
  • .changeset/clerk-iuserprovider.md
  • auth/clerk/src/index.test.ts
  • auth/clerk/src/index.ts
  • packages/core/src/auth/interfaces/sso.ts
  • packages/core/src/server/composite-auth.ts
  • packages/server/src/server/handlers/auth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • auth/clerk/src/index.test.ts
  • auth/clerk/src/index.ts

@dane-ai-mastra dane-ai-mastra Bot added tests: failing ❌ Changed tests passed against base and removed tests: green ✅ Changed tests failed against base as expected labels Jun 11, 2026
@dane-ai-mastra dane-ai-mastra Bot added tests: green ✅ Changed tests failed against base as expected and removed tests: failing ❌ Changed tests passed against base labels Jun 11, 2026
@vercel vercel Bot temporarily deployed to Preview – mastra-playground-ui June 11, 2026 15:51 Inactive
@vercel vercel Bot temporarily deployed to Preview – mastra-docs-1.x June 11, 2026 15:51 Inactive
@rphansen91

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

rphansen91 and others added 8 commits June 11, 2026 13:14
Implement getCurrentUser and getUser methods so Studio can detect
the logged-in user from existing Clerk JWT tokens.

- getCurrentUser: extracts token from Authorization header or __session
  cookie, verifies via JWKS, fetches full user from Clerk API with
  fallback to JWT claims
- getUser: fetches user details from Clerk Users API by ID
- getUserProfileUrl: returns /user/{id}

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai>
…or Studio login

Implement full Studio login support for Clerk auth provider:

- IUserProvider: getCurrentUser (JWT + session cookie), getUser (Clerk API), getUserProfileUrl
- ISSOProvider: OAuth 2.0/OIDC flow via Clerk as IdP (getLoginUrl, handleCallback, getLoginButtonConfig)
- ISessionProvider: encrypted cookie sessions (PBKDF2 + AES-GCM), create/validate/destroy/refresh
- authenticateToken: check session cookie first, fall back to JWT (matches Okta pattern)
- authorizeUser: accept both JWT users (sub) and session users (id)
- SSO methods dynamically attached only when OAuth credentials configured (duck-typing safe)

Requires CLERK_OAUTH_CLIENT_ID + CLERK_OAUTH_CLIENT_SECRET from Clerk Dashboard OAuth Application.
Session encryption via CLERK_COOKIE_PASSWORD (min 32 chars).

48 tests passing, zero type errors.

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai>
Add clerk.ts auth config, add 'clerk' to AuthProviderType, add @mastra/auth-clerk dependency.

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai>
- Fix empty expired state test with vi.useFakeTimers
- Add JSDoc comments to cookie-only session stubs
- Add warning comment about single-instance in-memory state store
- Add code example to changeset

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-6) <noreply@mastra.ai>
- OAuth state validation now uses stateless signed tokens instead of in-memory Map
- Works correctly in serverless/Lambda and load-balanced environments
- Added fetch timeouts (10s) to prevent indefinite hangs
- Added proper regex escaping for cookie name matching
- Updated tests to use signed state tokens

Co-Authored-By: Mastra Code (anthropic/claude-opus-4-5) <noreply@mastra.ai>
- Replace insecure FNV-1a based hmacSign with proper HMAC-SHA256 via Web Crypto API
- Make ISSOProvider.getLoginUrl async to support cryptographically secure state tokens
- Update tests to use async getLoginUrl calls
- Rewrite changeset to be user-facing

This addresses CodeRabbit's security concern about the custom hash function.
@rphansen91 rphansen91 force-pushed the feat/studio-auth-clerk branch from 6a94f55 to 35a315f Compare June 11, 2026 20:14
@rphansen91 rphansen91 merged commit d8df1f8 into main Jun 15, 2026
95 checks passed
@rphansen91 rphansen91 deleted the feat/studio-auth-clerk branch June 15, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

complexity: high High-complexity PR tests: green ✅ Changed tests failed against base as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants