Skip to content

fix: reject banned users in the PKCE grant - #2806

Open
Dhvanit41 wants to merge 1 commit into
supabase:masterfrom
Dhvanit41:fix/pkce-grant-banned-user
Open

Dhvanit41 wants to merge 1 commit into
supabase:masterfrom
Dhvanit41:fix/pkce-grant-banned-user

Conversation

@Dhvanit41

Copy link
Copy Markdown

What kind of change does this PR introduce?

Bug fix

What is the current behavior?

The PKCE grant (POST /token?grant_type=pkce) never checks whether the user is banned.

Every step that issues an auth code already rejects banned users (/verify, the external provider callback, SAML ACS), and so do the password and refresh token grants. But User.Ban() only sets banned_until; it does not remove the user's pending flow states. So an auth code issued before the ban can still be exchanged afterwards, within the flow state expiry (300s by default). That exchange creates a new session and returns an access token and a refresh token.

The refresh token is rejected on first use, because the refresh grant checks IsBanned(). The access token, however, stays valid until it expires (JWT_EXP, 3600s by default).

What is the new behavior?

PKCE() returns 400 with error_code: user_banned ("User is banned") when the user is banned, matching the password grant. No session is created.

The check runs after VerifyPKCE, so a request that has the auth code but not the code verifier still gets bad_code_verifier and learns nothing about the account's ban status.

Additional context

  • Added TestTokenPKCEGrantBannedUser: it starts a PKCE magic link sign in, follows /verify to obtain the auth code, bans the user, then exchanges the code. On master the exchange returns 200 with tokens. With this change it returns 400 user_banned, the session count is unchanged, and a wrong code verifier still returns bad_code_verifier.
  • Ran locally: go test ./internal/api/... (all packages pass), gofmt -s, go vet, staticcheck.
  • Same class of consistency fix as chore: ensure consistent ban behaviour #2759 (ban check on admin credentials).

The PKCE grant exchanged an auth code for a session without checking
whether the user is banned. Banning a user only sets banned_until and does
not remove pending flow states, so an auth code issued before the ban could
still be exchanged afterwards for a new session and access token.

Check IsBanned() after the code verifier is validated, returning the same
user_banned error as the password grant, and add a test that bans the user
between issuing and exchanging the auth code.
@Dhvanit41
Dhvanit41 requested a review from a team as a code owner September 15, 2026 10:29
@Dhvanit41

Copy link
Copy Markdown
Author

@fadymak

this is ready for review let me know if there is need to make change.

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