Skip to content

Instantly share code, notes, and snippets.

@mahnunchik
Created September 29, 2023 00:41
Show Gist options
  • Save mahnunchik/596637aaa81d078ef76e2d9e828d0a3c to your computer and use it in GitHub Desktop.
Save mahnunchik/596637aaa81d078ef76e2d9e828d0a3c to your computer and use it in GitHub Desktop.
WebAuthn Electron
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://webauthn.io/; script-src 'self' 'unsafe-inline' https://unpkg.com/; style-src 'self' 'unsafe-inline'">
<title>Hello World!</title>
<script src="https://unpkg.com/@simplewebauthn/browser"></script>
</head>
<body>
<h1>Hello World!</h1>
<div>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
</div>
<div>
<p>browserSupportsWebAuthn: <span id="browserSupportsWebAuthn"></span><p>
<p>browserSupportsWebAuthnAutofill: <span id="browserSupportsWebAuthnAutofill"></span><p>
</div>
<div class="col-sm-12 mb-3">
<input
type="text"
id="username"
placeholder="example_username"
x-model="username"
autocomplete="username webauthn"
/>
</div>
<button
id="register-button"
onclick="startRegistration()"
>
Register
</button>
<button
id="register-button"
onclick="startAuthentication()"
>
Authenticate
</button>
<script type="module" src="./renderer.js"></script>
</body>
</html>
const { app, protocol, net, BrowserWindow } = require('electron')
const path = require('path')
const { pathToFileURL } = require('url');
function createWindow () {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
})
mainWindow.loadURL('https://webauthn.io/')
mainWindow.webContents.openDevTools()
}
app.whenReady().then(() => {
protocol.handle('https', (req) => {
const { host, pathname } = new URL(req.url);
if (host === 'webauthn.io' && (pathname === '/' || pathname === '/renderer.js' )) {
const url = new URL('.' + (pathname === '/' ? '/index.html' : pathname), pathToFileURL(__filename));
return net.fetch(url);
}
return net.fetch(req, { bypassCustomProtocolHandlers: true });
});
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
{
"name": "lumpy-dime-set-9mmzx",
"productName": "lumpy-dime-set-9mmzx",
"description": "My Electron application description",
"keywords": [],
"main": "./main.js",
"version": "1.0.0",
"author": "evlasenko",
"scripts": {
"start": "electron ."
},
"dependencies": {},
"devDependencies": {
"electron": "26.2.2"
}
}
/**
* The preload script runs before. It has access to web APIs
* as well as Electron's renderer process modules and some
* polyfilled Node.js functions.
*
* https://www.electronjs.org/docs/latest/tutorial/sandbox
*/
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
document.getElementById('browserSupportsWebAuthn').innerHTML = SimpleWebAuthnBrowser.browserSupportsWebAuthn();
document.getElementById('browserSupportsWebAuthnAutofill').innerHTML = await SimpleWebAuthnBrowser.browserSupportsWebAuthnAutofill();
window.startRegistration = async function startRegistration() {
const username = document.getElementById('username').value;
if (!username) {
console.error('Please enter a username to register');
return;
}
const apiRegOptsResp = await fetch('https://webauthn.io/registration/options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
// e.g. 'preferred'
user_verification: 'preferred',
// e.g. 'direct'
attestation: 'none',
// e.g. 'platform'
attachment: 'all',
// e.g. ['es256', 'rs256']
//algorithms,
// e.g. 'preferred'
discoverable_credential: 'preferred',
}),
});
const registrationOptionsJSON = await apiRegOptsResp.json();
console.log('REGISTRATION OPTIONS');
console.log(JSON.stringify(registrationOptionsJSON, null, 2));
// Start WebAuthn registration
const regResp = await SimpleWebAuthnBrowser.startRegistration(registrationOptionsJSON);
console.log('REGISTRATION RESPONSE');
console.log(JSON.stringify(regResp, null, 2));
// Submit response
const apiRegVerResp = await fetch('https://webauthn.io/registration/verification', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
response: regResp,
}),
});
const verificationJSON = await apiRegVerResp.json()
// Display outcome
if (verificationJSON.verified === true) {
console.log('Success! Now try to authenticate...');
} else {
console.error(`Registration failed: ${verificationJSON.error}`);
}
}
window.startAuthentication = async function startAuthentication(startConditionalUI = false) {
const username = document.getElementById('username').value;
if (!username) {
console.error('Please enter a username to register');
return;
}
// Submit options
const apiAuthOptsResp = await fetch('https://webauthn.io/authentication/options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
user_verification: 'preferred',
}),
});
const authenticationOptionsJSON = await apiAuthOptsResp.json();
console.log('AUTHENTICATION OPTIONS');
console.log(JSON.stringify(authenticationOptionsJSON, null, 2));
if (authenticationOptionsJSON.error) {
console.error(authenticationOptionsJSON.error);
return;
}
// Start WebAuthn authentication
const authResp = await SimpleWebAuthnBrowser.startAuthentication(authenticationOptionsJSON, startConditionalUI);
console.log('AUTHENTICATION RESPONSE');
console.log(JSON.stringify(authResp, null, 2));
// Submit response
const apiAuthVerResp = await fetch('https://webauthn.io/authentication/verification', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
response: authResp,
}),
});
const verificationJSON = await apiAuthVerResp.json()
if (verificationJSON.verified === true) {
console.log('Authentication Success!');
} else {
console.error(`Authentication failed: ${verificationJSON.error}`);
}
}
/* styles.css */
/* Add styles here to customize the appearance of your app */
@juanluisbaptiste
Copy link

@mahnunchik I was thinking of testing this out with Ferdium, do you know where to add your workaround in that case?

@gavan1
Copy link

gavan1 commented Mar 28, 2025

I get this error

FIDO: fido_discovery_factory.cc:70 Cannot use Bluetooth because the responsible app for the process does not have Bluetooth metadata in its Info.plist. Launch from Finder to fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment