✨ SvelteKit FIDO U2F Two Factor Authentication (2FA)
This is post is about SvelteKit FIDO U2F Login. FIDO U2F provides a way to beef up login security using a hardware USB token owned by the user. In computing typically we authenticate ourselves (let the system know we are who we say we are) using three possible types of identifier. These are: something we know (like a password), something we have (like a 2FA code generated by an authenticator app) or something we are (like biometric data generated using facial recognition or a thumbprint). Traditionally, we relied on a password only, though requesting a second authentication type (two factor authentication or 2FA) or multifactor authentication (MFA) is now more commonplace. FIDO U2F keys fall into the second category and make login more resistant to phishing attacks than other second factor methods.
In this post we look at what FIDO is in more detail and how you might implement the front end in a Svelte app. You might add this code to a roll-your-own login solution or even add FIDO U2F support while using an authentication service which does not offer the feature. I hope you find it interesting and useful.
🔑 What is FIDO U2F
U2F is a shorthand for Universal 2nd Factor. The FIDO part comes from the FIDO Alliance which is a body for generating open security standards. FIDO U2F is an open standard and you can buy a (relatively cheap) device from a number of manufacturers. Yubico make FIDO U2F keys as well as keys with additional proprietary features enabled. You could implement login for your app for the advanced Yubico key features, though it makes sense at least to support the more widely available FIDO U2F keys. Although USB devices are most common there are also bluetooth devices suitable for use with some phones.
Browser Support
The latest versions of both Firefox and Chrome browsers support FIOO U2F. In the code below we look at a progressive enhancement method for adding SvelteKit FIDO U2F login.
How it Works
The login process involves the user being prompted to insert the device into their machine and tap its button. Authentication follows a challenge-response pattern. The device is treated like an human input device (such as a keyboard) by the computer and so widely supported. When plugged in, the device receives the challenge and once the user presses the button it generates and sends the response. That response allows the server to authenticate the user, using cryptography and a stored code unique to the user generated on initial registration.
FIDO U2F keys are quite secure and less susceptible to phishing attacks than using an authenticator app for example. It is not possible to clone the device and it needs to be physically with the user on log in. Now we know a little about how the devices work, let's look at a SvelteKit implementation of the front end.
🧑🏽 SvelteKit FIDO U2F Login Client Code
We look at the front end here using the u2f-api
package. Your app will also need a back end with a database to store the user key permanently and also a way of temporarily caching the challenge sent to the user.
Sample Code
Let's look at some code which lets the user login with or register a FIDO U2F token. This could be included on a login screen and also in the user setting part of your app. Looking at the markup first:
{#if fidoU2fSupported}
{#if registering}
<form on:submit|preventDefault={completeRegistration}>
<TextInputField
value={label}
required
placeholder={PLACEHOLDER_TEXT.fidoU2fLabel}
id="fido-u2f-key-label"
title={TITLE.fidoU2fLabel}
on:update={(event) => {
label = event.detail;
}}
/>
<button type="submit" disabled={registerData == null && label === ''}
>Complete registration</button
>
</form>
{/if}
<button on:click={handleAuthenticateRegister} disabled={submitting}
>{registerLoginButtonText}</button
>
{:else}
<div>FIDO U2F is not supported on your browser</div>
{/if}
In line 101
we have a check to make sure the user browser supports FIDO U2F. They might have registered already on another device which does support it so this feedback is helpful for user experience. If the user is registering we can show an input which lets them label the device. It is common to have a backup device which is stored somewhere safe and only used if the everyday device is damaged or lost. For that reason the user may have multiple keys registered. In a more fully-featured version, we could let the user select the device they want to use for the present login from a list.
Finally the button is there so the user can find the key and be ready before completing the login process. We use the same button for login and registration, just varying the text depending on the case.
Component JavaScript Code
import { isSupported, register, sign } from 'u2f-api';
$: fidoU2fSupported = checkFidoU2FSupport();
async function handleAuthenticateRegister() {
if (fidoU2fRegistered) {
await handleAuthenticate();
} else {
await handleRegister();
}
}
The plugin lets us check for browser support. Our code uses the isSupported
function which is a named import from u2f-api
. We put the result into a reactive variable as the result might not be available when the component first mounts. This way we update the user interface once we know either way.
Following on, we check whether we are logging the user in or registering them and proceed accordingly.
Registration
Next let's take a look at registration:
async function completeRegistration() {
try {
/* add code here to send the registration data to your server */
if (registrationSuccessful) {
await goto('/dashboard');
}
} catch (error) {
console.error(`Error in completeRegistration: ${error}`);
}
}
async function handleRegister() {
if (browser && fidoU2fSupported) {
try {
registering = true;
/* add code here to request fidoU2fBeginRegister from your server */
registerData = await register([fidoU2fBeginRegister]);
} catch (error) {
let message;
if (error?.metaData?.type) {
message = error.metaData.type;
} else {
message = error;
}
console.error(`Error in handleRegister: ${message}`);
}
}
}
The handleRegister
function will be called first in the registration flow. To start registration we need a challenge from the server so you would add this at the top of the function. Next we use the u2f-api
register function to prompt the user to insert their device. Note that as part of the FIDO U2F standard, the user must be on an HTTPS website. You can see how to run a secure SvelteKit dev server in a recent video.
The device generates registration data which we store in the registerData
variable. In the meantime the dialogue should have appeared with a text box letting the user enter a label for the device. When they submit the label, we trigger the completeRegistration
function. This sends the FIDO U2F key generated data to the server so it can store it. The data will be needed to generate challenges and also authenticate the user based on the response next time they log in.
Login
Finally the login code follows a similar pattern:
async function authenticate(fidoU2fSignRequest) {
try {
const signData = await sign(fidoU2fSignRequest);
/* add code here to send the signature to your server and get a yay or nay */
if (authorised) {
user.set({ ...$user, mfaAuthenticated: true });
await goto('/dashboard');
} else {
console.log('Access denied!');
await goto('/login');
}
} catch (error) {
console.error(`Error in authenticate function in FidoU2f: ${error}`);
}
}
async function handleAuthenticate() {
try {
/* add code here to send the registration data to your server */
if (registrationSuccessful) {
await goto('/dashboard');
}
} catch (error) {
console.error(`Error in completeRegistration: ${error}`);
}
}
With a login, the user will trigger a handleAuthenticate
call. For the first step we need to get a challenge from the server. This will be user specific so typically the server will identify the user from session data (they would have completed the first authentication step at this stage). To save an extra trip, here we assume the server responds with challenges for all of the user's registered keys and we pick the right one (with some user help) once we have them. For simplicity here, we just pass the first sign request to the authenticate
function (in line 59
).
That function calls sign
(line 38
) which prompts the user to insert their key and tap the button, generating a signature. Finally we send that signature to the server which either authorises the user or declines. Based on the response we can restart the authentication process or redirect the user to their personal dashboard.
🙌🏽 SvelteKit FIDO U2F Login: What we Learned
In this post we looked at:
what multifactor authentication is,
why you might choose FIDO U2F for second factor authentication 2FA,
some example code for implementing client side SvelteKit FIDO U2F login.
I do hope there is at least one thing in this article which you can use in your work or a side project. Because you can implement the backend in a number of different ways we focused on example code rather than building out a functioning app from start to finish. I'm keen to hear if you prefer the usual complete tutorial approach or some example code instead for this kind of project.
You can see the example code for this SvelteKit FIDO U2F login project on the Rodney Lab Git Hub repo.
🙏🏽 SvelteKit FIDO U2F Login: Feedback
Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, please consider supporting me through Buy me a Coffee.
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram. Also, see further ways to get in touch with Rodney Lab. I post regularly on SvelteKit as well as other topics. Also subscribe to the newsletter to keep up-to-date with our latest projects.