Skip to content

Add OAuth 2.0 + PKCE support for GPT Actions with unified API surface#39

Merged
vitorhugo-java merged 3 commits into
copilot/add-oauth2-integration-gpt-actionsfrom
copilot/add-isolated-oauth-support
May 29, 2026
Merged

Add OAuth 2.0 + PKCE support for GPT Actions with unified API surface#39
vitorhugo-java merged 3 commits into
copilot/add-oauth2-integration-gpt-actionsfrom
copilot/add-isolated-oauth-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 29, 2026

This PR adds an OAuth 2.0 Authorization Code flow with PKCE for GPT Actions, without changing the existing JWT-based user auth or the separate Google Drive OAuth integration. GPT tokens authenticate against the existing /api/v1/** endpoints directly — no duplicate controller layer.

  • OAuth flow for GPT Actions

    • adds /oauth2/authorize and /oauth2/token for a dedicated GPT client
    • validates configured client ID/secret, allowed redirect URIs, requested scopes, and PKCE S256
    • persists short-lived authorization codes server-side and exchanges them for signed bearer tokens
  • Unified security model

    • GptOAuthSecurityConfig chain scoped to /oauth2/** only; OAuth endpoints are public
    • JwtAuthenticationFilter extended to try GPT OAuth tokens as a fallback after user JWT validation, keeping both auth modes in the same chain without BearerTokenAuthenticationFilter conflicts
    • GPT tokens carry ROLE_GPT_CLIENT (not ROLE_USER) plus any non-USER roles (e.g. ROLE_BETA), making scope checks meaningful
    • JwtAuthenticationToken subject resolves to the user email so SecurityUtils and repository-level user isolation continue to work unchanged
  • One API surface, two authentication modes

    • GPT tokens access the existing endpoints directly — no /api/v1/gpt/** duplicate layer
    • AuthController#me, ApplicationController#{create,getAll,getById,updateStatus} annotated with @PreAuthorize("hasRole('USER') or hasAuthority('SCOPE_...')")
    • Google Drive endpoints accessible to GPT require hasRole('BETA') and (hasRole('USER') or hasAuthority('SCOPE_...')); all other Google Drive endpoints remain USER-only at URL level
    • URL-level hasAnyRole("USER","GPT_CLIENT") guards in SecurityConfig for each GPT-accessible path; hasRole("USER") catch-all covers everything else
  • Config and docs

    • adds env-driven GPT OAuth configuration:
      • OPENAI_GPT_CLIENT_ID
      • OPENAI_GPT_CLIENT_SECRET
      • OPENAI_GPT_REDIRECT_URIS
      • OPENAI_GPT_SCOPES
    • updates OpenAPI with an OAuth2 authorization-code security scheme; gpt-actions group lists the real existing endpoint paths instead of a parallel controller section
    • documents GPT Action setup and the new auth flow in README.md
    • updates application.yml, application-test.yml, and .env.example
  • Persistence and regression coverage

    • adds a migration for GPT authorization-code storage
    • adds tests for config loading, code exchange/token issuance, scope-based access to the existing endpoints, OpenAPI exposure, JWT regression, and Google Drive regression

Example GPT flow:

GET /oauth2/authorize?response_type=code&client_id=...&redirect_uri=...&scope=read:profile%20read:applications&code_challenge=...&code_challenge_method=S256
POST /oauth2/token
Authorization: Basic <client credentials>

GET /api/v1/auth/me
Authorization: ******

POST /api/v1/applications
Authorization: ******

Endpoint-level authorization on the existing controllers:

@PreAuthorize("hasRole('USER') or hasAuthority('SCOPE_write:applications')")
@PostMapping
public ResponseEntity<ApplicationResponse> create(@Valid @RequestBody ApplicationRequest request) {
    return ResponseEntity.status(HttpStatus.CREATED).body(applicationService.create(request));
}

Copilot AI added 2 commits May 29, 2026 11:31
- Delete GptActionController (duplicated /api/v1/gpt/** layer)
- Narrow GptOAuthSecurityConfig chain to /oauth2/** only; remove
  oauth2ResourceServer and BearerTokenAuthentication* handlers
- Extend JwtAuthenticationFilter to try GPT OAuth tokens as fallback
  when user JWT validation fails (avoids BearerTokenAuthenticationFilter
  conflict in the main chain)
- Change GptOAuthTokenService to emit ROLE_GPT_CLIENT instead of
  ROLE_USER; preserve non-USER roles (e.g. ROLE_BETA)
- Add URL-level hasAnyRole(USER, GPT_CLIENT) rules in SecurityConfig for
  each GPT-accessible path; keep hasRole(USER) catch-all for everything else
- Add @PreAuthorize(hasRole(USER) or hasAuthority(SCOPE_...)) to
  AuthController#me, ApplicationController#{create,getAll,getById,updateStatus}
- Update GoogleDriveController status/listBaseResumes/getBaseResumeContent/
  getGeneratedResumeContent to require ROLE_BETA and (USER or scope)
- Update OpenApiConfig gptOpenApi group to point to existing endpoint paths
- Update GptOAuthFlowIT to call /api/v1/applications and /api/v1/auth/me
- Update OpenApiDocumentationIT assertions to check existing paths
Add explicit return false with debug log when isTokenValid fails,
so the method always returns at a clear decision point rather than
falling through to the final return.
Copilot AI changed the title [WIP] Add isolated OAuth 2.0 + PKCE support for GPT Actions Add OAuth 2.0 + PKCE support for GPT Actions with unified API surface May 29, 2026
Copilot AI requested a review from vitorhugo-java May 29, 2026 11:38
@vitorhugo-java vitorhugo-java marked this pull request as ready for review May 29, 2026 11:50
@vitorhugo-java vitorhugo-java merged commit e6957a1 into copilot/add-oauth2-integration-gpt-actions May 29, 2026
@vitorhugo-java vitorhugo-java deleted the copilot/add-isolated-oauth-support branch May 29, 2026 11:50
vitorhugo-java pushed a commit that referenced this pull request May 29, 2026
* Add GPT OAuth implementation scaffold

* Add GPT action endpoints and docs

* Address GPT OAuth validation feedback

* Add OAuth 2.0 + PKCE support for GPT Actions with unified API surface (#39)

* Initial plan

* Remove GptActionController; reuse existing API for GPT OAuth tokens

- Delete GptActionController (duplicated /api/v1/gpt/** layer)
- Narrow GptOAuthSecurityConfig chain to /oauth2/** only; remove
  oauth2ResourceServer and BearerTokenAuthentication* handlers
- Extend JwtAuthenticationFilter to try GPT OAuth tokens as fallback
  when user JWT validation fails (avoids BearerTokenAuthenticationFilter
  conflict in the main chain)
- Change GptOAuthTokenService to emit ROLE_GPT_CLIENT instead of
  ROLE_USER; preserve non-USER roles (e.g. ROLE_BETA)
- Add URL-level hasAnyRole(USER, GPT_CLIENT) rules in SecurityConfig for
  each GPT-accessible path; keep hasRole(USER) catch-all for everything else
- Add @PreAuthorize(hasRole(USER) or hasAuthority(SCOPE_...)) to
  AuthController#me, ApplicationController#{create,getAll,getById,updateStatus}
- Update GoogleDriveController status/listBaseResumes/getBaseResumeContent/
  getGeneratedResumeContent to require ROLE_BETA and (USER or scope)
- Update OpenApiConfig gptOpenApi group to point to existing endpoint paths
- Update GptOAuthFlowIT to call /api/v1/applications and /api/v1/auth/me
- Update OpenApiDocumentationIT assertions to check existing paths

* Make invalid-token return path explicit in tryUserJwt

Add explicit return false with debug log when isTokenValid fails,
so the method always returns at a clear decision point rather than
falling through to the final return.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
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.

2 participants