Skip to content

feat(core): support revokeToken in stateful strategy#234

Merged
halvaradop merged 4 commits into
masterfrom
feat/support-revoke-token-stateful
Jul 25, 2026
Merged

feat(core): support revokeToken in stateful strategy#234
halvaradop merged 4 commits into
masterfrom
feat/support-revoke-token-stateful

Conversation

@halvaradop

@halvaradop halvaradop commented Jul 24, 2026

Copy link
Copy Markdown
Member

Description

This pull request adds support for revokeToken() in the experimental Stateful session strategy.

With this change, revokeToken() is now fully supported by both available session strategies:

  • Stateless (JWT)
  • Stateful (Database)

The implementation ensures consistent behavior across both strategies, allowing applications to revoke OAuth or OpenID Connect (OIDC) provider tokens regardless of the configured session storage mechanism.

Key Changes

  • Added Stateful session support for revokeToken().
  • Verified consistent behavior between the Stateless and Stateful session strategies.
  • Extended provider token management support for database-backed sessions.

Note

This PR is part of the ongoing effort to implement the Stateful session strategy. To keep reviews focused and manageable, the implementation has been split into a series of smaller pull requests, each covering a specific aspect of the feature.

Related PRs

@coderabbitai ignore

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
auth Skipped Skipped Jul 25, 2026 2:13am

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds OAuth token revocation to stateful and stateless session strategies, delegates the API handler to those strategies, introduces shared provider revocation and error handling, fixes array merging, and adds extensive action/API tests.

Changes

OAuth token revocation

Layer / File(s) Summary
Revocation contract and API delegation
packages/core/src/@types/session.ts, packages/core/src/api/revokeToken.ts
Adds SessionStrategy.revokeToken and delegates revocation, header generation, and response handling to the configured strategy.
Provider revocation and error contracts
packages/core/src/shared/utils/revoke-token.ts, packages/core/src/shared/errors.ts, packages/core/src/shared/logger.ts, packages/core/src/shared/utils.ts
Adds provider token revocation, OAuth unlinking errors and logging, and excludes arrays from recursive merges.
Stateful and stateless strategy revocation
packages/core/src/session/stateful.ts, packages/core/src/session/stateless.ts
Revokes provider tokens, unlinks stateful accounts, clears access-token cookies, and returns updated headers.
Revoke action validation and provider behavior
packages/core/test/actions/providers/tokens/revoke/stateful.test.ts
Tests validation, CSRF, sessions, cookies, provider responses, network failures, and custom revoke configurations.
Stateful API lifecycle and CSRF coverage
packages/core/test/api/stateful/revokeToken.test.ts, packages/core/test/presets.ts
Tests unlinking, disconnect-only flows, lifecycle handling, response mapping, custom configuration, and double-submit CSRF behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API
  participant SessionStrategy
  participant OAuthProvider
  participant Adapter
  Client->>API: Request OAuth token revocation
  API->>SessionStrategy: revokeToken(oauth, headers, disconnect)
  SessionStrategy->>OAuthProvider: Revoke access token
  OAuthProvider-->>SessionStrategy: Revocation response
  SessionStrategy->>Adapter: Unlink OAuth account
  SessionStrategy-->>API: Updated headers
  API-->>Client: Success or mapped error response
Loading

Possibly related PRs

Suggested labels: feature

🚥 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 matches a real core change, though it only highlights the stateful strategy and omits the broader revokeToken work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/support-revoke-token-stateful

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
packages/core/test/actions/providers/tokens/revoke/stateful.test.ts (1)

581-593: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Body contents are never asserted, so tokenHint mapping is untested.

expect.any(URLSearchParams) passes regardless of content. Since this test exists specifically to cover params.tokenHint/extra params, assert the serialized body to confirm token_type_hint=refresh_token and that tokenHint isn't leaked as a raw param.

🧪 Proposed assertion
         expect(mockFetch).toHaveBeenCalled()
+        const body = mockFetch.mock.calls[0][1].body as URLSearchParams
+        expect(body.get("token_type_hint")).toBe("refresh_token")
+        expect(body.get("tokenHint")).toBeNull()
         expect(mockFetch).toHaveBeenCalledWith(
🤖 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 `@packages/core/test/actions/providers/tokens/revoke/stateful.test.ts` around
lines 581 - 593, Strengthen the request assertion in the token revocation test
by inspecting the URLSearchParams body rather than only matching its type.
Verify it serializes token_type_hint=refresh_token and does not contain a raw
tokenHint parameter, while preserving the existing request expectations.
packages/core/test/api/stateful/revokeToken.test.ts (2)

192-203: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the oauthAccountEntity preset instead of re-declaring it 8 times.

This identical literal is repeated at Lines 256-267, 312-323, 363-374, 417-428, 471-482, 630-641, 690-701, 767-778 and duplicates the oauthAccountEntity fixture added in packages/core/test/presets.ts (Lines 119-130), which this file already imports from.

♻️ Proposed refactor
-import { authInstance, jose, oauthCustomService, sessionEntityWithUser } from "`@test/presets.ts`"
+import { authInstance, jose, oauthAccountEntity, oauthCustomService, sessionEntityWithUser } from "`@test/presets.ts`"
-        const getOAuthAccountMock = vi.fn().mockResolvedValue({
-            accountId: "account-123",
-            accessToken: "access-token",
-            refreshToken: "refresh-token",
-            idToken: "id-token",
-            tokenType: "Bearer",
-            scopes: "scope1 scope2",
-            issuer: "https://example.com",
-            accessTokenExpiresAt: new Date(Date.now() + 3600 * 1000),
-            refreshTokenExpiresAt: new Date(Date.now() + 7200 * 1000),
-            updatedAt: new Date(),
-        })
+        const getOAuthAccountMock = vi.fn().mockResolvedValue(oauthAccountEntity)
🤖 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 `@packages/core/test/api/stateful/revokeToken.test.ts` around lines 192 - 203,
The revoke-token tests repeatedly redeclare the same OAuth account fixture
instead of using the existing preset. Replace each identical getOAuthAccountMock
resolved value in the affected test cases with the imported oauthAccountEntity
preset, preserving any test-specific overrides through the established
fixture-copy pattern.

22-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reset the global Fetch stubs between these tests. packages/core/vitest.config.ts enables unstubEnvs, so vi.stubEnv("BASE_URL", ...) is isolated, but it does not restore vi.stubGlobal("fetch", ...) unless unstubGlobals is enabled globally. Adding afterEach(() => vi.unstubAllGlobals()) in these describe blocks is enough to keep mockResolvedValueOnce/mockRejectedValueOnce mocks from leaking into later cases.

🤖 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 `@packages/core/test/api/stateful/revokeToken.test.ts` at line 22, Add
afterEach cleanup calling vi.unstubAllGlobals() in the describe blocks covering
revokeToken tests in packages/core/test/api/stateful/revokeToken.test.ts and
packages/core/test/actions/providers/tokens/revoke/stateful.test.ts, so fetch
stubs and one-time mock values are reset between tests.
🤖 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 `@packages/core/src/`@types/session.ts:
- Around line 263-268: Update the revokeToken contract and its implementations
to accept an optional skipCSRFCheck parameter matching destroySession, and
ensure each strategy performs its own CSRF validation unless explicitly skipped.
Propagate the parameter through all revokeToken callers while preserving
existing token-revocation behavior.

In `@packages/core/src/api/revokeToken.ts`:
- Around line 54-55: In the catch block of the token-revocation flow, remove the
console.error call and rely on the existing
ctx.logger?.log("OAUTH_ACCESS_TOKEN_ERROR", ...) structured logging instead.
Preserve the current error handling and ensure the error is not emitted through
an unstructured console path.

In `@packages/core/src/session/stateful.ts`:
- Around line 944-948: The revokeToken flow must perform an internal
verifyCSRFToken self-check before mutating OAuth/session state, matching
destroySession’s behavior. Update revokeToken and its related session
interfaces/callers as needed so the validation is always enforced without
relying solely on external callers.

In `@packages/core/src/session/stateless.ts`:
- Around line 334-357: Update revokeToken to perform the same internal CSRF
validation used by destroySession before revoking tokens or clearing the cookie,
reusing the existing verifyCSRFToken flow and preserving the current disconnect
and header behavior.
- Around line 334-352: Update revokeToken so the disconnect=true path skips
cookie verification and OAuth token parsing, allowing local cleanup even with
stale or malformed cookies. Validate provider immediately after resolving
oauth[oauthId], and throw the established UNSUPPORTED_OAUTH_CONFIGURATION error
when it is missing; only verify/parse the cookie and call revokeProviderToken in
the !disconnect branch, removing the non-null assertion.

In `@packages/core/test/actions/providers/tokens/revoke/stateful.test.ts`:
- Around line 410-439: Update the test “handles malformed provider token cookie”
to include the provider access-token cookie,
`__Secure-aura-auth.access_token.oauth-provider`, with a deliberately invalid or
garbage value in the Request Cookie header. Keep the existing assertions
verifying the failed response and that account status is not updated.

In `@packages/core/test/api/stateful/revokeToken.test.ts`:
- Around line 81-114: The test named “throws error when CSRF token is invalid”
does not exercise CSRF rejection because the default configuration skips the
check. Update the test setup around authInstance to explicitly set
skipCSRFCheck: false and assert CSRF_TOKEN_MISMATCH without OAuth lookup, or
rename the test and assertions to reflect the no-CSRF-check behavior; ensure the
test’s title, expected error, and mock-call assertions describe the same path.

---

Nitpick comments:
In `@packages/core/test/actions/providers/tokens/revoke/stateful.test.ts`:
- Around line 581-593: Strengthen the request assertion in the token revocation
test by inspecting the URLSearchParams body rather than only matching its type.
Verify it serializes token_type_hint=refresh_token and does not contain a raw
tokenHint parameter, while preserving the existing request expectations.

In `@packages/core/test/api/stateful/revokeToken.test.ts`:
- Around line 192-203: The revoke-token tests repeatedly redeclare the same
OAuth account fixture instead of using the existing preset. Replace each
identical getOAuthAccountMock resolved value in the affected test cases with the
imported oauthAccountEntity preset, preserving any test-specific overrides
through the established fixture-copy pattern.
- Line 22: Add afterEach cleanup calling vi.unstubAllGlobals() in the describe
blocks covering revokeToken tests in
packages/core/test/api/stateful/revokeToken.test.ts and
packages/core/test/actions/providers/tokens/revoke/stateful.test.ts, so fetch
stubs and one-time mock values are reset between tests.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 97e5b6dd-20d5-4597-88d3-d7ad465b9df7

📥 Commits

Reviewing files that changed from the base of the PR and between 5a137d9 and 5782608.

📒 Files selected for processing (11)
  • packages/core/src/@types/session.ts
  • packages/core/src/api/revokeToken.ts
  • packages/core/src/session/stateful.ts
  • packages/core/src/session/stateless.ts
  • packages/core/src/shared/errors.ts
  • packages/core/src/shared/logger.ts
  • packages/core/src/shared/utils.ts
  • packages/core/src/shared/utils/revoke-token.ts
  • packages/core/test/actions/providers/tokens/revoke/stateful.test.ts
  • packages/core/test/api/stateful/revokeToken.test.ts
  • packages/core/test/presets.ts

Comment thread packages/core/src/@types/session.ts
Comment thread packages/core/src/api/revokeToken.ts Outdated
Comment thread packages/core/src/session/stateful.ts
Comment thread packages/core/src/session/stateless.ts
Comment thread packages/core/src/session/stateless.ts
Comment thread packages/core/test/actions/providers/tokens/revoke/stateful.test.ts
Comment thread packages/core/test/api/stateful/revokeToken.test.ts
@halvaradop
halvaradop merged commit 11f6c38 into master Jul 25, 2026
7 checks passed
@halvaradop
halvaradop deleted the feat/support-revoke-token-stateful branch July 25, 2026 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant