feat(auth-clerk): Add IUserProvider + ISSOProvider + ISessionProvider for Studio login#16659
Conversation
🦋 Changeset detectedLatest commit: 35a315f The changes in this PR will be included in the next version bump. This PR includes changesets to release 21 packages
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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis 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. ChangesClerk SSO and Session Cookie Provider
🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
.changeset/clerk-iuserprovider.mdauth/clerk/src/index.test.tsauth/clerk/src/index.tsexamples/agent/package.jsonexamples/agent/src/mastra/auth/clerk.tsexamples/agent/src/mastra/auth/index.tsexamples/agent/src/mastra/auth/types.ts
|
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 {
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. |
46132f9 to
8da391c
Compare
PR triageLinked issue check skipped for core contributor @rphansen91. PR complexity score
Applied label: Changed test gateChanged tests failed against the base branch as expected. Label: |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
auth/clerk/src/index.test.ts (2)
613-619: ⚡ Quick winAssert the clear-cookie header matches the original cookie scope.
Max-Age=0alone does not prove logout will work. IfgetClearSessionHeaders()ever stops mirroring the original cookie scope/flags (at leastPath, 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 winAdd 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
authenticateTokenand to accept session users identified byidinauthorizeUser; 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
📒 Files selected for processing (3)
.changeset/clerk-iuserprovider.mdauth/clerk/src/index.test.tsauth/clerk/src/index.ts
✅ Files skipped from review due to trivial changes (1)
- .changeset/clerk-iuserprovider.md
There was a problem hiding this comment.
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 winAnchor the
__sessioncookie lookup too.The
__sessionfallback still uses an unanchored regex, so a header likefoo__session=...; __session=realwill read the first value and can shadow the real Clerk cookie. Reuse the same boundary-aware parsing you already added forthis.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 winRequire a stable cookie secret before enabling SSO.
When
CLERK_COOKIE_PASSWORDis absent, this still enables SSO with a per-process random secret. That makes the signedstatetoken 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
ISessionProviderdoes not round-trip the same session.
createSession()returns a random UUID-backedSession,getSessionHeaders()never emits aSet-Cookie,getSessionIdFromRequest()returns the encrypted cookie blob instead of that session id, andvalidateSession()/refreshSession()always returnnull. Any consumer using the genericISessionProvidercontract cannot actually establish, recover, or validate the session it just created outsidehandleCallback(). Either wire these methods to the same cookie payload or avoid attachingISessionProvideruntil 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
📒 Files selected for processing (2)
auth/clerk/src/index.test.tsauth/clerk/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- auth/clerk/src/index.test.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
.changeset/clerk-iuserprovider.md (1)
8-14:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winUse 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 credentialsAs 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
📒 Files selected for processing (6)
.changeset/clerk-iuserprovider.mdauth/clerk/src/index.test.tsauth/clerk/src/index.tspackages/core/src/auth/interfaces/sso.tspackages/core/src/server/composite-auth.tspackages/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
|
@coderabbitai review |
✅ Action performedReview finished.
|
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.
6a94f55 to
35a315f
Compare
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
ISSOProviderandISessionProviderinterfaces.What Changed
auth/clerk/src/index.tsgetCurrentUser(extracts JWT from Authorization header or__sessioncookie, verifies via JWKS),getUser(calls Clerk APIclerk.users.getUser),getUserProfileUrl/oauth/authorize,/oauth/token). IncludesgetLoginUrl,handleCallback,getLoginButtonConfig. Requires a separate Clerk OAuth Application (Client ID + Client Secret) created in the Clerk Dashboard.createSession,validateSession,destroySession,refreshSession,getSessionIdFromRequest,getSessionHeaders,getClearSessionHeadersuser.sub(JWT) anduser.id(session cookie) fieldsoauthClientId+oauthClientSecretare configured, keeping the provider duck-typing safe forbuildCapabilities()detectionauth/clerk/src/index.test.tsauthenticateToken,authorizeUser,getCurrentUser,getUser,getLoginUrl,handleCallback,getLoginButtonConfig, session encryption/decryption, cookie management, state validationConfiguration
Test Plan
pnpm testinauth/clerk)tsc --noEmit)/api/auth/capabilitiesreturnslogin.type=ssowith Clerk provider/api/auth/mereturns authenticated user/api/agentsand other protected routes return 200 with session cookieELI5
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
Tests
Packaging / examples
@mastra/auth-clerkdependency, adds initClerk() example that logs SSO vs JWT-only mode, and registers 'clerk' provider in examples.Core interface updates
Configuration
Dual modes supported:
Example SSO config: publishableKey, jwksUri, secretKey, oauthClientId, oauthClientSecret, session.cookiePassword.
Verification
Notes & highlights