I want to implement WebAuthn using Java/Vaadin for the client side.
My goal is to reach that point that any users can register/enroll a WebAuthn in the context of 2FA. The created token should be saved on a privacyIDEA server for later use. It also handles the authentification of the users.
What I have done so far:
- Creating the web application with Vaadin (Java framework)
- Implementing methods for the token management in Java (still unfinished)
- Using a JavaScript client plugin to faciliate authentification against privacyIDEA server using WebAuthn tokens
Code snippet from JavaScript client-side library:
window.registerWebAuthn = function (challenge, rpName, rpId, userId, userName, displayName) {
const publicKeyCredentialCreationOptions = {
challenge: new Uint8Array(challenge),
rp: {
name: rpName,
id : rpId
},
user: {
id: new Uint8Array(userId),
name: userName,
displayName: displayName
}
};
navigator
.credentials
.create({publicKey: publicKeyCredentialCreationOptions})
.then(function (newCredentialInfo) {
console.log(newCredentialInfo);
}).catch(function (err) {
console.error(err);
});
};
The JavaScript function registerWebAuthn
takes several parameters and utilizes the WebAuthn API to create a new public key credential. The then
block handles the successful creation of the credential, and it logs the newCredentialInfo
to the console.
It is expected to start the WebAuthn registration process by pressing the button Create token. To do this, I call the Js function registerWebAuthn()
inside Vaadins executeJs()
function.
Vaadin/Java code:
@JsModule("./src/pi-webauthn.js")
public class MFAPageView {
private Button buttonPrimary0 = new Button();
public MFAPageView() {
buttonPrimary0.setText("Create token");
layoutColumn2.add(buttonPrimary0);
buttonPrimary0.addClickListener(e -> {
UI.getCurrent().getPage().executeJs("registerWebAuthn()");
}
}
How do I access the WebAuthn credentials in newCredentialInfo
and send them to privacyIDEA server to perform user authentification?
Do you have any idea?
Update
The communication between Mozilla Firefox and my Yubikey works fine. The user credentials are being created and can be displayed in the browser console.
Output:
PublicKeyCredential { rawId: ArrayBuffer, response: AuthenticatorAttestationResponse, id: "nV8W7CnC2ZZbNtajZ_gTPWrmuzH52rcHChbZ4qPSfg0CjPMI8SpYNZ8-dCO6TyTzOZavLrRtmw6r0N_9Oem-yw", type: "public-key" }
id: "nV8W7CnC2ZZbNtajZ_gTPWrmuzH52rcHChbZ4qPSfg0CjPMI8SpYNZ8-dCO6TyTzOZavLrRtmw6r0N_9Oem-yw"
rawId: ArrayBuffer { byteLength: 64 }
...
response: AuthenticatorAttestationResponse { attestationObject: ArrayBuffer, clientDataJSON: ArrayBuffer }
...
type: "public-key"
<prototype>: PublicKeyCredentialPrototype { getClientExtensionResults: getClientExtensionResults(), rawId: Getter, response: Getter, … }
...
I need these credentials to be transferred to privacyIDEA to finish the registration of the WebAuthn token. Before I can do that, the credentials need to be transferred to the Java/Vaadin client.
For this purpose I edited the registerWebAuthn
function slightly:
window.registerWebAuthn = function () {
const publicKeyCredentialCreationOptions = {
challenge: new Uint8Array(26),
rp: {
name: "Example",
id : "localhost"
},
user: {
id: new Uint8Array(26),
name: "[email protected]",
displayName: "Foo Bar"
}
};
navigator
.credentials
.create({publicKey: publicKeyCredentialCreationOptions})
.then(function (newCredentialInfo) {
console.log(newCredentialInfo);
return newCredentialInfo;
}).catch(function (err) {
console.error(err);
});
};
The essentiell part for me is coming now. I have tried to return newCredentialInfo
in the Java console. For that I changed the Vaadin/Java code:
@JsModule("./src/pi-webauthn.js")
public class MFAPageView {
private Button buttonPrimary0 = new Button();
public MFAPageView() {
buttonPrimary0.setText("Create token");
layoutColumn2.add(buttonPrimary0);
buttonPrimary0.addClickListener(e -> {
UI.getCurrent().getPage().executeJs("return registerWebAuthn()")
.then(newCredentialInfo -> {
System.out.println(newCredentialInfo);
System.out.println(new Gson().toJson(newCredentialInfo));
});
});
Unfortunately I get an empty string as a result.
elemental.json.impl.JreJsonNull@5f7e6f19
{}
What is wrong with my code?
You can encode
newCredentialInfo
with something like this:Yeah, probably clean that up a bit, but to illustrate the point.
Then you can return
encodedCredential
fromregisterWebAuthn()
and handle it in athen()
of yourexecuteJS()
call. Something like:Again, you might really want to clean up this code.
HTH even though it's not exactly production ready.