# Apps OAuth de terceros

OAuth sirve cuando tu aplicación debe operar un workspace que pertenece a otra persona. La persona autoriza los scopes que ve en Meteor; tu app recibe un token de acceso temporal. **Nunca** le pidas ni captures su API key.

Meteor implementa OAuth 2.0 Authorization Code con **PKCE S256 obligatorio**. El access token se usa como Bearer para la misma API pública; dura una hora. El refresh token dura 30 días y rota en cada uso.

## Antes de redirigir

En el workspace dueño de tu integración, abre **Ajustes → Desarrolladores → Apps OAuth** y registra la app. Guarda el `client_id`. Si eliges una app confidencial, guarda también el `client_secret`: Meteor lo muestra solo al crearla o al rotarlo.

- Registra cada `redirect_uri` completo. Meteor compara la URL de retorno **exactamente**; no acepta comodines ni fragmentos (`#`).
- El callback debe usar HTTPS, salvo `localhost`, `127.0.0.1` o `::1` durante desarrollo.
- Declara solo los scopes mínimos. Los scopes de partner no se pueden asignar a una app OAuth.
- Elige **pública** para una SPA o app nativa que no puede proteger un secreto. Elige **confidencial** solo si el canje ocurre en tu servidor.

## Redirigir al consentimiento

Genera por usuario un `state` impredecible y un `code_verifier` PKCE de 43 a 128 caracteres. Guarda ambos en la sesión de tu app. Calcula `code_challenge = base64url(SHA-256(code_verifier))` y redirige el navegador a la pantalla de Meteor:

```ts
import { createHash, randomBytes } from 'node:crypto';
import { OAuth } from '@meteor.ia/sdk';

const verifier = randomBytes(48).toString('base64url');
const challenge = createHash('sha256').update(verifier).digest('base64url');
const state = randomBytes(24).toString('base64url');

// Guarda verifier y state en la sesión antes de redirigir.
const oauth = new OAuth();
const url = oauth.authorizeUrl({
  clientId: process.env.MET_OAUTH_CLIENT_ID!,
  redirectUri: 'https://tu-app.example.com/oauth/callback',
  scope: ['runs:execute', 'contacts:read'],
  state,
  codeChallenge: challenge,
});
res.redirect(url);
```

La persona inicia sesión en Meteor si hace falta y ve el nombre de tu app, su sitio y los scopes solicitados. Si acepta, Meteor redirige a tu `redirect_uri` con `code` y el mismo `state`. Si rechaza, vuelve con `error=access_denied`.

## Canjear el código en tu servidor

En el callback, primero compara `state` con el que guardaste y luego canjea el código. Envía siempre el mismo `redirect_uri` y el `code_verifier` original. El código es de un solo uso y vence en 60 segundos.

```ts
const { access_token, refresh_token, expires_in, scope } = await oauth.exchangeCode({
  clientId: process.env.MET_OAUTH_CLIENT_ID!,
  clientSecret: process.env.MET_OAUTH_CLIENT_SECRET, // solo apps confidenciales
  code: String(req.query.code),
  redirectUri: 'https://tu-app.example.com/oauth/callback',
  codeVerifier: session.pkceVerifier,
});

// Cifra refresh_token al guardarlo. access_token es una key met_ temporal.
```

No envíes el `client_secret`, el `code_verifier`, el código ni el refresh token al navegador, registros o analítica. El token no incorpora un `workspace_id`: conserva en tu propio vínculo de instalación el workspace al que operará cada conexión si la ruta que usas lo requiere.

## Llamar la API y renovar

El `access_token` es un Bearer token para los scopes aprobados. Úsalo con la API REST o el SDK; para rutas que lo necesitan, configura el `workspaceId` de esa instalación.

```ts
import Met from '@meteor.ia/sdk';

const met = new Met(access_token, { workspaceId: installation.workspaceId });
const run = await met.runs.create({ input: 'Resume los leads de hoy' });
```

Antes de que venza, o tras una respuesta de token expirado, renueva desde tu servidor. Cada refresh emite **un refresh token nuevo**: reemplaza el anterior de forma atómica. Reusar un refresh ya consumido revoca todo el grant por seguridad.

```ts
const renewed = await oauth.refresh({
  clientId: process.env.MET_OAUTH_CLIENT_ID!,
  clientSecret: process.env.MET_OAUTH_CLIENT_SECRET,
  refreshToken: installation.refreshToken,
});
// Guarda renewed.access_token y renewed.refresh_token juntos.
```

## Revocar y rotar

Una persona puede revocar tu app desde **Ajustes → Desarrolladores → Apps OAuth**. Tu app también puede revocar un access o refresh token; revocar un refresh token invalida el grant completo:

```ts
await oauth.revoke(installation.refreshToken, {
  clientId: process.env.MET_OAUTH_CLIENT_ID!,
  clientSecret: process.env.MET_OAUTH_CLIENT_SECRET,
});
```

Para apps confidenciales, rota el `client_secret` desde la misma pantalla. La rotación normal conserva el secreto anterior por 24 horas para desplegar el nuevo; usa la invalidación inmediata solo si se filtró.

Si tu workspace dueño ya usa [webhooks salientes](webhooks.html), puedes suscribirte a `app.authorized` y `app.revoked` con `integrations:read`. Esos eventos llegan al workspace que registró la app e incluyen `client_id`, `workspace_id`, scopes y `grant_id`, sin datos personales.

## Límites actuales

El servidor de autorización no publica todavía metadata OAuth en `/.well-known/oauth-authorization-server` ni registro dinámico de clientes. Configura manualmente las URLs que documenta esta guía; no intentes descubrirlas desde el dominio.
