Logo

dev-resources.site

for different kinds of informations.

Implementing FIDO2 Authentication: A Developer's Step-by-Step Guide

Published at
12/9/2024
Categories
fido
digitalidentity
iam
authentication
Author
deepakgupta
Author
11 person written this
deepakgupta
open
Implementing FIDO2 Authentication: A Developer's Step-by-Step Guide
  • Intro
  • Why FIDO2?
  • Implementation Overview
  • Step-by-Step Guide
  • Common Challenges & Solutions
  • Testing Your Implementation
  • Security Best Practices

Introduction to FIDO2 Authentication

Implementing FIDO2 Authentication: A Developer's Step-by-Step Guide

FIDO2 is the latest set of specifications from the FIDO Alliance, aiming to enable passwordless authentication. It comprises two main components:

  • WebAuthn API : A web standard published by the World Wide Web Consortium (W3C) that allows web applications to use public-key cryptography instead of passwords.
  • Client to Authenticator Protocol (CTAP): A protocol that enables an external authenticator (like a hardware security key) to communicate with the client (like a web browser).

Key Benefits of FIDO2:

  • Enhanced Security : Uses asymmetric cryptography, reducing the risk of credential theft.
  • Improved User Experience : Eliminates the need for passwords, making authentication seamless.
  • Phishing Resistance : Credentials are bound to specific origins, mitigating phishing attacks.

Why FIDO2?

Before diving into the implementation, let's understand why FIDO2 is worth your time:

βœ… No More Password Headaches

  • Zero password storage
  • No reset workflows needed
  • Reduced support costs

βœ… Superior Security

  • Phishing-resistant
  • Uses public key cryptography
  • Eliminates credential database risks

βœ… Better User Experience

  • Fast biometric authentication
  • No passwords to remember
  • Works across devices

Implementation Overview

Here's what we'll build:

  1. User registration with FIDO2 credentials
  2. Passwordless login using those credentials
  3. Secure session management

Implementing FIDO2 Authentication: A Developer's Step-by-Step Guide
FIDO Authentication Flow

What You'll Need

// Required packages for Node.js
npm install fido2-lib express body-parser

Enter fullscreen mode Exit fullscreen mode

Hardware Requirements

  • Authenticator Devices : FIDO2-compatible security keys (e.g., YubiKey 5 Series) or biometric devices like fingerprint scanners.
  • Development Machine : A computer capable of running a web server and accessing the internet.
  • Test Devices : Multiple browsers and devices for cross-platform testing.

Software Requirements

  • Programming Language : Knowledge of JavaScript for client-side and a server-side language like Node.js, Python, or Java.
  • Web Server : Apache, Nginx, or any server capable of handling HTTPS requests.
  • Databases : MySQL, PostgreSQL, MongoDB, or any database for storing user credentials.
  • Libraries and Frameworks :
    • Client-Side : Support for the WebAuthn API.
    • Server-Side : FIDO2 server libraries compatible with your programming language.

Dependencies and Tools

  • SSL Certificates : HTTPS is required for WebAuthn.
  • Browser Support : Latest versions of Chrome, Firefox, Edge, or Safari.
  • Development Tools : Code editor (e.g., Visual Studio Code), Postman for API testing.

Basic Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ Browser β”‚ ←──► β”‚ Server β”‚ ←──► β”‚ Database β”‚
β”‚ (WebAuthn) β”‚ β”‚ (FIDO2Lib) β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Enter fullscreen mode Exit fullscreen mode

Step-by-Step Guide

1. Server Setup

First, let's set up our Express server with FIDO2 capabilities:

const express = require('express');
const { Fido2Lib } = require('fido2-lib');
const app = express();

// Initialize FIDO2
const f2l = new Fido2Lib({
  timeout: 60000,
  rpId: "example.com",
  rpName: "FIDO Example App",
  challengeSize: 32,
  attestation: "none"
});

app.use(express.json());

Enter fullscreen mode Exit fullscreen mode

2. Registration Endpoint

Create an endpoint to start the registration process:

app.post('/auth/register-begin', async (req, res) => {
  try {
    const user = {
      id: crypto.randomBytes(32),
      name: req.body.username,
      displayName: req.body.displayName
    };

    const registrationOptions = await f2l.attestationOptions();

    // Add user info to the options
    registrationOptions.user = user;
    registrationOptions.challenge = Buffer.from(registrationOptions.challenge);

    // Store challenge for verification
    req.session.challenge = registrationOptions.challenge;
    req.session.username = user.name;

    res.json(registrationOptions);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Enter fullscreen mode Exit fullscreen mode

3. Client-Side Registration

Here's the frontend JavaScript to handle registration:

async function registerUser() {
  // 1. Get registration options from server
  const response = await fetch('/auth/register-begin', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: '[email protected]' })
  });
  const options = await response.json();

  // 2. Create credentials using WebAuthn
  const credential = await navigator.credentials.create({
    publicKey: {
      ...options,
      challenge: base64ToBuffer(options.challenge),
      user: {
        ...options.user,
        id: base64ToBuffer(options.user.id)
      }
    }
  });

  // 3. Send credentials to server
  await fetch('/auth/register-complete', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      id: credential.id,
      rawId: bufferToBase64(credential.rawId),
      response: {
        attestationObject: bufferToBase64(
          credential.response.attestationObject
        ),
        clientDataJSON: bufferToBase64(
          credential.response.clientDataJSON
        )
      }
    })
  });
}

// Helper functions
function bufferToBase64(buffer) {
  return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}

function base64ToBuffer(base64) {
  return Uint8Array.from(atob(base64), c => c.charCodeAt(0));
}

Enter fullscreen mode Exit fullscreen mode

4. Authentication Flow

Server-side authentication endpoint:

app.post('/auth/login-begin', async (req, res) => {
  try {
    const assertionOptions = await f2l.assertionOptions();

    // Get user's registered credentials from database
    const user = await db.getUser(req.body.username);
    assertionOptions.allowCredentials = user.credentials.map(cred => ({
      id: cred.credentialId,
      type: 'public-key'
    }));

    req.session.challenge = assertionOptions.challenge;
    req.session.username = req.body.username;

    res.json(assertionOptions);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Enter fullscreen mode Exit fullscreen mode

Client-side authentication:

async function loginUser() {
  // 1. Get authentication options
  const response = await fetch('/auth/login-begin', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: '[email protected]' })
  });
  const options = await response.json();

  // 2. Get assertion from authenticator
  const assertion = await navigator.credentials.get({
    publicKey: {
      ...options,
      challenge: base64ToBuffer(options.challenge),
      allowCredentials: options.allowCredentials.map(cred => ({
        ...cred,
        id: base64ToBuffer(cred.id)
      }))
    }
  });

  // 3. Verify with server
  await fetch('/auth/login-complete', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      id: assertion.id,
      rawId: bufferToBase64(assertion.rawId),
      response: {
        authenticatorData: bufferToBase64(
          assertion.response.authenticatorData
        ),
        clientDataJSON: bufferToBase64(
          assertion.response.clientDataJSON
        ),
        signature: bufferToBase64(
          assertion.response.signature
        )
      }
    })
  });
}

Enter fullscreen mode Exit fullscreen mode

Common Challenges & Solutions

1. Browser Compatibility

// Check if WebAuthn is supported
if (!window.PublicKeyCredential) {
  console.log('WebAuthn not supported');
  // Fall back to traditional authentication
  return;
}

// Check if user verifying platform authenticator is available
const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
if (!available) {
  console.log('Platform authenticator not available');
  // Consider security key instead
}

Enter fullscreen mode Exit fullscreen mode

2. Error Handling

// Client-side error handling
try {
  const credential = await navigator.credentials.create({/*...*/});
} catch (error) {
  switch (error.name) {
    case 'NotAllowedError':
      console.log('User declined to create credential');
      break;
    case 'SecurityError':
      console.log('Origin not secure');
      break;
    default:
      console.error('Unknown error:', error);
  }
}

Enter fullscreen mode Exit fullscreen mode

3. Base64 URL Encoding

function base64UrlEncode(buffer) {
  const base64 = bufferToBase64(buffer);
  return base64.replace(/\+/g, '-')
               .replace(/\//g, '_')
               .replace(/=/g, '');
}

Enter fullscreen mode Exit fullscreen mode

Testing Your Implementation

1. Basic Test Suite

describe('FIDO2 Authentication', () => {
  it('should generate registration options', async () => {
    const response = await fetch('/auth/register-begin', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username: '[email protected]' })
    });
    const options = await response.json();

    expect(options).toHaveProperty('challenge');
    expect(options).toHaveProperty('rp');
    expect(options.rp.name).toBe('FIDO Example App');
  });
});

Enter fullscreen mode Exit fullscreen mode

2. Virtual Authenticator Testing

// Using Chrome's Virtual Authenticator Environment
const virtualAuthenticatorOptions = {
  protocol: 'ctap2',
  transport: 'internal',
  hasResidentKey: true,
  hasUserVerification: true,
  isUserConsenting: true
};

const authenticator = await driver.addVirtualAuthenticator(
  virtualAuthenticatorOptions
);

Enter fullscreen mode Exit fullscreen mode

Security Best Practices

  1. Always Use HTTPS
if (window.location.protocol !== 'https:') {
  throw new Error('FIDO2 requires HTTPS');
}

Enter fullscreen mode Exit fullscreen mode
  1. Validate Origin
const expectedOrigin = 'https://example.com';
const clientDataJSON = JSON.parse(
  new TextDecoder().decode(credential.response.clientDataJSON)
);
if (clientDataJSON.origin !== expectedOrigin) {
  throw new Error('Invalid origin');
}

Enter fullscreen mode Exit fullscreen mode
  1. Challenge Verification
if (!timingSafeEqual(
  storedChallenge,
  credential.response.challenge
)) {
  throw new Error('Challenge mismatch');
}

Enter fullscreen mode Exit fullscreen mode

Production Checklist

βœ… HTTPS configured

βœ… Error handling implemented

βœ… Browser support detection

βœ… Backup authentication method

βœ… Rate limiting enabled

βœ… Logging system in place

βœ… Security headers configured

Next Steps

  1. Implement user presence verification
  2. Add transaction confirmation
  3. Set up backup authentication methods
  4. Configure audit logging
  5. Implement rate limiting

Resources:

Need help? Join Discord community for support.

authentication Article's
30 articles in total
Favicon
Pushed Authorization Requests in .NET 9: Why and How to Use Them
Favicon
Google Authentication in MERN Stack
Favicon
JWT Authentication With NodeJS
Favicon
The Speakeasy Door to Your Network - Port Knocking (2)
Favicon
The Speakeasy Door to Your Network - Port Knocking (1)
Favicon
[Part 1] Rails 8 Authentication but with JWT
Favicon
Understanding Passkeys: The Behind-the-Scenes Magic of Passwordless Authentication
Favicon
Introduction to 3D Secure: Enhancing Online Payment Security
Favicon
Accessing the AuthAction Management API with Machine-to-Machine Application
Favicon
Ensuring Successful Passkey Deployment: Testing Strategies for Enterprises
Favicon
Learn Django REST Framework Authentication: A Complete Step-by-Step Python Guide
Favicon
Bulletproof JWT Authentication: Essential Security Patterns for Production Apps
Favicon
Django Authentication Made Easy: A Complete Guide to Registration, Login, and User Management
Favicon
How to Configure GitHub Authentication Using SSH Certificates
Favicon
Building an Attendance System Powered by Face Recognition Using React and FACEIO
Favicon
Password Composition Policies Are Bad and Here's Why
Favicon
JSON Web Tokens (JWT): GuΓ­a Esencial y Buenas PrΓ‘cticas
Favicon
How to integrate Passkeys into Enterprise Stacks?
Favicon
Authentication and Authorization: A Tale of Security, Flaws, and Fixes πŸ΄β€β˜ οΈ
Favicon
Using Clerk SSO to access Google Calendar and other service data
Favicon
Implementing FIDO2 Authentication: A Developer's Step-by-Step Guide
Favicon
How to Create a quick Authentication library for NestJS/MongoDB application
Favicon
Initial Planning & Technical Assessment for Passkeys
Favicon
Authentication with Clerk in NestJS Server Application
Favicon
SSO with Firebase Authentication
Favicon
Laravel Authentication Using Passport
Favicon
Django built-in authentication system
Favicon
Convex & Kinde
Favicon
Streamline enterprise customer onboarding with SAML and Clerk
Favicon
Γ‰ seguro guardar dados do usuΓ‘rio no localStorage?

Featured ones: