# Iniciar sesión con UTILIA con el SDK Python

Implementa «Iniciar sesión con UTILIA» con utilia-sdk, en modo asíncrono o síncrono: URL de autorización con PKCE, canje del código, datos de la persona y cierre de sesión.

El SDK Python de UTILIA OS (`utilia-sdk`) resuelve el flujo OAuth por ti: genera el par PKCE y el `state`, construye la URL de autorización, canjea el código, guarda los tokens y los renueva antes de que caduquen. Tiene un cliente asíncrono (`UtiliaSDK`) y otro síncrono (`UtiliaSDKSync`) con los mismos métodos.

Está pensado para scripts, herramientas de línea de órdenes y aplicaciones de escritorio, en las que una sola persona inicia sesión. Para una aplicación web con muchas personas a la vez, como Flask o Django, usa Authlib, como en [Iniciar sesión con UTILIA en Flask](https://os.utilia.ai/docs/developers/oauth/sign-in-flask).

## Antes de empezar

- Python 3.9 o superior.
- Activa OAuth en tu aplicación externa, como explica [Activar OAuth en tu aplicación](https://os.utilia.ai/docs/developers/oauth/apps-panel), y registra como dirección de vuelta, por ejemplo, `http://localhost:8000/callback`.
- Instala el SDK:

```bash
pip install utilia-sdk
```

## Flujo asíncrono

1. **Crea el SDK con OAuth**

   ```python
   import os

   from utilia_sdk import FileTokenStorage, UtiliaSDK

   sdk = UtiliaSDK(
       base_url="https://os.utilia.ai/api",
       oauth={
           "client_id": os.environ["UTILIA_CLIENT_ID"],
           "client_secret": os.environ.get("UTILIA_CLIENT_SECRET"),  # solo con servidor propio
           "redirect_uri": "http://localhost:8000/callback",
           "scopes": ["openid", "profile", "email"],
           "token_storage": FileTokenStorage(".utilia-tokens.json"),
       },
   )
   ```

   `FileTokenStorage` guarda los tokens y el estado pendiente en un archivo, así que sobreviven entre ejecuciones. Si la aplicación se ejecuta en el equipo de la persona, no pongas `client_secret`: no se puede guardar en secreto, y PKCE la protege.
2. **Lleva a la persona a autorizar**

   ```python
   auth_url = await sdk.oauth.get_authorization_url()
   print(f"Abre esta URL en tu navegador: {auth_url}")
   ```

   Admite opciones como `scopes`, `resource`, `prompt` y `state`.
3. **Canjea el código**

   Cuando UTILIA OS devuelve a la persona a tu dirección de vuelta, recoge `code` y `state`:

   ```python
   tokens = await sdk.oauth.handle_callback(code, callback_state=state)
   print(tokens.scope)  # lo que la persona concedió de verdad
   ```

   Con `callback_state`, el SDK comprueba que la respuesta es la de tu petición.
4. **Obtén los datos de la persona**

   ```python
   user = await sdk.oauth.get_user_info()

   print(user.sub)    # identificador estable
   print(user.name)
   print(user.email)
   ```

   En el cliente asíncrono, `get_user_info()` es una corrutina: sin `await` devolvería la corrutina y no los datos.

Al arrancar de nuevo, comprueba si ya hay sesión:

```python
if await sdk.oauth.is_authenticated():
    user = await sdk.oauth.get_user_info()
    print(f"Hola, {user.name}")
else:
    print(f"Abre esta URL: {await sdk.oauth.get_authorization_url()}")
```

## Flujo síncrono

```python
import os

from utilia_sdk import FileTokenStorage, UtiliaSDKSync

sdk = UtiliaSDKSync(
    base_url="https://os.utilia.ai/api",
    oauth={
        "client_id": os.environ["UTILIA_CLIENT_ID"],
        "redirect_uri": "http://localhost:8000/callback",
        "scopes": ["openid", "profile", "email"],
        "token_storage": FileTokenStorage(".utilia-tokens.json"),
    },
)

print(f"Abre esta URL en tu navegador: {sdk.oauth.get_authorization_url()}")

code = input("Pega el valor de code de la dirección de vuelta: ")
state = input("Pega el valor de state: ")
sdk.oauth.handle_callback(code, callback_state=state)

user = sdk.oauth.get_user_info()
print(f"Hola, {user.name} ({user.email})")
```

## Renovación y errores

El SDK renueva el access token automáticamente antes de que caduque y guarda el refresh token nuevo de cada renovación, como exige la [rotación del refresh token](https://os.utilia.ai/docs/developers/oauth/refresh-token-rotation). Si la renovación ya no es posible (la cadena caducó o se revocó, o la persona perdió el acceso), la llamada lanza una excepción: trátala volviendo a pedir la autorización.

```python
try:
    user = await sdk.oauth.get_user_info()
except Exception:
    await sdk.oauth.revoke_token()  # limpia lo guardado
    print(f"La sesión ha caducado. Autoriza de nuevo: {await sdk.oauth.get_authorization_url()}")
```

## Cerrar sesión

```python
await sdk.oauth.logout()   # en el cliente síncrono: sdk.oauth.logout()
```

Revoca el access token en UTILIA OS, lo que anula también el refresh token de esa emisión, y borra los tokens guardados.

## Qué puedes hacer con el token

El token de este flujo sirve para identificar a la persona y para llamar a los servidores MCP de UTILIA OS en su nombre (pide su `resource` y sus scopes al autorizar). Los servicios REST del SDK, como `sdk.tickets` o `sdk.users`, usan la API externa y se configuran con la clave de API de tu aplicación (`api_key`), no con OAuth.

La referencia completa de la configuración OAuth del SDK está en [SDK Python: OAuth](https://os.utilia.ai/docs/developers/sdk/oauth).
