Skip to content

Add PKCE and state parameter validation to OAuth callback #564

Description

@Oluwaseyi89

Summary

Add PKCE and state parameter validation to OAuth callback — The OAuth callback endpoint accepts code without verifying the state anti-CSRF parameter, enabling authorization code injection.

Social Media Link

Let's collaborate on Discord. And ensure to star our repo.

Problem Statement

Confirmed in project-portal/project-portal-backend/internal/integration/handler.go and project-portal/project-portal-backend/internal/integration/service.go:

  1. InitiateOAuth2 never generates a state value: Service.InitiateOAuth2 returns a hardcoded string "https://" + provider + ".com/oauth/authorize?client_id=..." with no state query parameter appended at all.

  2. OAuth2Callback never validates state: Handler.OAuth2Callback reads only provider and code from the query string (c.Query("code")) — it never reads or checks a state parameter.

  3. No PKCE code_verifier/code_challenge support: Neither InitiateOAuth2 nor HandleOAuth2Callback generates a code_verifier, derives a code_challenge, or sends it as part of the authorization/token exchange, leaving public-client flows vulnerable to authorization code interception.

  4. HandleOAuth2Callback only checks code is non-empty: The entire validation in service.go is if code == "" { return errors.New("invalid code") } — no actual token exchange, no signature or origin validation of the callback request.

  5. No server-side state storage: There is no repository method or in-memory store for issued state values, so even if a state were generated, there would be nowhere to look it up and mark it consumed.

  6. No expiry on state/code_verifier values: Because none exist, there's naturally no TTL enforcement, meaning a leaked or replayed authorization flow could not be time-bound even if state tracking were added.

  7. No single-use enforcement: A state value (once implemented) needs to be invalidated after first use to prevent replay — this logic is entirely absent.

  8. OAuth2Authorize builds the authorization URL client-side agnostic of PKCE: Handler.OAuth2Authorize calls h.service.InitiateOAuth2(ctx, provider) and redirects — no code_challenge_method=S256 parameter is ever added to the outbound URL.

  9. No token exchange implementation: HandleOAuth2Callback has a comment // Exchange code for token and save and // Mock saving token — the actual HTTP call to the provider's token endpoint is never made.

  10. No origin/redirect_uri validation: There's no check that the callback's redirect_uri matches what was registered for the connection, which is required to prevent redirect_uri manipulation attacks.

  11. IncomingWebhook has a similar placeholder comment for signature verification: Handler.IncomingWebhook comment says // Verify signature logic would go here and unconditionally returns 200 — related to, but distinct from, the OAuth flow gap (tracked here since it sits in the same handler file).

  12. No test coverage for the OAuth flow: There is no test file for internal/integration covering the authorize/callback state or PKCE behavior.

Required Changes

  1. Generate a cryptographically random state value in InitiateOAuth2, persist it (with an expiry) keyed by provider/user, and append it to the authorization URL.

  2. Add state parameter reading and validation to OAuth2Callback/HandleOAuth2Callback, rejecting the request if state is missing, unknown, expired, or already consumed.

  3. Generate a PKCE code_verifier and derive code_challenge (S256) in InitiateOAuth2; store the verifier server-side keyed by state.

  4. Add code_challenge and code_challenge_method=S256 query parameters to the outbound authorization URL.

  5. Implement the actual token exchange HTTP call in HandleOAuth2Callback, including the code_verifier in the request body per PKCE spec.

  6. Add single-use enforcement: mark a state value consumed immediately after validation so it cannot be replayed.

  7. Add a TTL (e.g. 10 minutes) on stored state/code_verifier pairs with cleanup of expired entries.

  8. Add redirect_uri validation against the value registered for the integration connection.

  9. Persist the exchanged access/refresh token via the connection repository instead of the current no-op comment.

  10. Add structured error responses distinguishing "invalid state", "expired state", and "token exchange failed".

  11. Add unit tests for the authorize → callback → token-exchange flow, covering valid, missing, expired, and replayed state.

  12. Implement webhook signature verification in IncomingWebhook to close the adjacent placeholder in the same file.

Acceptance Criteria

  1. InitiateOAuth2 generates and persists a unique state and PKCE code_verifier/code_challenge pair.
  2. The outbound authorization URL includes state and code_challenge/code_challenge_method=S256.
  3. OAuth2Callback rejects requests with missing, unknown, or expired state.
  4. A state value cannot be successfully reused after its first valid callback.
  5. Token exchange sends code_verifier and completes a real HTTP call to the provider's token endpoint.
  6. Exchanged tokens are persisted, not just logged in a comment.
  7. redirect_uri mismatches are rejected.
  8. Expired state/code_verifier entries are cleaned up.
  9. Unit tests cover valid, missing, expired, and replayed state scenarios.
  10. IncomingWebhook verifies signatures instead of unconditionally returning success.

Directory to Work on:

project-portal/project-portal-backend/

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendThis issue is about building backend API services.ginThis issue is to be implemented with the `golang` `gin` framework for backend APIs.golangThis issue is to be implemented with `golang` programming language.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions