# Autenticación en el navegador

> El recorrido completo desde el botón hasta la sesión, con Authorization Code y PKCE.


En el navegador tu aplicación es un cliente público: no tiene ni puede tener un secreto. El flujo es Authorization Code con PKCE, y el SDK lo hace entero.

## Crear la instancia

```ts title="andco.ts"
import { createAndcoInstanceForBrowser } from "@andco/sdk/browser";

export const andco = createAndcoInstanceForBrowser({
  clientId: "tu-client-id",
  redirectTo: "https://tuapp.com/",
  initialScopes: ["openid", "email", "profile"],
});
```

`initialScopes` son los que se piden cuando no indicas otros. `redirectTo` debe coincidir exactamente con la URL registrada.

```ts
createAndcoInstanceForBrowser(options: AndcoBrowserOptions): AndcoSessionClient
```

| Opción | Por omisión | Qué hace |
| --- | --- | --- |
| `clientId` | — | requerido |
| `redirectTo` | — | requerido, debe coincidir con la URL registrada |
| `initialScopes` | — | scopes pedidos cuando `signIn` no indica otros |
| `detectSessionInUrl` | `true` | intercambia el código que la redirección dejó en la URL |
| `relayPopupCallback` | `true` | entrega el callback al `opener` cuando este documento es una ventana emergente de Andco |
| `replaceUrl` | History API | cómo se limpia la URL después de consumirla |
| `transactionStorage` | `sessionStorage` | dónde vive la transacción entre la redirección y la vuelta |
| `presenter` | `browserPresenter()` | cómo se presenta la autorización |
| `storage` | `sessionStorage` | dónde vive la sesión |
| `initialSession` | — | una sesión ya resuelta, para que el primer cuadro sea real |

`clientSecret` está rechazado por tipos: una credencial confidencial no puede llegar a un navegador. El detalle completo está en la [referencia](/docs/reference/andco-sdk-browser).

## Presentar la autorización

Hay dos presentaciones y el SDK usa la primera por omisión.

```ts
signIn(options?: AndcoSignInOptions): Promise<Result<AndcoSession | null>>
```

| Opción | Qué hace |
| --- | --- |
| `presentation` | `"popup"` (por omisión) o `"redirect"` |
| `scopes` | reemplaza a `initialScopes` para esta autorización |
| `authorizations` | contribuciones de Resource Server Definitions, compuestas en una sola petición |
| `resource` | uno o varios Resource Indicators |
| `orgId` | organización sobre la que se autoriza |
| `authorizationDetails` | detalles RAR de la petición |
| `redirectTo` | reemplaza el de la instancia para esta llamada |
| `externalId` | referencia opaca a un destino, propia del OAuth Client |
| `state` | correlaciona el callback con una presentación ya abierta |

### Ventana emergente

```ts
const { data: session, error } = await andco.auth.signIn();
```

El documento no se reemplaza, así que la promesa se resuelve con la sesión. Si la persona cierra la ventana, `session` es `null` y `error` también: cerrar no es un fallo.

### Redirección

```ts
await andco.auth.signIn({ presentation: "redirect" });
```

Aquí el documento se reemplaza, así que la promesa no devuelve sesión. El resultado llega en la carga siguiente: al construir la instancia, el SDK reconoce el código en la URL, lo intercambia, guarda la sesión y limpia los parámetros. No tienes que escribir una ruta de callback.

## Leer la sesión

```ts
// Lectura síncrona: tres estados.
const session = andco.session.getSnapshot();

// Esperar a que termine la carga inicial.
const { error } = await andco.session.ready;

// Observar cambios. Devuelve la función para dejar de observar.
const stop = andco.auth.onChange((session) => render(session));
```

```ts
getSnapshot(): AndcoSession | null | undefined
```

`undefined` es Instance Readiness: la carga inicial no ha terminado. No es lo mismo que `null`, que es "no hay sesión" — tratarlos igual muestra un estado desconectado en cada carga. La lectura nunca dispara la carga por sí sola.

```ts
get ready: Promise<Result<void>>
```

Se resuelve cuando termina la carga inicial. Solo hace falta si lees `getSnapshot` de forma síncrona antes de eso; el resto del SDK espera la carga por su cuenta.

```ts
onChange(listener: (session: AndcoSession | null) => void): () => void
```

Es lo que conectas a tu framework. Devuelve la función de baja, así que en un efecto es una línea: `useEffect(() => andco.auth.onChange(setSession), [andco])`.

```ts
getSession(): Promise<Result<AndcoSession | null>>
```

Igual que `getSnapshot`, pero asíncrono: espera la carga inicial y devuelve un `Result` en vez de distinguir `undefined`.

```ts
refresh(): Promise<Result<AndcoSession>>
```

Refresca ahora en vez de esperar a que una petición de token encuentre la sesión vencida. Falla si no hay `refreshToken`.

## Ampliar permisos

Un Grant se amplía pidiendo una autorización nueva con más scopes. La sesión resultante reemplaza a la anterior.

```ts
await andco.auth.signIn({ scopes: ["openid", "email", "profile", "bank_accounts:read"] });
```

## Cerrar sesión

```ts
signOut(): Promise<Result<void>>
```

```ts
await andco.auth.signOut();
```

Esto olvida la credencial local. Revocar el Grant en Andco es otra cosa y se hace desde el servidor.

## Dónde se guarda

Por omisión, en `sessionStorage`: la sesión vive mientras viva la pestaña. Si necesitas que sobreviva a un cierre del navegador, entrega tu propio almacenamiento.

```ts
import { WebStorage } from "@andco/sdk";

createAndcoInstanceForBrowser({
  clientId,
  redirectTo,
  storage: new WebStorage(window.localStorage),
});
```

El almacenamiento puede ser asíncrono: el SDK espera sus lecturas, así que un almacén cifrado o remoto también sirve.
