Skip to content

Oauth2: Prevent redeeming an unused authorization code via the refresh token grant - #66008

Open
julien-nc wants to merge 1 commit into
masterfrom
fix/oauth2-refresh-grant-unused-code
Open

julien-nc wants to merge 1 commit into
masterfrom
fix/oauth2-refresh-grant-unused-code

Conversation

@julien-nc

Copy link
Copy Markdown
Member

Summary

The authorization code expiration and redemption state checks only applied to the authorization_code grant, so an expired but still unredeemed authorization code could be exchanged for an access token through the refresh token grant, bypassing the intended 10-minute expiration until the expired code record was cleaned up.

Require a row to have already delivered a token (token_count > 0) when rotating through the refresh token grant, enforced atomically in rotateToken() which already serializes concurrent redemptions through the hashed_code check.

Checklist

AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@julien-nc julien-nc added this to the Nextcloud 36 milestone Oct 29, 2026
@julien-nc
julien-nc requested a review from a team as a code owner October 29, 2026 14:39
@julien-nc
julien-nc requested review from Altahrim, come-nc, salmart-dev and sorbaugh and removed request for a team October 29, 2026 14:39
@julien-nc
julien-nc force-pushed the fix/oauth2-refresh-grant-unused-code branch from ecb18a4 to 33b1386 Compare October 29, 2026 14:49
@kesselb kesselb removed the security label Oct 30, 2026
@kesselb

kesselb commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

/backport to stable35 please

@kesselb

kesselb commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

/backport to stable34 please

@kesselb

kesselb commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

/backport to stable33 please

…efresh token grant

The authorization code expiration and redemption state checks only
applied to the authorization_code grant, so an expired but still
unredeemed authorization code could be exchanged for an access token
through the refresh token grant, bypassing the intended 10-minute
expiration until the expired code record was cleaned up.

Require a row to have already delivered a token (token_count > 0)
when rotating through the refresh token grant, enforced atomically in
rotateToken() which already serializes concurrent redemptions through
the hashed_code check.

Assisted-by: OpenCode:GLM-5.3-Flash
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
@julien-nc
julien-nc force-pushed the fix/oauth2-refresh-grant-unused-code branch from 33b1386 to 8b26402 Compare November 2, 2026 10:29
@julien-nc
julien-nc requested a balanced review from Copilot November 2, 2026 10:30

Copilot AI 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.

🔵 Needs a closer look

The security-sensitive OAuth redemption changes require independent human verification of grant-state transitions and database-backed regression tests, since this review was static only.

No open issues 🎉

What changed in this PR

This PR strengthens OAuth2 token redemption by preventing unused authorization codes, including expired ones, from being exchanged through the refresh token grant.

Changes:

  • Enforces grant-specific token state atomically during token rotation.
  • Passes the grant type directly from the controller to the mapper.
  • Adds mapper tests for allowed and rejected grant/state combinations and updates controller mock expectations.
File Description
apps/​oauth2/​tests/​Db/​AccessTokenMapperTest.php Tests rotation acceptance and rejection for both grant types.
apps/​oauth2/​tests/​Controller/​OauthApiControllerTest.php Updates rotation expectations to use the refresh grant string.
apps/​oauth2/​lib/​Db/​AccessTokenMapper.php Requires refresh token rows to have previously delivered a token.
apps/​oauth2/​lib/​Controller/​OauthApiController.php Passes the requested grant type to token rotation.

Review effort: Balanced

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants