diff --git a/uffd/mfa/templates/auth.html b/uffd/mfa/templates/auth.html
index 57f5d588454bfbe7237c99cdcdc7a148576d3983..033b98fcc29572c5f7f852d69e3dace4f9f0a9bc 100644
--- a/uffd/mfa/templates/auth.html
+++ b/uffd/mfa/templates/auth.html
@@ -51,7 +51,7 @@ function begin_webauthn() {
 		method: 'POST',
 	}).then(function(response) {
 		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) {
 		$('#webauthn-btn-text').text('Waiting for response from your device');
 		return navigator.credentials.get(options);
@@ -75,8 +75,26 @@ function begin_webauthn() {
 		} else {
 			throw new Error('Response from authenticator rejected');
 		}
-	}, function(reason) {
-		$('#webauthn-alert').text('Authentication with your FIDO token failed!');
+	}, function(err) {
+		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-spinner').addClass('d-none');
 		$('#webauthn-btn-text').text('Try FIDO token again');
diff --git a/uffd/mfa/templates/setup.html b/uffd/mfa/templates/setup.html
index 48388149007311f58184c627b243fb34a9929586..6ffeac9bae91fd2d0fa5f9c50cd6d898f2dbe286 100644
--- a/uffd/mfa/templates/setup.html
+++ b/uffd/mfa/templates/setup.html
@@ -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">
 			<div class="row m-0">
 				<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>
 					<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>
@@ -173,8 +173,11 @@ $('#webauthn-form').on('submit', function(e) {
 	fetch({{ url_for('mfa.setup_webauthn_begin')|tojson }}, {
 		method: 'POST',
 	}).then(function(response) {
-		if(response.ok) return response.arrayBuffer();
-		throw new Error('Error getting registration data!');
+		if (response.ok)
+			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) {
 		$('#webauthn-btn-text').text('Waiting for response from your device');
 		return navigator.credentials.create(options);
@@ -194,10 +197,28 @@ $('#webauthn-form').on('submit', function(e) {
 			$('#webauthn-btn-text').text('Success');
 			window.location = {{ url_for('mfa.setup')|tojson }};
 		} else {
-		throw new Error('Server rejected authenticator response');
+			throw new Error('Response from authenticator rejected');
 		}
-	}, function(reason) {
-		$('#webauthn-alert').text('Registration failed!');
+	}, function(err) {
+		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-spinner').addClass('d-none');
 		$('#webauthn-btn-text').text('Retry registration');
@@ -212,7 +233,7 @@ if (typeof(PublicKeyCredential) != "undefined") {
 	$('#webauthn-name').prop('disabled', false);
 	{% endif %}
 } 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');
 }
 
diff --git a/uffd/mfa/views.py b/uffd/mfa/views.py
index 408e6a982e1d65edf09735b98b58ca7753e9d742..a0259797d94edb4d18f88f63423efaac333c6d7b 100644
--- a/uffd/mfa/views.py
+++ b/uffd/mfa/views.py
@@ -1,4 +1,4 @@
-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
 
 from fido2.client import ClientData
@@ -117,7 +117,7 @@ def setup_webauthn_begin():
 	server = get_webauthn_server()
 	registration_data, state = server.register_begin(
 		{
-			"id": user.loginname.encode(),
+			"id": user.dn.encode(),
 			"name": user.loginname,
 			"displayName": user.displayname,
 		},
@@ -140,7 +140,6 @@ def setup_webauthn_complete():
 	method = WebauthnMethod(user, auth_data, name=data['name'])
 	db.session.add(method)
 	db.session.commit()
-	print("REGISTERED CREDENTIAL:", auth_data.credential_data)
 	return cbor.dumps({"status": "OK"})
 
 @bp.route('/setup/webauthn/<int:id>/delete')
@@ -178,6 +177,8 @@ def auth_webauthn_complete():
 	client_data = ClientData(data["clientDataJSON"])
 	auth_data = AuthenticatorData(data["authenticatorData"])
 	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(
 		session.pop("webauthn-state"),
 		creds,