Skip to content

feat: implement OAuth provider authorization bridge with PKCE support - #577

Merged
chenyme merged 1 commit into
devfrom
OAuth
Aug 6, 2026
Merged

feat: implement OAuth provider authorization bridge with PKCE support#577
chenyme merged 1 commit into
devfrom
OAuth

Conversation

@chenyme

@chenyme chenyme commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Add an instance-owned OAuth authorization bridge for Web, App, and Desktop public clients.

Previously, provider OAuth codes were returned directly to a Web callback, which could not safely support native custom schemes or desktop loopback callbacks. This change keeps provider authorization codes, client secrets, and provider tokens on the user’s DEEIX server. Public clients receive only a short-lived, single-use DEEIX grant bound to the client’s PKCE verifier.

This PR:

  • Adds server-owned provider callback handling.
  • Adds PKCE-protected authorization and one-time grant exchange endpoints.
  • Uses the existing Redis or in-memory cache backend for atomic transaction consumption.
  • Routes successful exchanges through the existing user resolution, 2FA, session, and audit flows.
  • Migrates current Web sign-in and registration to the bridge when supported.
  • Preserves the legacy Web OAuth flow for backward compatibility and account binding.
  • Exposes bridge capabilities through login-options.
  • Shows the canonical server callback and the deprecated legacy callback in the admin console.
  • Updates Swagger, generated TypeScript contracts, and deployment documentation.

Change type

  • Bug fix
  • Feature
  • Documentation
  • Refactor
  • Configuration / deployment
  • Security hardening
  • Other

Affected areas

  • Frontend / UI
  • Backend / API
  • Authentication / authorization
  • Conversations / streaming
  • Files / RAG / extraction
  • Model routing / providers
  • MCP / tools
  • Billing / payments
  • Admin console
  • Deployment / Docker / configuration
  • Documentation

Verification

  • pnpm check
  • cd backend && go test ./...
  • cd backend && make swagger
  • Generated Swagger and TypeScript API contracts are up to date.
  • Backend OAuth bridge tests cover callback isolation, independent provider/client PKCE, redirect allowlisting, provider denial, one-time consumption, and invalid verifier rejection.
  • Full production build was not run; static checks, contract generation, and the complete Go test suite passed.

Screenshots, API examples, or logs

Admin identity-provider configuration now shows:

  • Server callback URL (recommended) — canonical callback for Web, App, and Desktop.
  • Legacy Web callback URL (deprecated soon) — retained temporarily for account binding and older Web clients.

Canonical provider callback:

https://api.example.com/api/v1/auth/providers/<provider-slug>/callback

Start an authorization:

POST /api/v1/auth/providers/linux-do/authorize
Content-Type: application/json

{
  "clientID": "com.deeix.chat.native",
  "redirectURI": "com.deeix.chat:/oauth/callback",
  "codeChallenge": "<PKCE-S256-challenge>",
  "clientState": "<random-client-state>",
  "intent": "login",
  "next": "/chat"
}

Example response:

{
  "errorMsg": "",
  "data": {
    "authorizationURL": "https://provider.example.com/oauth/authorize?...",
    "expiresAt": "2026-08-06T12:10:00Z"
  }
}

After the provider redirects to the DEEIX server, the server returns a short-lived grant to the client callback:

com.deeix.chat:/oauth/callback?provider=linux-do&grant=<one-time-grant>&state=<client-state>

Exchange the grant:

POST /api/v1/auth/providers/linux-do/exchange
Content-Type: application/json

{
  "clientID": "com.deeix.chat.native",
  "grant": "<one-time-grant>",
  "codeVerifier": "<original-client-pkce-verifier>"
}

The response uses the existing login response contract, including existing 2FA challenge behavior.

Configuration, migration, and compatibility notes

  • PUBLIC_API_BASE_URL must contain the externally reachable API base URL.

  • Production deployments must use an HTTPS PUBLIC_API_BASE_URL.

  • The canonical provider callback is generated as:

    <PUBLIC_API_BASE_URL>/api/v1/auth/providers/<provider-slug>/callback
    
  • Reverse-proxy deployments must configure the public proxy URL, not the Go service’s internal listen address.

  • No database migration is required.

  • Short-lived transactions and grants use the configured cache backend:

    • Redis for distributed/multi-instance deployments.
    • In-memory cache for single-process deployments.
  • Transactions expire after 10 minutes.

  • One-time client grants expire after 90 seconds.

  • Supported built-in public clients:

    • Web: allowed Web origin with /auth/callback?provider=<slug>.
    • Native: com.deeix.chat:/oauth/callback.
    • Desktop: loopback HTTP callback with a dynamic port and /oauth/callback.
  • Existing GET /auth/providers/:slug/start and POST /auth/providers/:slug/callback routes remain available.

  • Older servers and deployments without a configured bridge continue using the legacy Web flow.

  • Account identity binding currently continues using the legacy Web callback.

  • The admin console marks the legacy callback as being deprecated.

  • Multi-platform clients using the bridge have not yet been formally released.

  • Swagger documents and generated @deeix/api-contract types are included.

Documentation

  • Documentation is not needed for this change.
  • Documentation was updated.
  • Documentation still needs to be updated.

Updated documentation covers:

  • Canonical server callback configuration.
  • PUBLIC_API_BASE_URL behavior across reverse-proxy, static frontend, and separated frontend/backend deployments.
  • Public-client PKCE and one-time grant behavior.
  • Legacy callback compatibility and deprecation status.
  • Multi-platform release status.

Security and privacy

  • No secrets, tokens, credentials, local config, or personal data are included.
  • User data access remains scoped by authenticated user context unless an admin-only path explicitly requires broader access.
  • Security-sensitive behavior was reviewed, including authentication, authorization, provider routing, file processing, billing, and admin APIs where relevant.

Security properties:

  • Provider authorization codes never enter App/Desktop custom callbacks.
  • Provider client secrets and tokens remain on the DEEIX server.
  • Provider and client PKCE verifiers are independent.
  • OAuth state is signed and binds the provider and server-side transaction.
  • Client redirects are allowlisted by registered client type.
  • Native redirects use an exact reverse-domain scheme.
  • Desktop redirects are restricted to loopback HTTP addresses and /oauth/callback.
  • Grants are random, short-lived, single-use, and bound to the original client PKCE challenge.
  • Redis consumption is atomic.
  • Callback, authorization, and exchange responses use Cache-Control: no-store.
  • Request rate limiting continues to apply through the existing public-auth middleware.
  • Provider email conflicts remain server-side and preserve the existing safe account-binding response.

Checklist

  • I searched existing issues and pull requests.
  • Changes are focused and do not include unrelated refactors.
  • Tests or static verification were run where practical.
  • User-facing behavior, deployment steps, API contracts, or configuration changes are documented.
  • Generated artifacts are included only when this project explicitly requires them.
  • Caches, build output, .pyc files, .env files, and local storage data are not committed.

@chenyme chenyme self-assigned this Aug 6, 2026
@chenyme
chenyme merged commit 07d9bc4 into dev Aug 6, 2026
11 checks passed
@chenyme
chenyme deleted the OAuth branch August 6, 2026 09:05
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