6. FIDO Passwordless/WebAuthn
There is nice tutorial we made under Krptn’s news on our homepage. Next to reading the documentation, the tutorials helps you to follow and undestand the program.
Note
In order for this section to make sense, please read User Auth first.
To see WebAuthn with Krptn implemented in action, you can have a look at our Flask example on GitHub.
First make sure that the required configuration options for FIDO are set.
Currently, we only support passwordless as a second (or third) authentication factor. The password still has to be enabled.
For security reasons, we can only have one FIDO credential registered. To remove the FIDO credential:
For convention,model
will be the current user’s user model (that is, standardUser
object). It is your task to retrieve the model using user sessions.
1model.removeFIDO()
6.1. Register
1options = model.beginFIDOSetup()
The above code generates options for FIDO. Please send these to the client’s browser. In the browser, please run the following JS:
<!---
This code was taken from Google's WebAuthn Glitch Tutorial: https://glitch.com/edit/#!/webauthn-codelab-start?path=README.md%3A1%3A0
This code was changed to work with Krypton's Auth Backends. These include changing auth URLs, loading JSON data.
Here is the original copyright notice:
Copyright 2019 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License
--->
<script async src="https://cdn.jsdelivr.net/gh/herrjemand/Base64URL-ArrayBuffer@latest/lib/base64url-arraybuffer.js"></script>
<script>
async function register() {
const response = await fetch('/fidoReg', {cache: 'no-cache'}); // /fidoReg should return FIDO options as generated above
const options = await response.json();
options.user.id = base64url.decode(options.user.id);
options.challenge = base64url.decode(options.challenge);
if (options.excludeCredentials) {
for (let cred of options.excludeCredentials) {
cred.id = base64url.decode(cred.id);
}
}
const cred = await navigator.credentials.create({
publicKey: options,
});
const credential = {};
credential.id = cred.id;
credential.rawId = base64url.encode(cred.rawId);
credential.type = cred.type;
if (cred.response) {
const clientDataJSON =
base64url.encode(cred.response.clientDataJSON);
const attestationObject =
base64url.encode(cred.response.attestationObject);
credential.response = {
clientDataJSON,
attestationObject,
};
}
localStorage.setItem('KryptonFIDOcredId', credential.id);
return await fetch('/fidoFinishReg', { // See below what /fidoFinishReg should do
body: JSON.stringify(credential),
cache: 'no-cache',
method: 'POST',
headers: {'Content-Type': 'application/json'}
});
}
</script>
Please see our tutorial for more details on the above code.
Inside /fidoFinishReg
(or however you rename it):
1import json
2model.completeFIDOSetup(json.dumps(request.get_json()["credentials"])) # Of course, depending on your web framework this will differ
6.2. Login
First of all, we need to obtain our FIDO options:
1options = model.getFIDOOptions()
These will need to be transmited to the browser, and the result (returned from the browser) of the authentication should be passed to login
function:
1model.login(pwd='MyPWD', fido=fidoResponse) # fidoResponse, is the stringified JSON from the browser.
On failure, a krypton.auth.users.bases.UserError
will be raised and model.FIDORequired
will be set to True
.
To obtain authentication result in the browser:
<!---
Some of this code was taken from Google's WebAuthn Glitch Tutorial: https://glitch.com/edit/#!/webauthn-codelab-start?path=README.md%3A1%3A0
This code was changed to work with Krypton's Auth Backends. These include changing auth URLs, loading JSON data.
Here is the original copyright notice:
Copyright 2019 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License
--->
<script async src="https://cdn.jsdelivr.net/gh/herrjemand/Base64URL-ArrayBuffer@latest/lib/base64url-arraybuffer.js"></script>
<script>
async function doFido() {
const email = document.getElementsByName('email')[0].value; // Replace with your password form
const pwd = document.getElementsByName('password')[0].value; // Replace with your password form
const query = {}
query.email = email;
// To the below request, please return the response from model.getFIDOOptions()
// Don't forget to replace your endpoint
const repsonse = await fetch('/getFidoLogin', // Replace endpoint with yours
{cache: 'no-cache',
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(query)}
);
const options = await repsonse.json();
options.challenge = base64url.decode(options.challenge);
for (let cred of options.allowCredentials) {
cred.id = base64url.decode(cred.id);
}
const cred = await navigator.credentials.get({
publicKey: options
});
const credential = {};
credential.fido = 1;
credential.id = cred.id;
credential.type = cred.type;
credential.rawId = base64url.encode(cred.rawId);
if (cred.response) {
const clientDataJSON =
base64url.encode(cred.response.clientDataJSON);
const authenticatorData =
base64url.encode(cred.response.authenticatorData);
const signature =
base64url.encode(cred.response.signature);
const userHandle =
base64url.encode(cred.response.userHandle);
credential.response = {
clientDataJSON,
authenticatorData,
signature,
userHandle,
};
}
const finalCredentials = JSON.stringify(credential);
// Please pass the stringified JSON `finalCredentials` as the `fido` parameter to the `login` function.
// You still need to provide the user's password to the funcion also.
}
</script>
Again, see our tutorial for more details on the above code.