Skip to content

fix(agent-bff): send client_id on the Forest refresh grant - #1953

Open
nbouliol wants to merge 2 commits into
mainfrom
feature/prd-1384-agent-bff-sends-no-client_id-on-the-forest-refresh-grant
Open

nbouliol wants to merge 2 commits into
mainfrom
feature/prd-1384-agent-bff-sends-no-client_id-on-the-forest-refresh-grant

Conversation

@nbouliol

@nbouliol nbouliol commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

An API OAuth session (hosted Zendesk included) now outlives the Forest access token: agent-bff refreshes it instead of signing the user out about an hour after login.

Fixes PRD-1384

Why

The Forest server requires client_id on /oauth/token and checks it against the client bound to the refresh token. agent-bff sent only { grant_type, refresh_token }, so the server answered 400 invalid_request, which agent-bff maps to session_expired.

What

File Change
oauth/session-store.ts clientId is required on CreateSessionInput and StoredSession
oauth/oauth-routes.ts The code exchange stores request.clientId in the session
oauth/forest-server-client.ts refreshServerToken({ refreshToken, clientId }) sends client_id, shaped like exchangeCode
oauth/session-lifecycle.ts ensureFreshServerAccess takes a logger. A session without a client id expires without calling the server. A refresh the server rejects is logged (Warn, error code + description, renderingId, userId)
ai/ai-routes-middleware.ts, auth/forest-server-token-middleware.ts Pass their logger

Behaviour

  • The error mapping is unchanged: invalid_grant, invalid_request and invalid_client still end in session_expired. The new log is what would have surfaced this bug.
  • No scope is sent: the server falls back to the refresh token's original scope.
  • Sessions are in memory, so the deploy signs everyone in again once; every new session carries its client id.

API change

ForestServerClient.refreshServerToken and ensureFreshServerAccess are exported. Their only callers are inside agent-bff, and a refresh without client_id already fails on the server, so this ships as a fix.

Tests

  • Contract: test/oauth/fixtures/forestadmin-server-oauth-route-validator-issue-token.ts copies the server's issueToken Joi schema (no .unknown(), so extra keys fail too). The bodies refreshServerToken and exchangeCode actually send pass it. Removing client_id from the refresh body fails the test.
  • session-lifecycle: the refresh uses the stored client id; a session without one ends in session_expired, logged, no server call; a rejected refresh logs the server's code and description.
  • oauth-routes: the code exchange stores the client id.
yarn workspace @forestadmin/agent-bff test

Release

Merges to main on its own, not held on feat/gateway-r1. The merge redeploys the hosted API service. Rollback: revert the commit.

Definition of Done

General

  • Write an explicit title for the Pull Request, following Conventional Commits specification
  • Test manually the implemented changes
  • Validate the code quality (indentation, syntax, style, simplicity, readability)

Security

  • Consider the security impact of the changes made

馃 Generated with Claude Code

Note

Send client_id on the Forest refresh grant in agent-bff

  • The Forest server now requires client_id in refresh-token grants, so the BFF stores the OAuth client ID in the session at authorization-code exchange time and sends it with refresh_token on later refresh calls (forest-server-client.ts, session-store.ts).
  • ensureFreshServerAccess now requires a Logger and rejects sessions without a stored client ID before contacting the Forest server, logging OAuth errors (invalid_grant, invalid_request, invalid_client) with rendering and user context (session-lifecycle.ts).
  • Adds Joi schemas and contract tests for /oauth/token request bodies, including a negative test that a refresh request without client_id is rejected.
  • Behavioral Change: pre-existing sessions without a stored clientId now fail as session_expired without attempting a refresh. refreshServerToken and CreateSessionInput signatures changed.

Macroscope summarized 2dbba01.

The Forest server requires client_id on /oauth/token and checks it against
the client bound to the refresh token. Without it every API OAuth session
was signed out about an hour after login.

The session now stores the client id at code exchange. A refresh the
server rejects is logged with its error code and description, and a
session without a client id expires without calling the server.

The contract test copies the issueToken Joi schema from forestadmin-server
(packages/private-api/src/domain/oauth/oauth-route-validator.ts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 2, 2026

Copy link
Copy Markdown

PRD-1384

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@qltysh

qltysh Bot commented Oct 2, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (3)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/agent-bff/src/oauth/forest-server-client.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/oauth/session-lifecycle.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/ai/ai-routes-middleware.ts100.0%
Total100.0%
馃殾 See full report on Qlty Cloud 禄

馃洘 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@nbouliol
nbouliol requested a review from Tonours October 2, 2026 14:35

This branch has not been deployed

No deployments
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