<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ biometric authentication - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ biometric authentication - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 28 Jul 2026 11:52:41 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/biometric-authentication/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up WebAuthn in Node.js for Passwordless Biometric Login ]]>
                </title>
                <description>
                    <![CDATA[ JWT auth feels clean until a stolen token still looks valid to your server. That's the real problem: a bearer token proves possession of a token, but it doesn't prove possession of a trusted device. I ]]>
                </description>
                <link>https://www.freecodecamp.org/news/set-up-webauthn-in-node-js-for-passwordless-biometric-login/</link>
                <guid isPermaLink="false">69bc51d6b238fd45a32f1cb0</guid>
                
                    <category>
                        <![CDATA[ #webauthn ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ biometric authentication ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sumit Saha ]]>
                </dc:creator>
                <pubDate>Thu, 19 Mar 2026 19:43:18 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/8cc356c5-b011-4316-8236-a0443eb2cc03.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>JWT auth feels clean until a stolen token still looks valid to your server. That's the real problem: a bearer token proves possession of a token, but it doesn't prove possession of a trusted device. If an attacker gets a reusable token, replay starts to look like a normal login.</p>
<p>WebAuthn changes the shape of the system. The private key stays on the user's device. Your server stores a public key, a credential ID, and a counter. Each registration or login signs a fresh challenge. The browser, the authenticator, and your backend all take part in the ceremony.</p>
<p>This guide walks you through the full path in Node.js. You'll set up the backend, wire registration and login, store passkeys correctly, replace long-lived bearer auth with short server sessions, support backup devices, and add step-up verification for risky actions.</p>
<p><strong>Warning</strong>: WebAuthn works in secure contexts. Use localhost for local development, and use HTTPS everywhere else.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-why-jwt-alone-falls-short">Why JWT Alone Falls Short</a></p>
</li>
<li><p><a href="#heading-what-webauthn-changes">What WebAuthn Changes</a></p>
</li>
<li><p><a href="#heading-initialize-the-project">Initialize the Project</a></p>
</li>
<li><p><a href="#heading-install-dependencies">Install Dependencies</a></p>
</li>
<li><p><a href="#heading-define-the-data-model">Define the Data Model</a></p>
</li>
<li><p><a href="#heading-build-the-server-foundation">Build the Server Foundation</a></p>
</li>
<li><p><a href="#heading-registration-ceremony">Registration Ceremony</a></p>
</li>
<li><p><a href="#heading-authentication-ceremony">Authentication Ceremony</a></p>
</li>
<li><p><a href="#heading-what-replaces-the-long-lived-jwt">What Replaces the Long-Lived JWT</a></p>
</li>
<li><p><a href="#heading-multi-device-and-recovery-logic">Multi-Device and Recovery Logic</a></p>
</li>
<li><p><a href="#heading-step-up-authentication-for-sensitive-actions">Step-Up Authentication for Sensitive Actions</a></p>
</li>
<li><p><a href="#heading-recap">Recap</a></p>
</li>
<li><p><a href="#heading-try-it-yourself">Try it Yourself</a></p>
</li>
<li><p><a href="#heading-final-words">Final Words</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along and get the most out of this guide, you should have:</p>
<ul>
<li><p>Basic knowledge of JavaScript and Node.js.</p>
</li>
<li><p>Basic knowledge of TypeScript and Express.</p>
</li>
<li><p>Basic frontend to backend flow. You should be able to follow <code>fetch()</code> requests from the browser to the server and back.</p>
</li>
<li><p>A modern browser and a passkey-capable authenticator, such as Touch ID, Face ID, Windows Hello, Android biometrics, or a security key.</p>
</li>
<li><p>Local testing on <code>localhost</code>. The demo uses <code>localhost</code> as the relying party ID and origin during development.</p>
</li>
<li><p>No prior WebAuthn knowledge required. The article explains the registration and authentication flow step by step.</p>
</li>
</ul>
<h2 id="heading-why-jwt-alone-falls-short">Why JWT Alone Falls Short</h2>
<p>JWTs are not the villain.</p>
<p>The weak point is the usual deployment pattern around JWTs. Teams often place long-lived tokens in places attackers love, then trust those tokens for too long.</p>
<p>The failure path usually looks like this:</p>
<ul>
<li><p>Your server issues a reusable bearer token.</p>
</li>
<li><p>The browser stores it.</p>
</li>
<li><p>Malware, XSS, session theft, or a fake login flow grabs it.</p>
</li>
<li><p>The attacker replays it.</p>
</li>
<li><p>Your backend sees a valid token and treats the request as real.</p>
</li>
</ul>
<p>That pattern falls apart fast on high-risk routes. Admin actions, money movement, payout approval, email change, API key creation, or destructive writes deserve stronger proof.</p>
<p>WebAuthn gives you stronger proof because the secret never leaves the authenticator.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/4cecbde6-69be-4790-8c1c-68fb28d3cc00.png" alt="JWT replay vs WebAuthn challenge flow diagram" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>In the above diagram, the left side (reusable JWT path) shows the risk of reusable tokens. A server issues a JWT after login. The browser stores the token. An attacker steals the token and sends requests. The backend accepts the token until expiration. Replay becomes possible.</p>
<p>On the right side (WebAuthn path), the flow changes. The server sends a fresh challenge for each login. Your device signs the challenge using a private key stored inside the authenticator. The server verifies the signature before creating a short session.</p>
<p>The key point is simple: JWTs rely on a stored bearer secret, while WebAuthn relies on device-bound cryptographic proof created for a single challenge.</p>
<h2 id="heading-what-webauthn-changes">What WebAuthn Changes</h2>
<p>WebAuthn uses asymmetric cryptography.</p>
<p>The authenticator creates a key pair. The private key stays on the device. Your backend stores the public key and uses it later to verify signatures. During login, your server sends a fresh challenge. The device signs it, and your backend verifies the result against the stored public key.</p>
<p>That changes three things at once:</p>
<ul>
<li><p>The browser never receives a reusable password secret.</p>
</li>
<li><p>A stolen public key is useless for login.</p>
</li>
<li><p>Each ceremony depends on a fresh server challenge.</p>
</li>
</ul>
<p>On the web, passkeys ride on top of WebAuthn. A passkey might live on the local device, a synced platform account, or a physical security key. In practice, your app still deals with the same core objects: credential ID, public key, transports, counter, device type, and backup state.</p>
<h2 id="heading-initialize-the-project">Initialize the Project</h2>
<p>So far, we've talked about why WebAuthn matters and how it changes the login experience. Now it's time to build a small project so you can see the whole flow working end to end.</p>
<p>In this demo, you'll create a simple Node.js app where a user can register a passkey, sign in with that passkey, and then access protected routes through a short-lived session. The idea here is not to build a polished full-stack product. The idea is to build the core backend flow clearly, so you can understand how registration, login, sessions, and step-up verification connect to each other.</p>
<p>Before we start, make sure Node.js and npm are installed on your machine. You can install Node.js from the official website, or use nvm if you prefer managing multiple Node versions.</p>
<pre><code class="language-shell">node -v
npm -v
</code></pre>
<p>The expected output is a Node LTS version and an npm version number.</p>
<p>Next, create a new project folder and initialize the basic structure:</p>
<pre><code class="language-shell">mkdir webauthn-node-demo
cd webauthn-node-demo
npm init -y
npx tsc --init
mkdir src
</code></pre>
<h2 id="heading-install-dependencies">Install Dependencies</h2>
<p>Now that your project is initialized, install the required packages.</p>
<ol>
<li><strong>TypeScript and tsx:</strong> TypeScript types the backend while <code>tsx</code> runs TypeScript files during development.</li>
</ol>
<pre><code class="language-shell">npm install -D typescript tsx @types/node
npx tsc -v
npx tsx --version
</code></pre>
<ol>
<li><strong>Express and session management:</strong> Express handles the HTTP routes, and <code>express-session</code> stores short-lived server session state.</li>
</ol>
<pre><code class="language-shell">npm install express express-session @types/express @types/express-session
</code></pre>
<ol>
<li><strong>SimpleWebAuthn:</strong><code>@simplewebauthn/server</code> generates registration options and verifies responses. <code>@simplewebauthn/browser</code> starts browser-side flows.</li>
</ol>
<pre><code class="language-shell">npm install @simplewebauthn/server @simplewebauthn/browser
</code></pre>
<p>Open your <code>package.json</code> and update the "scripts" block to include these commands:</p>
<pre><code class="language-json">{
    "scripts": {
        "dev": "tsx watch src/app.ts",
        "build": "tsc",
        "start": "node dist/app.js"
    }
}
</code></pre>
<h2 id="heading-define-the-data-model">Define the Data Model</h2>
<p>Before we write the routes, we need to decide how this app will store passkeys.</p>
<p>With password-based login, you usually store a password hash. With WebAuthn, you store something different. After a user registers a passkey, your server needs to keep the credential data required to verify future login attempts. That includes things like the credential ID, public key, counter, and some metadata about the authenticator.</p>
<p>This is why the data model matters from the beginning. Registration and authentication both depend on this stored data, so it's worth making the structure clear before we move deeper into the flow.</p>
<p>A good way to think about it is this. A user can have one or more passkeys, and each passkey should be stored as its own record linked to that user.</p>
<p>Create a new file named <code>src/app.ts</code>. We'll build the backend in this file, and we'll start by defining the data model at the top.</p>
<pre><code class="language-typescript">// src/app.ts
type Passkey = {
    id: string;
    publicKey: Uint8Array;
    counter: number;
    deviceType: "singleDevice" | "multiDevice";
    backedUp: boolean;
    transports?: string[];
};

type User = {
    id: string;
    email: string;
    webAuthnUserID: Uint8Array;
    passkeys: Passkey[];
};

const users = new Map&lt;string, User&gt;();

function findUserByEmail(email: string) {
    return [...users.values()].find((user) =&gt; user.email === email);
}
</code></pre>
<p>What matters here:</p>
<ul>
<li><p><code>id</code> identifies the credential later.</p>
</li>
<li><p><code>publicKey</code> verifies future signatures.</p>
</li>
<li><p><code>counter</code> helps detect cloned or misbehaving authenticators.</p>
</li>
<li><p><code>deviceType</code> and <code>backedUp</code> give you useful recovery signals.</p>
</li>
<li><p><code>webAuthnUserID</code> should be a stable binary value, stored once per user.</p>
</li>
</ul>
<p>Tip: If your database returns <code>Buffer</code> or another binary wrapper for <code>publicKey</code>, convert it back to <code>Uint8Array</code> before verification.</p>
<h2 id="heading-build-the-server-foundation">Build the Server Foundation</h2>
<p>Next, append the core Express app and relying party settings to <code>src/app.ts</code>:</p>
<pre><code class="language-typescript">// src/app.ts
import express from "express";
import session from "express-session";
import { randomBytes, randomUUID } from "node:crypto";
import {
    generateAuthenticationOptions,
    generateRegistrationOptions,
    verifyAuthenticationResponse,
    verifyRegistrationResponse,
    type WebAuthnCredential,
} from "@simplewebauthn/server";

const rpName = "Node Auth Lab";
const rpID = "localhost";
const origin = "http://localhost:3000";

declare module "express-session" {
    interface SessionData {
        currentChallenge?: string;
        pendingUserId?: string;
        userId?: string;
        stepUpUntil?: number;
    }
}

const app = express();

app.use(express.json());

app.use(
    session({
        secret: "replace-this-in-production",
        resave: false,
        saveUninitialized: false,
        cookie: {
            httpOnly: true,
            sameSite: "lax",
            secure: false,
            maxAge: 10 * 60 * 1000,
        },
    }),
);
</code></pre>
<p>This gives you the shared state you need for:</p>
<ul>
<li><p>registration challenge tracking</p>
</li>
<li><p>authentication challenge tracking</p>
</li>
<li><p>logged-in session state</p>
</li>
<li><p>short step-up windows for risky actions</p>
</li>
</ul>
<h2 id="heading-registration-ceremony">Registration Ceremony</h2>
<p>Registration is the part where a user's device creates a new passkey and connects it to their account.</p>
<p>You will often see the word ceremony in WebAuthn documentation. In simple terms, it refers to the full exchange between your server, the browser, and the authenticator during registration or login. So when we say registration ceremony, we simply mean the full process of creating and verifying a new passkey.</p>
<p>There are three parts involved here:</p>
<ul>
<li><p>Your backend prepares the registration options and challenge.</p>
</li>
<li><p>The browser starts the WebAuthn request.</p>
</li>
<li><p>Then the authenticator, such as a phone, laptop, or security key, creates the key pair and returns the result.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d1c87944-a244-4b82-b7ac-d10f109b1024.png" alt="WebAuthn registration flow" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>In the above diagram, the registration ceremony links a new passkey to your account. The browser asks the server for registration options. The server generates a challenge and returns a JSON configuration. Next, the browser starts the WebAuthn ceremony. Your authenticator creates a new key pair after biometric or security key verification. The private key stays inside the device. Then the browser sends the attestation response to the server.</p>
<p>Verification happens next. The server checks challenge, origin, and relying party ID. After validation, the server stores credential ID, public key, counter value, device type, and backup state. The account now holds a passkey.</p>
<p>Now that the overall flow is clear, let's build it step by step. We'll start by returning registration options from the backend.</p>
<h3 id="heading-1-return-registration-options-from-the-backend">1. Return Registration Options from the Backend</h3>
<p>This endpoint creates a new user if needed, generates the registration options, and stores the challenge server-side.</p>
<p>Append the following route to your <code>src/app.ts</code> file:</p>
<pre><code class="language-typescript">// src/app.ts
app.post("/auth/register/options", async (req, res) =&gt; {
    const { email } = req.body;

    if (!email) {
        return res.status(400).json({ error: "Email is required" });
    }

    let user = findUserByEmail(email);

    if (!user) {
        user = {
            id: randomUUID(),
            email,
            webAuthnUserID: randomBytes(32),
            passkeys: [],
        };

        users.set(user.id, user);
    }

    const options = await generateRegistrationOptions({
        rpName,
        rpID,
        userName: user.email,
        userDisplayName: user.email,
        userID: user.webAuthnUserID,
        attestationType: "none",
        excludeCredentials: user.passkeys.map((passkey) =&gt; ({
            id: passkey.id,
            transports: passkey.transports,
        })),
        authenticatorSelection: {
            residentKey: "preferred",
            userVerification: "preferred",
        },
    });

    req.session.currentChallenge = options.challenge;
    req.session.pendingUserId = user.id;

    res.json(options);
});
</code></pre>
<p>A few decisions here matter:</p>
<ul>
<li><p><code>attestationType: 'none'</code> keeps the flow lighter unless you need richer device provenance.</p>
</li>
<li><p><code>excludeCredentials</code> stops duplicate registration of the same authenticator.</p>
</li>
<li><p><code>userVerification: 'preferred'</code> lets the browser lean toward biometrics or local device unlock.</p>
</li>
</ul>
<h3 id="heading-2-start-registration-in-the-browser">2. Start Registration in the Browser</h3>
<p>On the browser side, you ask your backend for options, then pass them into <code>startRegistration()</code>.</p>
<p>Now, create a new file at <code>src/browser.ts</code> to handle the client-side WebAuthn interactions. Add the following function:</p>
<pre><code class="language-typescript">// src/browser.ts
import { startRegistration } from "@simplewebauthn/browser";

export async function registerPasskey(email: string) {
    const optionsResp = await fetch("/auth/register/options", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ email }),
    });

    const optionsJSON = await optionsResp.json();

    const registrationResponse = await startRegistration({ optionsJSON });

    const verifyResp = await fetch("/auth/register/verify", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify(registrationResponse),
    });

    return verifyResp.json();
}
</code></pre>
<p><strong>Note:</strong> Browsers can't run TypeScript or bare npm imports directly. In a real application, you would import <code>src/browser.ts</code> into your frontend framework (React, Vue, and so on) or bundle it using a tool like Vite, Webpack, or esbuild before serving it to the client.</p>
<p>Under the hood, the browser now speaks to the authenticator. That might trigger Face ID, Touch ID, Windows Hello, Android biometrics, or a physical security key prompt.</p>
<h3 id="heading-3-verify-the-registration-response-and-save-the-passkey">3. Verify the Registration Response and Save the Passkey</h3>
<p>Once the browser sends the response back, verify it against the challenge and relying party details you stored earlier.</p>
<p>Append this verification route to <code>src/app.ts</code>:</p>
<pre><code class="language-typescript">// src/app.ts
app.post("/auth/register/verify", async (req, res) =&gt; {
    const user = users.get(req.session.pendingUserId ?? "");

    if (!user || !req.session.currentChallenge) {
        return res.status(400).json({ verified: false });
    }

    let verification;

    try {
        verification = await verifyRegistrationResponse({
            response: req.body,
            expectedChallenge: req.session.currentChallenge,
            expectedOrigin: origin,
            expectedRPID: rpID,
        });
    } catch (error) {
        return res.status(400).json({
            verified: false,
            error:
                error instanceof Error ? error.message : "Registration failed",
        });
    }

    if (!verification.verified || !verification.registrationInfo) {
        return res.status(400).json({ verified: false });
    }

    const { credential, credentialDeviceType, credentialBackedUp } =
        verification.registrationInfo;

    user.passkeys.push({
        id: credential.id,
        publicKey: credential.publicKey,
        counter: credential.counter,
        transports: credential.transports,
        deviceType: credentialDeviceType,
        backedUp: credentialBackedUp,
    });

    req.session.currentChallenge = undefined;
    req.session.pendingUserId = undefined;

    res.json({ verified: true });
});
</code></pre>
<p>At this point, the user has no password hash in the hot path. The authenticator now holds the private key. Your server stores only what it needs for later verification.</p>
<h2 id="heading-authentication-ceremony">Authentication Ceremony</h2>
<p>Authentication is the part where the user proves they still have the passkey they registered earlier. Just like registration, this process is also called a ceremony in WebAuthn. Here, the goal is not to create a new credential. The goal is to verify an existing one in a secure way.</p>
<p>There are four parts involved here:</p>
<ul>
<li><p>Your server creates a fresh challenge.</p>
</li>
<li><p>The browser passes that challenge to the authenticator.</p>
</li>
<li><p>The authenticator signs it using the private key stored on the device.</p>
</li>
<li><p>Then the server verifies the response using the public key it stored during registration.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/0296f66d-47c7-4ca5-ab0c-c5a6f9a8eef8.png" alt="WebAuthn authentication flow" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>In the above diagram, login occurs through a challenge-response process. The browser first asks the server for authentication options. The server generates a new challenge and returns the allowed credential list. Your browser then triggers the authenticator. The authenticator signs the challenge using the private key stored on the device. The browser sends the signed assertion to the backend.</p>
<p>Verification follows. The server validates signature, challenge, origin, and credential ID. The stored counter updates. A short session is issued after verification. Login depends on device proof instead of reusable credentials.</p>
<p>With that flow in mind, let's move into the implementation. We'll begin by returning authentication options from the backend.</p>
<h3 id="heading-1-return-authentication-options">1. Return Authentication Options</h3>
<p>Fetch the user, list allowed credentials, and store the new challenge.</p>
<pre><code class="language-typescript">// src/app.ts
app.post("/auth/login/options", async (req, res) =&gt; {
    const { email } = req.body;
    const user = findUserByEmail(email);

    if (!user) {
        return res.status(404).json({ error: "User not found" });
    }

    const options = await generateAuthenticationOptions({
        rpID,
        allowCredentials: user.passkeys.map((passkey) =&gt; ({
            id: passkey.id,
            transports: passkey.transports,
        })),
        userVerification: "preferred",
    });

    req.session.currentChallenge = options.challenge;
    req.session.pendingUserId = user.id;

    res.json(options);
});
</code></pre>
<h3 id="heading-2-start-authentication-in-the-browser">2. Start Authentication in the Browser</h3>
<p>The browser receives the options, then starts the ceremony. Add the login function to your <code>src/browser.ts</code> file:</p>
<pre><code class="language-typescript">// src/browser.ts
import { startAuthentication } from "@simplewebauthn/browser";

export async function loginWithPasskey(email: string) {
    const optionsResp = await fetch("/auth/login/options", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({ email }),
    });

    const optionsJSON = await optionsResp.json();

    const authenticationResponse = await startAuthentication({ optionsJSON });

    const verifyResp = await fetch("/auth/login/verify", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify(authenticationResponse),
    });

    return verifyResp.json();
}
</code></pre>
<h3 id="heading-3-verify-the-assertion-and-update-the-counter">3. Verify the Assertion and Update the Counter</h3>
<p>This is the moment where the backend decides whether the login is real.</p>
<pre><code class="language-typescript">// src/app.ts
app.post("/auth/login/verify", async (req, res) =&gt; {
    const user = users.get(req.session.pendingUserId ?? "");

    if (!user || !req.session.currentChallenge) {
        return res.status(400).json({ verified: false });
    }

    const passkey = user.passkeys.find((item) =&gt; item.id === req.body.id);

    if (!passkey) {
        return res
            .status(400)
            .json({ verified: false, error: "Passkey not found" });
    }

    const credential: WebAuthnCredential = {
        id: passkey.id,
        publicKey: passkey.publicKey,
        counter: passkey.counter,
        transports: passkey.transports,
    };

    let verification;

    try {
        verification = await verifyAuthenticationResponse({
            response: req.body,
            expectedChallenge: req.session.currentChallenge,
            expectedOrigin: origin,
            expectedRPID: rpID,
            credential,
            requireUserVerification: true,
        });
    } catch (error) {
        return res.status(400).json({
            verified: false,
            error:
                error instanceof Error
                    ? error.message
                    : "Authentication failed",
        });
    }

    if (!verification.verified) {
        return res.status(400).json({ verified: false });
    }

    passkey.counter = verification.authenticationInfo.newCounter;

    req.session.userId = user.id;
    req.session.currentChallenge = undefined;
    req.session.pendingUserId = undefined;

    res.json({ verified: true });
});
</code></pre>
<p>Two details here matter more than they first appear:</p>
<ul>
<li><p><code>requireUserVerification: true</code> forces a stronger ceremony for the verification step.</p>
</li>
<li><p><code>newCounter</code> should overwrite your stored counter after each successful login.</p>
</li>
</ul>
<p>That counter update is one of the few signals you have for spotting cloned or broken authenticators.</p>
<h2 id="heading-what-replaces-the-long-lived-jwt">What Replaces the Long-lived JWT</h2>
<p>Don't run this full WebAuthn flow, then issue a week-long bearer token and call the job done. That throws away the best part of the design.</p>
<p>A better model is:</p>
<ul>
<li><p>WebAuthn proves identity</p>
</li>
<li><p>the server creates a short session</p>
</li>
<li><p>the browser receives only an HTTP-only session cookie</p>
</li>
<li><p>risky actions ask for a fresh WebAuthn assertion again</p>
</li>
</ul>
<p>A tiny route guard shows the idea:</p>
<pre><code class="language-typescript">// src/app.ts
function requireSession(
    req: express.Request,
    res: express.Response,
    next: express.NextFunction,
) {
    if (!req.session.userId) {
        return res.status(401).json({ error: "Unauthorized" });
    }

    next();
}

app.get("/me", requireSession, (req, res) =&gt; {
    const user = users.get(req.session.userId ?? "");

    if (!user) {
        return res.status(404).json({ error: "User not found" });
    }

    res.json({
        id: user.id,
        email: user.email,
        passkeys: user.passkeys.length,
    });
});
</code></pre>
<p>This keeps the post-login browser state smaller and less reusable. The browser carries only a session cookie. The server owns the session state and its lifetime.</p>
<p>Tip: Short sessions plus fresh WebAuthn for risky actions is a stronger shape than one long bearer token with broad scope.</p>
<h2 id="heading-multi-device-and-recovery-logic">Multi-Device and Recovery Logic</h2>
<p>Strong auth fails fast if the first lost phone locks the user out forever. You need a real backup story from day one.</p>
<p>The clean pattern looks like this:</p>
<ul>
<li><p>register one platform passkey on the user's main device</p>
</li>
<li><p>register one extra credential, such as a security key or another trusted device</p>
</li>
<li><p>verify a contact channel during account setup</p>
</li>
<li><p>expose passkey management in account settings</p>
</li>
<li><p>store device metadata so users see what is registered</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/7c8d8867-38d1-403f-a409-54e0988221d8.png" alt="Multi-device recovery and step-up auth" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>In the above diagram, the left side shows the recovery strategy. You start with a primary passkey on your main device. Then you add another trusted authenticator such as a second device or hardware security key. A recovery channel should exist. Device inventory helps track active credentials, and lost devices must be revoked quickly.</p>
<p>The right side focuses on step up authentication. Sensitive actions require fresh verification. Examples include payouts, email change, API key generation, or destructive operations. When such an action begins, the server issues a new challenge. Your authenticator signs again. Access lasts for a short step up window before new verification becomes required.</p>
<p>A simple product rule helps here. Don't hide the second passkey flow inside a deep settings page. Put "Add another passkey" right after the first successful registration.</p>
<p>Passkeys also sync across major platform ecosystems. That helps the user experience, but your backend should still treat each registered credential as a first-class record with its own ID, public key, counter, device type, and backup state.</p>
<p>For account recovery, keep the bar high. Recovery should not become the weakest path in the whole system. Emailed magic links, recovery codes, and support-driven recovery all need rate limits, audit trails, and strict checks.</p>
<h2 id="heading-step-up-authentication-for-sensitive-actions">Step-up Authentication for Sensitive Actions</h2>
<p>Logging in once should not grant silent permission for every dangerous route.</p>
<p>Step-up auth means this:</p>
<ul>
<li><p>the user is already signed in</p>
</li>
<li><p>they try a sensitive action</p>
</li>
<li><p>the server demands a fresh WebAuthn ceremony</p>
</li>
<li><p>the app grants a short window for that one class of actions</p>
</li>
</ul>
<p>You can use step-up auth for:</p>
<ul>
<li><p>payout approval</p>
</li>
<li><p>credential management</p>
</li>
<li><p>email or phone change</p>
</li>
<li><p>API key creation</p>
</li>
<li><p>organization deletion</p>
</li>
<li><p>role elevation</p>
</li>
<li><p>access to billing controls</p>
</li>
</ul>
<p>Start by issuing new authentication options with strict user verification.</p>
<pre><code class="language-typescript">// src/app.ts
app.post("/auth/step-up/options", requireSession, async (req, res) =&gt; {
    const user = users.get(req.session.userId ?? "");

    if (!user) {
        return res.status(404).json({ error: "User not found" });
    }

    const options = await generateAuthenticationOptions({
        rpID,
        allowCredentials: user.passkeys.map((passkey) =&gt; ({
            id: passkey.id,
            transports: passkey.transports,
        })),
        userVerification: "required",
    });

    req.session.currentChallenge = options.challenge;

    res.json(options);
});
</code></pre>
<p>Then verify the response and issue a short step-up window.</p>
<pre><code class="language-typescript">// src/app.ts
app.post("/auth/step-up/verify", requireSession, async (req, res) =&gt; {
    const user = users.get(req.session.userId ?? "");
    const passkey = user?.passkeys.find((item) =&gt; item.id === req.body.id);

    if (!user || !passkey || !req.session.currentChallenge) {
        return res.status(400).json({ verified: false });
    }

    let verification;
    try {
        verification = await verifyAuthenticationResponse({
            response: req.body,
            expectedChallenge: req.session.currentChallenge,
            expectedOrigin: origin,
            expectedRPID: rpID,
            credential: {
                id: passkey.id,
                publicKey: passkey.publicKey,
                counter: passkey.counter,
                transports: passkey.transports,
            },
            requireUserVerification: true,
        });
    } catch (error) {
        return res.status(400).json({
            verified: false,
            error: error instanceof Error ? error.message : "Step-up failed",
        });
    }

    if (!verification.verified) {
        return res.status(400).json({ verified: false });
    }

    passkey.counter = verification.authenticationInfo.newCounter;
    req.session.stepUpUntil = Date.now() + 5 * 60 * 1000;
    req.session.currentChallenge = undefined;

    res.json({ verified: true });
});
</code></pre>
<p>A tiny guard handles the rest.</p>
<pre><code class="language-typescript">// src/app.ts
function requireRecentStepUp(
    req: express.Request,
    res: express.Response,
    next: express.NextFunction,
) {
    if (!req.session.stepUpUntil || req.session.stepUpUntil &lt; Date.now()) {
        return res.status(403).json({ error: "Fresh verification required" });
    }

    next();
}

app.post("/billing/payout", requireSession, requireRecentStepUp, (req, res) =&gt; {
    res.json({ ok: true });
});
</code></pre>
<p>This is where WebAuthn stops being a login feature and starts becoming part of your authorization model.</p>
<p>Now that all your routes and guards are built, append the server start command to the very bottom of <code>src/app.ts</code> to bring the backend to life:</p>
<pre><code class="language-typescript">// src/app.ts
const PORT = 3000;
app.listen(PORT, () =&gt; {
    console.log(`Server listening on http://localhost:${PORT}`);
});
</code></pre>
<h2 id="heading-recap">Recap</h2>
<p>You started with a common weak pattern: a reusable token trusted for too long. Then you replaced the core proof model:</p>
<ul>
<li><p>the device keeps the private key</p>
</li>
<li><p>the server stores the public key and counter</p>
</li>
<li><p>each ceremony signs a fresh challenge</p>
</li>
<li><p>the app uses short server sessions after verification</p>
</li>
<li><p>risky routes trigger fresh step-up authentication</p>
</li>
</ul>
<p>That's the real shift.</p>
<p>WebAuthn is not a cosmetic login upgrade. It changes where trust lives. Once you move from reusable bearer proof to device-bound cryptographic proof, your Node.js auth stack starts to behave like a modern security system instead of a thin session wrapper.</p>
<h2 id="heading-try-it-yourself">Try it Yourself</h2>
<p>The full source code is available on GitHub. <a href="https://github.com/logicbaselabs/web-authn">Clone the repository</a> here and follow the setup guide in the <code>README</code> to test the biometric login flow locally.</p>
<h2 id="heading-final-words">Final Words</h2>
<p>If you found the information here valuable, feel free to share it with others who might benefit from it.</p>
<p>I’d really appreciate your thoughts – mention me on X <a href="https://x.com/sumit_analyzen">@sumit_analyzen</a> or on Facebook <a href="https://facebook.com/sumit.analyzen">@sumit.analyzen</a>, <a href="https://youtube.com/@logicBaseLabs">watch my coding tutorials</a>, or simply <a href="https://www.linkedin.com/in/sumitanalyzen/">connect with me on LinkedIn</a>.</p>
<p>You can also checkout my official website <a href="https://www.sumitsaha.me">www.sumitsaha.me</a> for more details about me.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Secure User Authentication Methods – 2FA, Biometric, and Passwordless Login Explained ]]>
                </title>
                <description>
                    <![CDATA[ In today's digital world, user authentication is essential in ensuring secure access to online accounts and resources.  With the rise of cyber-threats, companies need to ensure that their users are authenticated before accessing any sensitive informa... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/user-authentication-methods-explained/</link>
                <guid isPermaLink="false">66b8dc06d3be22cd680b3b84</guid>
                
                    <category>
                        <![CDATA[ Application Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ biometric authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Two-factor authentication ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hillary Nyakundi ]]>
                </dc:creator>
                <pubDate>Tue, 17 Jan 2023 18:05:56 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/OOP.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today's digital world, user authentication is essential in ensuring secure access to online accounts and resources. </p>
<p>With the rise of cyber-threats, companies need to ensure that their users are authenticated before accessing any sensitive information. This helps protect the online safety of both parties.</p>
<p>In the past, the most common authentication method that we're all likely familiar with was using a username and password to sign in to apps and services. </p>
<p>And if you're a programmer, you've likely developed projects where you have implemented this method as a form of authentication.</p>
<p>But hey! Guess what? Things have changed over the past couple of years. With advancements in technology, this means security has to be taken more seriously and we need more strict authentication approaches.</p>
<p>And that's what we'll learn about in this tutorial.</p>
<h2 id="heading-different-authentication-methods">Different Authentication Methods</h2>
<p>There are many different methods of authenticating users, and each has its own advantages and disadvantages. The most common methods of user authentication are:</p>
<ul>
<li>username and password,</li>
<li>two-factor authentication,</li>
<li>biometrics</li>
</ul>
<p>just to list a few.</p>
<p>But as time passes, we continue to evolve and new methods are introduced that provide a safer way to store user data. Some examples of these methods include:</p>
<ul>
<li>Passwordless login</li>
<li>Multi-factor authentication, and</li>
<li>Token-based authentication</li>
</ul>
<p>In this article, we will explore the most common methods of user authentication. By understanding the different options available, you can choose the best method for your needs. </p>
<p>But first, let's understand what we mean by authentication.</p>
<h2 id="heading-what-is-user-authentication">What is User Authentication?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/e79vtruyjlui8cz8j8r6-1.png" alt="e79vtruyjlui8cz8j8r6-1" width="600" height="400" loading="lazy"></p>
<p>To better understand what authentication is all about, we can relate it to a real world example. </p>
<p>Let's take a scenario where you are out at the store shopping and you need to make a payment with your credit card. You go through the steps of swiping your card to authenticate the payment, but what really happens before the transaction is complete?</p>
<p>After swiping your card, the machine will read the card info and send it to the issuer for verification. The issuer will then check for a couple of things in order to verify the transaction. </p>
<p>In this case, the issuer will check the card info against what is on record, like expiry date, card number, and account balance. If everything matches what is on record and there are sufficient funds, an authorization message is sent and the transaction is allowed. </p>
<p>In a case where the info doesn't match and/or there are not enough funds, a decline message is instead sent and the transaction is declined.</p>
<p>Now with this understanding, authentication is the process of verifying the identity of a user. User authentication verifies the identity of a user before granting access to sensitive information or systems. </p>
<h3 id="heading-what-are-some-common-authentication-methods">What are some common authentication methods?</h3>
<p>There are many ways to authenticate a user, and each platform has different methods that they use. But as I mentioned above, some common methods include username and password, fingerprint, facial recognition, and iris scan.</p>
<h4 id="heading-usernamepassword-authentication">Username/password authentication</h4>
<p>Username and password are the most common form of authentication. This is where a user enters their username and password into a login form, and if the credentials match what is stored in the database, the user is granted access. </p>
<p>But keep in mind that this method can be insecure if passwords are not properly encrypted or if users reuse the same password for multiple accounts.</p>
<h4 id="heading-biometric-authentication-methods">Biometric authentication methods</h4>
<p>Fingerprint authentication uses an individual's unique fingerprint to verify their identity. This can be done using a fingerprint scanner or by using a smartphone's built-in sensor. </p>
<p>This method is most commonly used in smart phones and recently there has been an increase in the use of this method in laptops, too.</p>
<p>Facial recognition works in a similar way, using an image of the user's face to verify their identity. Iris scanning is another biometric authentication method that uses an image of the user's iris to identify them.</p>
<h2 id="heading-more-secure-authentication-methods">More Secure Authentication Methods</h2>
<p>Even though these have historically been the most common methods, recently we have seen a rise in other methods which are said to be more secure. </p>
<p>In fact, many organizations are turning to these techniques in addition to the user providing a username and password. This is considered an extra layer of security.</p>
<p>These newer techniques include: </p>
<h3 id="heading-1-two-factor-authentication">1. Two-Factor Authentication</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/01/hktn8ib7inl9whmybqp2.jpg" alt="Two-Factor Authentication" width="600" height="400" loading="lazy"></p>
<p>Two-factor authentication, also known as 2FA, is an additional layer of security that can be used to protect your account. </p>
<p>2FA is a way of verifying a user from two different approaches, thats is: using something the user already knows (like their username and password), and using something the user has, like a phone.</p>
<p>When 2FA is enabled, apart from entering the username and password correctly, you will be prompted for the second piece of information (usually a code generated by an app on your phone or a code sent via SMS). This makes it much more difficult for someone to gain access to your account, even if they have your password.</p>
<p>2FA is not foolproof, but it is more secure compared to using only username and password. This makes it a valuable tool to help keep your account safe. If you are concerned about the security of your account, enabling 2FA will be of great help.</p>
<h3 id="heading-2-passwordless-login">2. Passwordless Login</h3>
<p>Passwordless login, just as the name suggests, is a method of logging into an account without needing a username or a password. There are many reasons why you might want to stop using a password and opt for a passwordless login experience.</p>
<p>For one, it's more convenient for users. They don't have to remember yet another username and password combination. And two, it's more secure. There are no weak passwords to be guessed or brute-forced by attackers.</p>
<p>So how do you set up a passwordless login? There are a few different methods you can use, each with its own set of pros and cons.</p>
<h4 id="heading-different-methods-of-passwordless-login">Different methods of passwordless login</h4>
<p>One popular method is to use an email link. When the user wants to log in, they provide their email address. They then receive an email with a link that expires after a certain amount of time. When they click the link, they're logged in without having to enter a password.</p>
<p>Another option is to use a one-time code generated by an app on the user's phone. The code is valid for only a short period of time, so even if someone were to intercept it, they wouldn't be able to use it.</p>
<p>Which method is best for you depends on your security needs and preferences. But whatever you choose, ditching the password is sure to make life easier for your users - and make your site more secure in the process.</p>
<p>For a practical guide on how to use the passwordless method, <a target="_blank" href="https://youtu.be/0OYA1c3bjgM">Auth0</a> has a step-by-step video guide on how to implement this.</p>
<h3 id="heading-3-multi-factor-authentication">3. Multi-factor Authentication</h3>
<p>Also known as MFA, multi-factor authentication is an authentication method that requires a user to verify their identity by providing more than one piece of information that identifies them. This can range from something the user knows, has, or is.</p>
<p>This means that in addition to having a username and password, you will be required to provide extra proof depending on the system you are trying to access. This extra proof can range from a fingerprint to a secret security key or even a code generated randomly.</p>
<p>A good example of this authentication is when you set up an online banking system. Despite having entered a correct username and password, your might be required to either provide your fingerprint or even a code in order for some transaction to happen. </p>
<p>This means that even if someone was to obtain your username and password, they would still need your fingerprint or a code that has been sent to your phone in order to accomplish a specific task.</p>
<h3 id="heading-4-token-based-authentication">4. Token-Based Authentication</h3>
<p>Token-based authentication is a method of authenticating users that involves providing them with a unique token. This token can be used to identify the user and provide access to certain resources. The toke usually contains a string of characters generated by the system that's sent to the user's device or email.</p>
<p>There are many benefits to using token-based authentication, including improved security and scalability. </p>
<p>Tokens, while costly and inconvenient at times, provide a greater level of security than passwords or biometrics since they are only issued when requested. In addition to this, they can also be set to expire after a certain period of time, making it more secure compared to the traditional approaches.</p>
<p>This method is relatively new and it has become more popular in recent years as web applications have become more complex and distributed across multiple servers. It offers several other advantages over other methods.</p>
<p>With token-based authentication, the token is stored on the client side, making it much more secure. In addition, since there's no need to store tokens on the server, scaling becomes much easier.</p>
<p>Overall, token-based authentication offers better security and performance than other methods. If you're looking to implement an auth system for your web application, consider using tokens.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>User authentication is a critical part of any application, whether it's mobile or web. </p>
<p>It is important to choose an authentication method that is both secure and easy to use. </p>
<p>There are many different factors to consider when choosing an authentication method, but the most important thing is to choose one that will protect your users' data. Hopefully this article has given you some insights into how to do that.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to strengthen your personal cybersecurity posture for when you're just this guy, you know? ]]>
                </title>
                <description>
                    <![CDATA[ “Zaphod’s just this guy, you know?” – Halfrunt, Hitchhiker’s Guide to the Galaxy by Douglas Adams. The book, not the movie. Definitely not the movie. Some people (??‍) are really into cybersecurity, end-to-end encryption, and totally geeked out when... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/personal-cybersecurity-posture/</link>
                <guid isPermaLink="false">66bd8f6dabf0ccf74f1ce993</guid>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ biometric authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ information security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ internet ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vpn ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Victoria Drake ]]>
                </dc:creator>
                <pubDate>Tue, 08 Oct 2019 13:05:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/10/cover.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <blockquote>
<p><em>“Zaphod’s just this guy, you know?”</em></p>
<p>– Halfrunt, Hitchhiker’s Guide to the Galaxy by Douglas Adams. The book, not the movie. Definitely not the movie.</p>
</blockquote>
<p>Some people (??‍) are really into cybersecurity, end-to-end encryption, and totally geeked out when they first learned how the <a target="_blank" href="https://en.wikipedia.org/wiki/Enigma_machine">Enigma</a> worked. These people are likely to have an innate interest in building a less-than-laughable personal cybersecurity posture.</p>
<p>Most people, unfortunately, consider cybersecurity optional. Most people say things like:</p>
<p><em>“There’s no one targeting lil ol’ me.”</em><br> <em>“I have nothing to hide, anyway.”</em><br> <em>“I’m too busy to learn all this stuff. Why can’t someone just give me a simple summary of best practices that I can skim in approximately seven minutes?”</em></p>
<p>To those people, I say, hello, hypothetical incorporeal reader! Here is a simple summary of best practices that you can skim in approximately seven minutes.</p>
<h4 id="heading-wait-why-do-i-care">Wait why do I care</h4>
<p>You may have a hard time understanding why cybersecurity matters when you’re just an average person. Sure, you don’t want your devices hacked or your personal data stolen, but it’s not like anyone is coming after <em>you</em>, specifically, right?</p>
<p>Hey Alex, I’ll take “right,” for $400. It’s unlikely anyone is attempting to steal your <em>particular</em> stuff, although I must admit that Persian rug of yours would really tie the room together. Instead, it can help to understand cybersecurity if you think of it in terms of low-hanging fruit.</p>
<p>You’ve got some fruit, I’ve got some fruit. Joe from down the block has a 1.21 gigawatt flux-capacitor-powered fruit-snatching robot. Joe doesn’t know either of us exist, but his robot goes (very quickly) from door to door, all the way around the block, looking for fruit. If my front door is locked and yours is standing open, whose fruit is Joe’s robot going to snatch?</p>
<p>If that sounds like boring, old, <em>regular</em> security, you’re correct! Cybersecurity isn’t about finding some magic spell that makes your fruit maximally secure. It’s about making your fruit more secure than the fruit next to you. You do this by employing some thoughtful habits, in much the same way as you learned to lock your front door to guard against fruit-snatching robots.</p>
<p>Security breaches and incidents happen every day. Most of them occur because an automated scanner cast a wide net and found a person or company with lax security that a hacker could then exploit. Don’t be that guy.</p>
<h4 id="heading-wait-whats-a-security-posture-anyway">Wait what's a security posture anyway</h4>
<p>Here is how the National Institute of Standards and Technology defines security posture:</p>
<blockquote>
<p><em>The security status of an enterprise’s networks, information, and systems based on information assurance resources (e.g., people, hardware, software, policies) and capabilities in place to manage the defense of the enterprise and to react as the situation changes.</em> (<a target="_blank" href="https://csrc.nist.gov/publications/detail/sp/800-30/rev-1/final#pubs-topics">NIST Special Publication 800–30, B-11</a>)</p>
</blockquote>
<p>The important bit above is, <em>“capabilities in place to manage the defense of the enterprise.”</em> In the context of personal security, you are the enterprise. Congratulations. May you boldly go where no man has gone before.</p>
<p>Before you explore strange new worlds (it <em>is</em> the Internet, after all), there are steps you can take to manage your defenses. The word “capabilities” is apt, as having certain things in place will pretty much give you cybersecurity superpowers. Here are the three steps I consider most important and beneficial:</p>
<ol>
<li>Use multifactor authentication</li>
<li>Use a VPN</li>
<li>Develop healthy skepticism</li>
</ol>
<p>With these three keys in hand, your cybersecurity posture goes from being robot lunch to War Games — where the winning move for an attacker is not to play.</p>
<h4 id="heading-1-use-multifactor-authentication">1. Use multifactor authentication</h4>
<p>Passwords are dead. Computationally, they are a solved problem, and cracking passwords is just <a target="_blank" href="https://howsecureismypassword.net/">a matter of time</a>. Unfortunately, many people still help to speed up the process by using the same <a target="_blank" href="https://haveibeenpwned.com/Passwords">compromised passwords</a> for multiple accounts, putting themselves at risk for inconceivable benefit. <a target="_blank" href="https://pages.nist.gov/800-63-3/sp800-63b.html#a2-length">Pass phrases</a> are longer and more complicated, and would take a lot more time to crack. I highly recommend them; even so, <a target="_blank" href="https://techcommunity.microsoft.com/t5/Azure-Active-Directory-Identity/Your-Pa-word-doesn-t-matter/ba-p/731984">your password ultimately doesn’t matter</a>.</p>
<p>The answer, at least for now, is <a target="_blank" href="https://en.wikipedia.org/wiki/Multi-factor_authentication">multifactor authentication</a> (MFA). MFA is made up of three kinds of authentication factors:</p>
<ol>
<li>Something you know, like a pass phrase;</li>
<li>Something you have, like a chip pin card or phone; and</li>
<li>Something that you are, like your face or fingerprint.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/mfa.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Two or more of these factors are infinitely better than a password alone, especially if <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_the_most_common_passwords">your password is on this list</a>.</p>
<p>Multiple authentication factors are now widely supported by account providers and social media sites. If you have the choice, avoid using text messages as a way of receiving authentication codes. SMS authentication leaves you vulnerable to the <a target="_blank" href="https://en.wikipedia.org/wiki/SIM_swap_scam">SIM swap attack</a> — please direct further questions to <a target="_blank" href="https://www.nytimes.com/2019/09/05/technology/sim-swap-jack-dorsey-hack.html">Jack Dorsey</a>. Instead, use an authenticator app like <a target="_blank" href="https://google-authenticator.com/">Google Authenticator</a> to generate codes on your device. This ensures that you alone, using that particular device, will have the correct authentication code. No power in the ‘verse can stop you.</p>
<p>The Google Authenticator app works with the specific device you set it up on, so when you get a new device you will need to <a target="_blank" href="https://support.google.com/accounts/troubleshooter/4430955?hl=en#ts=4430956">move Google Authenticator to your new phone</a>. Hardware authentication keys such as the <a target="_blank" href="https://www.yubico.com/">YubiKey</a> may present less hassle when switching devices, but aren’t yet as widely supported as authentication apps.</p>
<h4 id="heading-2-use-a-vpn">2. Use a VPN</h4>
<p>The difference between using a VPN and not using one is like how The Dark Knight Rises was really good and Batman v Superman was really, really bad. Same franchise, totally different standards.</p>
<p>Let’s say you send a lot of mail, but never bother to put your letters in envelopes or even fold them in half. Anyone who bothers to look will know that you’re not really the Dread Pirate Roberts after all. When you use a Virtual Private Network, especially if you often connect to public WiFi, it’s like putting your letters into cryptographically-sealed envelopes and sending them via a special invisible courier service. No one but the intended recipient can read your letters, and no one but you and the courier know to whom the letters are sent.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/vpnmail.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>VPNs prevent others from reading your communications, like opportunistic attackers who scan open WiFi, and even your own Internet Service Provider (ISP) who may sell your usage data for advertising dollars.</p>
<p>Choosing a trustworthy VPN provider requires some research, and is in itself material enough for a separate article. As a starting point, look for providers with firm policies against logging, and expect to pay between $5-$10 USD monthly for the service. Avoid free VPN apps and services with ambiguous privacy policies; they’ll typically cost you much more than you’ll know.</p>
<h4 id="heading-3-develop-healthy-skepticism">3. Develop healthy skepticism</h4>
<p>Ultimately, the weakest link in your cybersecurity defense is you. All the MFA and VPNs on the Internet won’t protect you if a scam or malware bot can trick you into opening the front gates. Yes, I know it’s a very nice looking wooden horse. Also free. Did you order it? No? Then it can stay outside.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/horse.png" alt="Image" width="600" height="400" loading="lazy">
<em>Always look a Trojan gift horse in the mouth.</em></p>
<p>Develop the habit of second-guessing things delivered to your virtual doorstep. Email, phone, and messaging scams range in sophistication, from rickety robot-assembled shotgun blasts to elaborate social engineering attacks that <a target="_blank" href="https://www.youtube.com/watch?v=8bAuA1isCz0">use cognitive biases very effectively</a>. Don’t assume you’re too clever for them; humans are very predictable creatures. After all, nobody expects the Spanish Inquisition.</p>
<p>Instead, ask questions. Double check communications that ask you to click on links or visit a website, even if they come from someone you know or a company you use. If you’re not certain, based on a previous in-person interaction, that your friend or bank or mother sent this email, pick up the phone and call them. Even if you think you are certain, pick up the phone and check. You don’t call your mother enough, anyway.</p>
<p>Oh, and if the person on the phone is from your local tax office or the IRS or the CRA and they’re about to freeze your accounts because a case of mistaken identity has resulted in you being criminally charged for not repaying a loan on a 600-foot yacht in Malibu, just hang up. You know better than that. Tax agencies don’t have phones.</p>
<h4 id="heading-your-personal-cybersecurity-starter-pack">Your personal cybersecurity starter pack</h4>
<p>You now have three keys to open three gates to a robust personal cybersecurity posture. If those keys have also unlocked your curiosity, there’s plenty more rabbit hole to go down. I highly recommend the <a target="_blank" href="https://securityinfive.com/">Security in Five podcast</a> for Binary Blogger’s great advice, which inspired much of this post. <a target="_blank" href="https://ssd.eff.org/">Surveillance Self Defense</a> offers the Electronic Frontier Foundation’s tips on securing online communication. Troy Hunt also has a YouTube series entitled <a target="_blank" href="https://www.troyhunt.com/get-to-grips-with-internet-security-basics-courtesy-of-varonis/">Internet Security Basics</a> that goes into more depth on how to protect yourself online.</p>
<p>For now, I hope you use your newfound cybersecurity powers for good. Mind what you have learned. Save you it can.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to implement secure Biometric Authentication on mobile devices ]]>
                </title>
                <description>
                    <![CDATA[ By Kathy Dinh A quick search for React Native biometric authentication would give you several tutorials. That was the first thing I did when there was a need for such a feature in one of my projects. Depending on the level of risk acceptable for your... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-implement-secure-biometric-authentication-on-mobile-devices-4dc518558c5c/</link>
                <guid isPermaLink="false">66c352c0c2631756f9f063d5</guid>
                
                    <category>
                        <![CDATA[ biometric authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React Native ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 17 Apr 2019 16:41:09 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*XeuEHmQJLOKLRhit-mZZRw.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Kathy Dinh</p>
<p>A quick search for React Native biometric authentication would give you several tutorials. That was the first thing I did when there was a need for such a feature in one of my projects. Depending on the level of risk acceptable for your app, one of those solutions might be suitable. For a high risk app like ours, it would not pass security testing.</p>
<p>If you want to add a secure biometric authentication to aa <strong><em>React Native iOS app</em></strong>, you are in the right place.</p>
<h4 id="heading-what-is-wrong-with-react-native-touchid"><strong>What is wrong with react-native-touchid?</strong></h4>
<p>Most implementations use the <em>react-native-touchid</em> package. At <a target="_blank" href="https://github.com/naoufal/react-native-touch-id/blob/2e7c4bcd0aedad01e45708fbb831fcc70fc48264/TouchID.m#L65">line 65</a> in the <a target="_blank" href="https://github.com/naoufal/react-native-touch-id/blob/master/TouchID.m">TouchID.m</a> file, the <em>authenticate</em> method calls the following LAContext method when attempting TouchID/FaceID authentication:</p>
<pre><code>evaluatePolicy:localizedReason:reply:^(BOOL success, NSError *error)
</code></pre><p>The method relies on a Local Authentication check if the provided fingerprint matches the one enrolled on the device. When the check passes, a success boolean is returned and the user has authenticated successfully with TouchID/FaceID.</p>
<p><em>There have been reports of the possibility to bypass Local Authentication by sending a success signal to Apple’s APIs on jailbroken or non-jailbroken iOS devices</em>. Thus, biometric authentication via Local Authentication is vulnerable to spoofing by an attacker who could interfere with the check at runtime.</p>
<h4 id="heading-what-is-the-secure-way-to-implement-biometric-authentication"><strong>What is the secure way to implement biometric authentication?</strong></h4>
<p>To implement biometric authentication in an iOS app, there are two ways — either through Apple’s Local Authentication APIs or through <em>access control of Keychain Services</em> natively provided by the underlying system.</p>
<p>Authenticating with Local Authentication is simpler but generally not recommended for critical applications. As described in the previous section, Local Authentication is a high level API whose behaviour can be overridden, i.e. an attacker could fake a successful authentication by changing the API’s response.</p>
<p>It is acknowledgedly <em>best practice to use Keychain Services</em> for implementing biometric authentication in high risk apps. Keychain Services enforces access control on its stored content using functionality provided by iOS and <a target="_blank" href="https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_secure_enclave">Secure Enclave</a>. The process executes at the hardware and operating system layer and thus minimises exposure to the less trustworthy application layer. Security risks do exist when user is on a jailbroken or malware-infected device, but these threats can be mitigated by mobile device management (MDM) technology.</p>
<h4 id="heading-how-do-we-implement-biometric-authentication-with-keychain-services"><strong>How do we implement biometric authentication with Keychain Services?</strong></h4>
<p>In order to access Keychain Services in our React Native app, we are going to use the package <a target="_blank" href="https://github.com/oblador/react-native-keychain">react-native-keychain</a>. The example code is in TypeScript, which should easily be converted to JavaScript.</p>
<p>First, <em>install</em> react-native-keychain and its type declaration as your project dependency:</p>
<pre><code>npm i -S react-native-keychain
</code></pre><pre><code>npm i -S @types/react-native-keychain
</code></pre><p>Next, we have to <em>link the library</em> as it depends on the native component. There are two ways of linking libraries in a React Native app: automatic linking and manual linking. I encountered many errors with CocoaPods while performing automatic linking. Manual linking works but involves many steps.</p>
<p>I discovered that the library is linked properly without errors if you run <em>react-native link</em> after temporarily removing <em>Podfile</em> under the iOS folder. To save you the hassle, let’s follow such a hybrid approach. Assuming your code is <em>under version control</em> so that it is possible to safely revert any changes, delete your Podfile, <em>then</em> run the linking command:</p>
<pre><code>react-native link react-native-keychain
</code></pre><p>Now, undo your Podfile deletion. For iOS 10 you’ll need to enable the <code>Keychain Sharing</code> entitlement in the <code>Capabilities</code> section of your build target.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/2HiA1V62TFWjNAJW6-sS2E-EpCAkon9aqFjE" alt="Image" width="800" height="191" loading="lazy"></p>
<p>Add the following key value pair to <em>Info.plist</em>:</p>
<pre><code>&lt;key&gt;NSFaceIDUsageDescription&lt;<span class="hljs-regexp">/key&gt;&lt;string&gt;Enabling Face ID allows you quick and secure access to your account.&lt;/</span>string&gt;
</code></pre><p>Then rebuild your project with:</p>
<pre><code>react-native run-ios
</code></pre><p>In case you encounter difficulty in installing react-native-keychain, refer to this <a target="_blank" href="https://github.com/oblador/react-native-keychain">GitHub README</a>.</p>
<p>Before asking the user to authenticate with TouchID/FaceID, it is wise to check if the user’s iOS device supports such capability by calling <code>[getSupportedBiometryType](https://github.com/oblador/react-native-keychain#getsupportedbiometrytype)</code>:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/UOLq4S9cQehzR8tgQIJqHEOofr-PK9J4a4lN" alt="Image" width="800" height="480" loading="lazy"></p>
<p>After confirming that biometric authentication is supported, you need to save some content in the keychain and set access control flags. The content could be user credentials or some access token. Keychain entry is encrypted and stored in secure storage. To store a value in the keychain, call <code>[setGenericPassword](https://github.com/oblador/react-native-keychain#setgenericpasswordusername-password--accesscontrol-accessible-accessgroup-service-securitylevel-)</code> :</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/9tIdxr-QPhcRBF2KOEA7k1stwcsB2RZ21HD-" alt="Image" width="800" height="200" loading="lazy"></p>
<p>A few points to note here:</p>
<ul>
<li>Setting <em>accessControl</em> to any of these <code>[Keychain.ACCESS_CONTROL](https://github.com/oblador/react-native-keychain#keychainaccess_control-enum)</code> enum values <code>BIOMETRY_ANY</code> , <code>BIOMETRY_CURRENT_SET</code> , <code>BIOMETRY_ANY_OR_DEVICE_PASSCODE</code> , <code>BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE</code> mandates that the user authenticate with TouchID/FaceID whenever we attempt to retrieve the keychain item.</li>
<li>We also set <em>accessible</em> to <code>Keychain.ACCESSIBLE</code> enum value <code>WHEN_PASSCODE_SET_THIS_DEVICE_ONLY</code> . This is the strictest accessible constraint, which enforces:</li>
</ul>
<pre><code>Your device must be unlocked <span class="hljs-keyword">for</span> the secret to be accessible.
</code></pre><pre><code>Your device must have a device passcode set.
</code></pre><pre><code>If you turn off your device passcode, the secret is deleted.
</code></pre><pre><code>The secret cannot be restored to a different device.
</code></pre><pre><code>The secret is not included <span class="hljs-keyword">in</span> iCloud backups.
</code></pre><p>Finally, we trigger the TouchID/FaceID authentication prompt by attempting to access the previously stored keychain value with <code>[getGenericPassword](https://github.com/oblador/react-native-keychain#getgenericpassword-authenticationprompt-service-)</code> :</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/ZX3bMhpsIXH9XNWGJklGOL5yUxNlOrXPkbK6" alt="Image" width="800" height="598" loading="lazy"></p>
<p>Since we saved our secret with access control previously, accessing the item requires user to pass biometric authentication. When authentication is successful, the result returns an object, whose <em>username</em> is ‘your-secret-name’, <em>password</em> is ‘your-secret-value’, and <em>service</em> is ‘your-service-name’.</p>
<p>After <em>5 failed attempts of TouchID/FaceID authentication system-wide</em>, biometric authentication is turned off everywhere on the device. The user must lock and unlock the device with a passcode to re-enable TouchID/FaceID. That’s why at line 14 we have to check for supported biometry type and handle the case appropriately, e.g. asking the user to login with their username/password.</p>
<h4 id="heading-caveats"><strong>Caveats</strong></h4>
<p>Though biometric authentication with react-native-keychain is suitable for critical applications, there are a few caveats I would like to bring to your attention:</p>
<p><strong>There is <em>no passcode fallback</em></strong><em>.</em> You may receive a requirement to allow the user to authenticate with their device passcode. Looking at the package’s README, you should find <code>Keychain.ACCESS_CONTROL</code> enum keys <code>DEVICE_PASSCODE</code> , <code>BIOMETRY_ANY_OR_DEVICE_PASSCODE</code> , <code>BIOMETRY_CURRENT_SET_OR_DEVICE_PASSCODE</code> .</p>
<p>Unfortunately, setting an access control value when calling <code>setGenericPassword</code> to any of those three enum keys do not enable an “Enter Password” fallback. <a target="_blank" href="https://github.com/oblador/react-native-keychain/issues/182">The issue has been reported on GitHub</a>, but there has been no response at the time of publishing this article.</p>
<p>You may think of implementing passcode fallback with a different library. Be aware that your system is only <em>as secure as your weakest link</em>. If your passcode fallback implementation executes at the application layer, it is a potential target for a security attack and defeats the purpose of relying on Keychain Services for biometric authentication.</p>
<p>Also<strong>, authenticating with react-native-keychain on Android devices <em>may not be considered secure</em></strong> as there is no equivalence of Keychain Services in Android.</p>
<h4 id="heading-wrapping-up">Wrapping up</h4>
<p>Thank you for reading this far. I hope you’ve found the tutorial useful. One improvement you may want to make is asking the user whether they would like to opt-in for biometric authentication before enabling it in your app. Also, you may add a setting to let the user turn on or off TouchID/FaceID authentication in your app settings page.</p>
<h4 id="heading-references"><strong>References</strong></h4>
<ul>
<li><a target="_blank" href="https://www.punchkick.com/blog/2016/03/31/best-practices-of-implementing-touch-id-within-financial-apps">Why local authentication is not secure</a></li>
<li><a target="_blank" href="https://docplayer.net/62572307-Keychain-and-authentication-with-touch-id.html">How Keychain Authentication with Touch ID Works</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
