# OAuth 2.0 with PKCE (/docs/apis/oauth-pkce)

Declare `auth` in the manifest. For workspace connections, register an `AddonConnectionAuthenticator` with `method: oauth`. The core owns PKCE, state, attempt lifetime and credential persistence. Do not create a second independent login flow for the same connection.

| Environment | Provider HTTP redirect                  | Landing page opens          |
| ----------- | --------------------------------------- | --------------------------- |
| Development | `http://localhost:3000/oauth/callback`  | `electron://oauth/callback` |
| Production  | `https://mistertick.com/oauth/callback` | `mr-tick://oauth/callback`  |

These are the current Discord/desktop bridge routes. Register HTTP URLs at the provider, not desktop deep links. Use the same `redirectUri` when exchanging the code. The internal `mr-tick-app://` resource/activation protocol is not the OAuth callback.

1. Register the profile and OAuth authenticator, including its HTTP `redirectUri`.
2. `core.connections.createAuthorizationRequest(scope)` supplies state, S256 challenge and redirect URI to the authenticator.
3. The authenticator returns `authUrl` and `timeoutMs`.
4. The provider redirects through the HTTP landing page; the host binds the desktop callback to the active attempt.
5. `completeAuthentication` receives code, verifier, state and redirect URI. Validate the provider response and return identity plus credentials.
6. The core persists credentials and completes the lifecycle.

The following factory implements only the authorization-request method. Token exchange, identity discovery and refresh are provider-specific:

```ts
import { Either, type AddonConnectionAuthenticator } from '@mr-tick/sdk'

type OAuthAuthenticator = Extract<
  AddonConnectionAuthenticator,
  { method: 'oauth' }
>

export function createAuthorizationRequest(
  clientId: string,
): OAuthAuthenticator['createAuthorizationRequest'] {
  return async (scope, parameters) => {
    void scope

    const url = new URL('https://provider.example/oauth/authorize')

    url.search = new URLSearchParams({
      client_id: clientId,
      response_type: 'code',
      redirect_uri: parameters.redirectUri,
      state: parameters.state,
      code_challenge: parameters.codeChallenge,
      code_challenge_method: parameters.codeChallengeMethod,
    }).toString()

    return Either.success({ authUrl: url.toString(), timeoutMs: 120000 })
  }
}
```

The state is bound to the attempt and cannot authorize another connection or revive an expired attempt. Do not embed client secrets in public clients. Validate token payloads at the transport boundary and preserve functional errors.

`context.host.oauth` still offers low-level `generatePKCE`, `generateState` and `authorize` browser helpers. `authorize` returns `Promise<OAuthResult>` and does not independently persist a core connection. Connection authentication uses the lifecycle above.

See [authentication](/docs/capabilities/auth), [OAuth PKCE](/docs/apis/oauth-pkce) and [vault and events](/docs/apis/vault-and-events).
