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
newCredentialInfowith something like this:Yeah, probably clean that up a bit, but to illustrate the point.
Then you can return
encodedCredentialfromregisterWebAuthn()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.