Back

Cómo Agregar Autenticación a una Aplicación Electron

Cómo Agregar Autenticación a una Aplicación Electron

Si has buscado cómo agregar autenticación a una aplicación Electron, probablemente hayas encontrado tutoriales que abren un BrowserWindow para mostrar un formulario de inicio de sesión, interceptan el callback de OAuth dentro de Electron y almacenan los tokens en texto plano. Ese enfoque está desactualizado, es inseguro y entra en conflicto con el funcionamiento actual de OAuth 2.0 para aplicaciones nativas.

Este artículo explica la arquitectura correcta: Authorization Code Flow con PKCE, inicio de sesión a través del navegador del sistema y un deep link o redirección loopback para devolver los tokens a tu aplicación.

Puntos Clave

  • Los flujos de inicio de sesión con BrowserWindow embebido violan el RFC 8252 y exponen a los usuarios a la interceptación de credenciales. Utiliza el navegador del sistema en su lugar.
  • PKCE (Proof Key for Code Exchange) reemplaza el client secret, el cual las aplicaciones Electron no pueden almacenar de forma segura.
  • El deep linking con un esquema URI personalizado o una redirección loopback devuelve el código de autorización al proceso principal.
  • Almacena los refresh tokens con safeStorage, que aprovecha la gestión de claves a nivel del sistema operativo (Keychain, DPAPI, libsecret).
  • Aísla el proceso renderer con contextIsolation: true y expón únicamente los métodos IPC estrictamente necesarios a través de contextBridge.

Por Qué la Autenticación en Electron Es Diferente a la Autenticación Web

Las aplicaciones Electron se ejecutan en una máquina controlada por el usuario. No existe un backend donde ocultar un client secret, ni un sandbox del navegador que aísle tu aplicación del sistema operativo. Esto cambia significativamente el modelo de amenazas.

El RFC 8252, el estándar OAuth 2.0 escrito específicamente para aplicaciones nativas y de escritorio, es explícito: no uses web views embebidos para flujos de inicio de sesión OAuth. Un BrowserWindow embebido puede interceptar credenciales de forma silenciosa, y el usuario no tiene manera de verificar que está interactuando con un proveedor de identidad legítimo. Utiliza el navegador del sistema en su lugar.

La Arquitectura Correcta: OAuth 2.0 con PKCE y Deep Linking

El flujo recomendado es el siguiente:

  1. El usuario hace clic en “Iniciar Sesión” en tu aplicación Electron.
  2. Tu aplicación abre el navegador del sistema con una URL de autorización.
  3. El usuario se autentica con tu proveedor de identidad (Auth0, Clerk, Firebase Authentication, AWS Cognito o cualquier proveedor compatible con OpenID Connect).
  4. El proveedor redirige a un esquema URI personalizado como myapp://auth/callback o a una dirección loopback como http://127.0.0.1:PORT/callback.
  5. El proceso principal de Electron intercepta esa redirección e intercambia el código de autorización por tokens.
  6. Los tokens se almacenan de forma segura utilizando safeStorage.

Dado que las aplicaciones Electron no pueden mantener un client secret de forma segura, PKCE (Proof Key for Code Exchange) reemplaza al secret por completo. Antes de que comience el flujo, tu aplicación genera un code_verifier aleatorio, lo convierte en un hash para obtener un code_challenge y envía el challenge junto con la solicitud de autorización. Al intercambiar el código por tokens, envías el code_verifier original. El servidor de autorización verifica que coincidan. Ningún secret abandona tu aplicación en ningún momento.

Configuración del Deep Linking para el Callback de OAuth

Registra un manejador de protocolo personalizado en tu proceso principal:

// main.js
const { app } = require('electron');
const path = require('path');

if (process.defaultApp) {
  if (process.argv.length >= 2) {
    app.setAsDefaultProtocolClient('myapp', process.execPath, [
      path.resolve(process.argv[1]),
    ]);
  }
} else {
  app.setAsDefaultProtocolClient('myapp');
}

// macOS: handle the deep link callback
app.on('open-url', (event, callbackUrl) => {
  event.preventDefault();
  handleAuthCallback(callbackUrl); // parse code, exchange for tokens
});

// Windows and Linux: deep links arrive via second-instance
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
  app.quit();
} else {
  app.on('second-instance', (event, argv) => {
    const callbackUrl = argv.find((arg) => arg.startsWith('myapp://'));
    if (callbackUrl) handleAuthCallback(callbackUrl);
  });
}

En Windows y Linux, el evento second-instance entrega el deep link como argumento de línea de comandos. En macOS, el evento correcto es open-url. Para el empaquetado, configura tu protocolo en electron-builder:

{
  "protocols": {
    "name": "myapp",
    "schemes": ["myapp"]
  }
}

Nota: En macOS y Linux, el deep linking generalmente solo funciona de manera confiable en aplicaciones empaquetadas, ya que el manejador de protocolo debe registrarse con el sistema operativo. Siempre prueba el deep linking contra una compilación empaquetada, no solo con electron ..

Almacenamiento Seguro de Tokens con safeStorage de Electron

Nunca almacenes tokens en localStorage, archivos de texto plano o electron-store sin cifrado. Utiliza safeStorage, que cifra los datos mediante la gestión de claves a nivel del sistema operativo (Keychain en macOS, DPAPI en Windows, libsecret en Linux):

const { app, safeStorage } = require('electron');
const fs = require('fs');
const path = require('path');

const TOKEN_PATH = path.join(app.getPath('userData'), 'refresh.enc');

async function saveRefreshToken(token) {
  if (!safeStorage.isEncryptionAvailable()) {
    throw new Error('Encryption is not available on this system');
  }

  const encrypted = await safeStorage.encryptStringAsync(token);
  fs.writeFileSync(TOKEN_PATH, encrypted);
}

async function loadRefreshToken() {
  if (!fs.existsSync(TOKEN_PATH)) return null;

  const encrypted = fs.readFileSync(TOKEN_PATH);
  return await safeStorage.decryptStringAsync(encrypted);
}

Verifica siempre safeStorage.isEncryptionAvailable() antes de cifrar, especialmente en Linux donde el keyring del sistema operativo puede no estar configurado. En algunos sistemas Linux, Electron puede recurrir a un backend basic_text si no hay un keyring seguro disponible. Puedes inspeccionar el backend activo con safeStorage.getSelectedStorageBackend() y advertir a los usuarios según corresponda.

Mantenimiento del Aislamiento del Proceso Renderer

El proceso renderer nunca debería acceder directamente a los tokens. Utiliza contextIsolation, nodeIntegration: false, y expón únicamente lo que el renderer necesita a través de un contextBridge con permisos mínimos:

// preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('auth', {
  login: () => ipcRenderer.invoke('auth:login'),
  logout: () => ipcRenderer.send('auth:logout'),
  getProfile: () => ipcRenderer.invoke('auth:get-profile'),
});

El proceso principal gestiona todas las operaciones con tokens. El renderer solo invoca métodos IPC con nombre. Esto limita el radio de impacto en caso de que un renderer sea comprometido por contenido malicioso.

Elección de un Proveedor de Identidad

La arquitectura descrita anteriormente funciona con cualquier proveedor compatible con OpenID Connect. Entre las opciones más populares para aplicaciones Electron se encuentran Auth0, Clerk, Firebase Authentication y AWS Cognito. Registra tu aplicación como tipo Nativa o De escritorio en el proveedor que elijas, y configura tu esquema URI personalizado como URI de redirección permitida.

El Patrón que Debes Evitar

Muchos tutoriales todavía muestran este patrón:

// ❌ Do not do this
const authWindow = new BrowserWindow({ /* ... */ });
authWindow.loadURL(authorizationUrl);
authWindow.webContents.session.webRequest.onBeforeRequest(
  { urls: ['http://localhost/callback*'] },
  ({ url }) => { /* intercept tokens */ }
);

Este patrón embebe el flujo de inicio de sesión dentro de Electron, lo cual viola el RFC 8252 y elimina la capacidad del usuario de verificar el proveedor de identidad. Evítalo independientemente de cuántos tutoriales sigan recomendándolo.

Conclusión

La forma correcta de agregar autenticación a una aplicación Electron es directa: abre el navegador del sistema, utiliza OAuth 2.0 con PKCE, gestiona el callback a través de deep linking o una redirección loopback, y almacena los tokens con safeStorage. Mantén el proceso renderer aislado de toda la lógica de tokens. Esta arquitectura es la que recomienda el RFC 8252 y la que esperan los proveedores modernos como Auth0 y Clerk cuando registras una aplicación nativa.

Preguntas Frecuentes

Sí. Una redirección loopback utiliza un servidor HTTP local en 127.0.0.1 con un puerto aleatorio para recibir el callback de OAuth. Evita el registro del protocolo a nivel del sistema operativo y funciona bien durante el desarrollo. La desventaja es que debes iniciar y detener el servidor en cada inicio de sesión, y algunos proveedores requieren una configuración explícita de URIs loopback en su panel de control.

Sí. Aunque tu proveedor emita un client secret, no debes incluirlo en un binario de Electron porque los usuarios pueden extraerlo. PKCE protege el intercambio del código de autorización independientemente de si existe un secret. Registra tu aplicación como cliente nativo o público para que PKCE sea obligatorio y no se espere el uso de un secret.

Almacena únicamente el refresh token con safeStorage. Cuando el access token expire, el proceso principal envía el refresh token al endpoint de tokens del proveedor y recibe un nuevo access token. Mantén los access tokens en memoria dentro del proceso principal, nunca en el renderer. Si el refresh token es revocado o ha expirado, solicita al usuario que inicie sesión nuevamente a través del navegador del sistema.

Los manejadores de protocolo personalizados deben registrarse con el sistema operativo, y ese registro generalmente ocurre cuando la aplicación se instala o empaqueta. Al ejecutar electron ., se lanza el binario de Electron directamente, por lo que el sistema operativo no sabe que tu aplicación gestiona el esquema myapp://. Siempre prueba el deep linking contra una compilación empaquetada generada con electron-builder o una herramienta similar.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay