Skip to content
Snippets Groups Projects
Commit 4b1539b3 authored by Julian's avatar Julian
Browse files

proper client-side webauthn error handling

parent ce9f63bf
No related branches found
No related tags found
No related merge requests found
...@@ -51,7 +51,7 @@ function begin_webauthn() { ...@@ -51,7 +51,7 @@ function begin_webauthn() {
method: 'POST', method: 'POST',
}).then(function(response) { }).then(function(response) {
if(response.ok) return response.arrayBuffer(); if(response.ok) return response.arrayBuffer();
throw new Error('No credential available to authenticate!'); throw new Error('You have not registered any U2F/FIDO2 devices for your account');
}).then(CBOR.decode).then(function(options) { }).then(CBOR.decode).then(function(options) {
$('#webauthn-btn-text').text('Waiting for response from your device'); $('#webauthn-btn-text').text('Waiting for response from your device');
return navigator.credentials.get(options); return navigator.credentials.get(options);
...@@ -75,8 +75,26 @@ function begin_webauthn() { ...@@ -75,8 +75,26 @@ function begin_webauthn() {
} else { } else {
throw new Error('Response from authenticator rejected'); throw new Error('Response from authenticator rejected');
} }
}, function(reason) { }, function(err) {
$('#webauthn-alert').text('Authentication with your FIDO token failed!'); console.log(err);
/* various webauthn errors */
if (err.name == 'NotAllowedError')
$('#webauthn-alert').text('Authentication timed out, was aborted or not allowed');
else if (err.name == 'InvalidStateError')
$('#webauthn-alert').text('Device is not registered for your account');
else if (err.name == 'AbortError')
$('#webauthn-alert').text('Authentication was aborted');
else if (err.name == 'NotSupportedError')
$('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser');
/* errors from fetch() */
else if (err.name == 'TypeError')
$('#webauthn-alert').text('Could not connect to server');
/* our own errors */
else if (err.name == 'Error')
$('#webauthn-alert').text(err.message);
/* fallback */
else
$('#webauthn-alert').text('Authentication failed ('+err+')');
$('#webauthn-alert').removeClass('d-none'); $('#webauthn-alert').removeClass('d-none');
$('#webauthn-spinner').addClass('d-none'); $('#webauthn-spinner').addClass('d-none');
$('#webauthn-btn-text').text('Try FIDO token again'); $('#webauthn-btn-text').text('Try FIDO token again');
......
...@@ -128,7 +128,7 @@ You need to setup at least one authentication method to enable two-factor authen ...@@ -128,7 +128,7 @@ You need to setup at least one authentication method to enable two-factor authen
<form id="webauthn-form" class="form mb-2"> <form id="webauthn-form" class="form mb-2">
<div class="row m-0"> <div class="row m-0">
<label class="sr-only" for="webauthn-name">Name</label> <label class="sr-only" for="webauthn-name">Name</label>
<input type="text" class="form-control mb-2 col-12 col-lg-auto mr-2" style="width: 15em;" id="webauthn-name" placeholder="Name" required {{ 'disabled' if mfa_init }}> <input type="text" class="form-control mb-2 col-12 col-lg-auto mr-2" style="width: 15em;" id="webauthn-name" placeholder="Name" required disabled>
<button type="submit" id="webauthn-btn" class="btn btn-primary mb-2 col" disabled> <button type="submit" id="webauthn-btn" class="btn btn-primary mb-2 col" disabled>
<span id="webauthn-spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span> <span id="webauthn-spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
<span id="webauthn-btn-text">Setup new device</span> <span id="webauthn-btn-text">Setup new device</span>
...@@ -173,8 +173,11 @@ $('#webauthn-form').on('submit', function(e) { ...@@ -173,8 +173,11 @@ $('#webauthn-form').on('submit', function(e) {
fetch({{ url_for('mfa.setup_webauthn_begin')|tojson }}, { fetch({{ url_for('mfa.setup_webauthn_begin')|tojson }}, {
method: 'POST', method: 'POST',
}).then(function(response) { }).then(function(response) {
if(response.ok) return response.arrayBuffer(); if (response.ok)
throw new Error('Error getting registration data!'); return response.arrayBuffer();
if (response.status == 403)
throw new Error('You need to generate recovery codes first');
throw new Error('Server error');
}).then(CBOR.decode).then(function(options) { }).then(CBOR.decode).then(function(options) {
$('#webauthn-btn-text').text('Waiting for response from your device'); $('#webauthn-btn-text').text('Waiting for response from your device');
return navigator.credentials.create(options); return navigator.credentials.create(options);
...@@ -194,10 +197,28 @@ $('#webauthn-form').on('submit', function(e) { ...@@ -194,10 +197,28 @@ $('#webauthn-form').on('submit', function(e) {
$('#webauthn-btn-text').text('Success'); $('#webauthn-btn-text').text('Success');
window.location = {{ url_for('mfa.setup')|tojson }}; window.location = {{ url_for('mfa.setup')|tojson }};
} else { } else {
throw new Error('Server rejected authenticator response'); throw new Error('Response from authenticator rejected');
} }
}, function(reason) { }, function(err) {
$('#webauthn-alert').text('Registration failed!'); console.log(err);
/* various webauthn errors */
if (err.name == 'NotAllowedError')
$('#webauthn-alert').text('Registration timed out, was aborted or not allowed');
else if (err.name == 'InvalidStateError')
$('#webauthn-alert').text('You attempted to register a device that is already registered');
else if (err.name == 'AbortError')
$('#webauthn-alert').text('Registration was aborted');
else if (err.name == 'NotSupportedError')
$('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser');
/* errors from fetch() */
else if (err.name == 'TypeError')
$('#webauthn-alert').text('Could not connect to server');
/* our own errors */
else if (err.name == 'Error')
$('#webauthn-alert').text(err.message);
/* fallback */
else
$('#webauthn-alert').text('Registration failed ('+err+')');
$('#webauthn-alert').removeClass('d-none'); $('#webauthn-alert').removeClass('d-none');
$('#webauthn-spinner').addClass('d-none'); $('#webauthn-spinner').addClass('d-none');
$('#webauthn-btn-text').text('Retry registration'); $('#webauthn-btn-text').text('Retry registration');
...@@ -212,7 +233,7 @@ if (typeof(PublicKeyCredential) != "undefined") { ...@@ -212,7 +233,7 @@ if (typeof(PublicKeyCredential) != "undefined") {
$('#webauthn-name').prop('disabled', false); $('#webauthn-name').prop('disabled', false);
{% endif %} {% endif %}
} else { } else {
$('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser!'); $('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser');
$('#webauthn-alert').removeClass('d-none'); $('#webauthn-alert').removeClass('d-none');
} }
......
from flask import Blueprint, render_template, session, request, redirect, url_for, flash, current_app from flask import Blueprint, render_template, session, request, redirect, url_for, flash, current_app, abort
import urllib.parse import urllib.parse
from fido2.client import ClientData from fido2.client import ClientData
...@@ -117,7 +117,7 @@ def setup_webauthn_begin(): ...@@ -117,7 +117,7 @@ def setup_webauthn_begin():
server = get_webauthn_server() server = get_webauthn_server()
registration_data, state = server.register_begin( registration_data, state = server.register_begin(
{ {
"id": user.loginname.encode(), "id": user.dn.encode(),
"name": user.loginname, "name": user.loginname,
"displayName": user.displayname, "displayName": user.displayname,
}, },
...@@ -140,7 +140,6 @@ def setup_webauthn_complete(): ...@@ -140,7 +140,6 @@ def setup_webauthn_complete():
method = WebauthnMethod(user, auth_data, name=data['name']) method = WebauthnMethod(user, auth_data, name=data['name'])
db.session.add(method) db.session.add(method)
db.session.commit() db.session.commit()
print("REGISTERED CREDENTIAL:", auth_data.credential_data)
return cbor.dumps({"status": "OK"}) return cbor.dumps({"status": "OK"})
@bp.route('/setup/webauthn/<int:id>/delete') @bp.route('/setup/webauthn/<int:id>/delete')
...@@ -178,6 +177,8 @@ def auth_webauthn_complete(): ...@@ -178,6 +177,8 @@ def auth_webauthn_complete():
client_data = ClientData(data["clientDataJSON"]) client_data = ClientData(data["clientDataJSON"])
auth_data = AuthenticatorData(data["authenticatorData"]) auth_data = AuthenticatorData(data["authenticatorData"])
signature = data["signature"] signature = data["signature"]
# authenticate_complete() (as of python-fido2 v0.5.0, the version in Debian Buster)
# does not check signCount, although the spec recommends it
server.authenticate_complete( server.authenticate_complete(
session.pop("webauthn-state"), session.pop("webauthn-state"),
creds, creds,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment