Como Adicionar Autenticação a um Aplicativo Electron
Se você pesquisou como adicionar autenticação a um aplicativo Electron, provavelmente encontrou tutoriais que abrem uma BrowserWindow para exibir um formulário de login, interceptam o callback do OAuth dentro do Electron e armazenam tokens em texto simples. Essa abordagem é desatualizada, insegura e conflita com o funcionamento atual do OAuth 2.0 para aplicativos nativos.
Este artigo explica a arquitetura correta: Authorization Code Flow com PKCE, login através do navegador do sistema e um deep link ou redirecionamento de loopback para retornar os tokens ao seu aplicativo.
Principais Conclusões
- Fluxos de login com
BrowserWindowincorporada violam a RFC 8252 e expõem os usuários à interceptação de credenciais. Use o navegador do sistema em vez disso. - O PKCE (Proof Key for Code Exchange) substitui o client secret, que aplicativos Electron não conseguem armazenar com segurança.
- O deep linking com um esquema de URI personalizado ou um redirecionamento de loopback retorna o authorization code ao processo principal.
- Armazene refresh tokens com
safeStorage, que utiliza o gerenciamento de chaves no nível do sistema operacional (Keychain, DPAPI, libsecret). - Isole o processo renderer com
contextIsolation: truee exponha apenas métodos IPC específicos através docontextBridge.
Por Que a Autenticação no Electron É Diferente da Autenticação Web
Aplicativos Electron são executados em uma máquina controlada pelo usuário. Não há backend para ocultar um client secret e não há sandbox do navegador isolando seu aplicativo do sistema operacional. Isso altera significativamente o modelo de ameaças.
A RFC 8252, o padrão OAuth 2.0 escrito especificamente para aplicativos nativos e desktop, é explícita: não use web views incorporadas para fluxos de login OAuth. Uma BrowserWindow incorporada pode interceptar credenciais silenciosamente, e o usuário não tem como verificar se está se comunicando com um provedor de identidade legítimo. Use o navegador do sistema em vez disso.
A Arquitetura Correta: OAuth 2.0 PKCE com Deep Linking
O fluxo recomendado funciona da seguinte forma:
- O usuário clica em “Entrar” no seu aplicativo Electron.
- Seu aplicativo abre o navegador do sistema com uma URL de autorização.
- O usuário se autentica com seu provedor de identidade (Auth0, Clerk, Firebase Authentication, AWS Cognito ou qualquer provedor compatível com OpenID Connect).
- O provedor redireciona para um esquema de URI personalizado como
myapp://auth/callbackou um endereço de loopback comohttp://127.0.0.1:PORTA/callback. - O processo principal do Electron intercepta esse redirecionamento e troca o authorization code por tokens.
- Os tokens são armazenados com segurança usando
safeStorage.
Como aplicativos Electron não conseguem manter um client secret em segurança, o PKCE (Proof Key for Code Exchange) substitui o secret completamente. Antes do início do fluxo, seu aplicativo gera um code_verifier aleatório, calcula seu hash em um code_challenge e envia o challenge junto com a requisição de autorização. Ao trocar o código por tokens, você envia o code_verifier original. O servidor de autorização verifica se eles correspondem. Nenhum secret sai do seu aplicativo.
Configurando o Deep Linking para o Callback do OAuth
Registre um handler de protocolo personalizado no seu processo 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);
});
}
No Windows e Linux, o evento second-instance entrega o deep link como um argumento de linha de comando. No macOS, open-url é o evento correto. Para empacotamento, configure seu protocolo no electron-builder:
{
"protocols": {
"name": "myapp",
"schemes": ["myapp"]
}
}
Nota: No macOS e Linux, o deep linking geralmente só funciona de forma confiável em aplicativos empacotados, pois o handler de protocolo deve ser registrado no sistema operacional. Sempre teste o deep linking em uma build empacotada, não apenas com
electron ..
Discover how at OpenReplay.com.
Armazenando Tokens com Segurança usando o safeStorage do Electron
Nunca armazene tokens em localStorage, arquivos simples ou electron-store sem criptografia. Use safeStorage, que criptografa dados usando o gerenciamento de chaves no nível do sistema operacional (Keychain no macOS, DPAPI no Windows, libsecret no 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);
}
Sempre verifique safeStorage.isEncryptionAvailable() antes de criptografar, especialmente no Linux, onde o keyring do sistema operacional pode não estar configurado. Em alguns sistemas Linux, o Electron pode recorrer a um backend basic_text se nenhum keyring seguro estiver disponível. Você pode inspecionar o backend ativo com safeStorage.getSelectedStorageBackend() e alertar os usuários de forma adequada.
Mantendo o Processo Renderer Isolado
Seu processo renderer nunca deve acessar tokens diretamente. Use contextIsolation, nodeIntegration: false e exponha apenas o que o renderer precisa através de um contextBridge restrito:
// 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'),
});
O processo principal gerencia todas as operações com tokens. O renderer apenas chama métodos IPC nomeados. Isso limita o impacto caso um renderer seja comprometido por conteúdo malicioso.
Escolhendo um Provedor de Identidade
A arquitetura descrita acima funciona com qualquer provedor compatível com OpenID Connect. As opções mais populares para aplicativos Electron incluem Auth0, Clerk, Firebase Authentication e AWS Cognito. Registre seu aplicativo como do tipo Nativo ou Desktop no provedor escolhido e configure seu esquema de URI personalizado como um redirect URI permitido.
O Padrão a Evitar
Muitos tutoriais ainda mostram este padrão:
// ❌ Do not do this
const authWindow = new BrowserWindow({ /* ... */ });
authWindow.loadURL(authorizationUrl);
authWindow.webContents.session.webRequest.onBeforeRequest(
{ urls: ['http://localhost/callback*'] },
({ url }) => { /* intercept tokens */ }
);
Isso incorpora o fluxo de login dentro do Electron, o que viola a RFC 8252 e remove a capacidade do usuário de verificar o provedor de identidade. Evite essa abordagem independentemente de quantos tutoriais ainda a recomendem.
Conclusão
A maneira correta de adicionar autenticação a um aplicativo Electron é direta: abra o navegador do sistema, use OAuth 2.0 com PKCE, trate o callback através de deep linking ou redirecionamento de loopback e armazene os tokens com safeStorage. Mantenha o processo renderer isolado de toda a lógica de tokens. Essa arquitetura é o que a RFC 8252 recomenda e o que provedores modernos como Auth0 e Clerk esperam quando você registra um aplicativo nativo.
Perguntas Frequentes
Sim. Um redirecionamento de loopback usa um servidor HTTP local em 127.0.0.1 com uma porta aleatória para receber o callback do OAuth. Ele evita o registro de protocolo no nível do sistema operacional e funciona bem durante o desenvolvimento. A desvantagem é que você deve iniciar e parar o servidor a cada login, e alguns provedores exigem configuração explícita de URIs de loopback em seu painel de controle.
Sim. Mesmo que seu provedor emita um client secret, você não deve incorporá-lo em um binário Electron, pois os usuários podem extraí-lo. O PKCE protege a troca do authorization code independentemente da existência de um secret. Registre seu aplicativo como um cliente nativo ou público para que o PKCE seja obrigatório e o secret não seja esperado.
Armazene apenas o refresh token com safeStorage. Quando o access token expirar, o processo principal envia o refresh token ao endpoint de token do provedor e recebe um novo access token. Mantenha os access tokens em memória no processo principal, nunca no renderer. Se o refresh token for revogado ou expirado, solicite ao usuário que faça login novamente através do navegador do sistema.
Os handlers de protocolo personalizados devem ser registrados no sistema operacional, e esse registro geralmente ocorre quando o aplicativo é instalado ou empacotado. Executar electron . inicia o binário do Electron diretamente, portanto o sistema operacional não sabe que seu aplicativo trata o esquema myapp://. Sempre teste o deep linking em uma build empacotada produzida pelo electron-builder ou uma ferramenta 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.