<?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" version="2.0"><channel><title><![CDATA[Dev Simplified]]></title><description><![CDATA[Simplify computer science and software development concepts, from networking to coding and tools, making complex topics easy for beginners.]]></description><link>https://blog.amitkumarashutosh.in</link><generator>RSS for Node</generator><lastBuildDate>Mon, 18 May 2026 20:19:10 GMT</lastBuildDate><atom:link href="https://blog.amitkumarashutosh.in/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Step-by-Step Guide to WebAuthn-Based 2FA Using SimpleWebAuthn Library]]></title><description><![CDATA[Introduction
WebAuthn is a modern authentication standard that enables passwordless and phishing-resistant login using biometrics (Face ID, fingerprints), security keys, or device PINs. It enhances security by eliminating reliance on traditional pass...]]></description><link>https://blog.amitkumarashutosh.in/implementing-webauthn-in-your-project-using-simplewebauthn-library</link><guid isPermaLink="true">https://blog.amitkumarashutosh.in/implementing-webauthn-in-your-project-using-simplewebauthn-library</guid><dc:creator><![CDATA[Amit Ashutosh]]></dc:creator><pubDate>Fri, 07 Mar 2025 08:32:11 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>WebAuthn is a modern authentication standard that enables passwordless and phishing-resistant login using biometrics (Face ID, fingerprints), security keys, or device PINs. It enhances security by eliminating reliance on traditional passwords, reducing the risk of credential theft and phishing attacks.</p>
<p>In this blog, I will guide you through integrating <strong>SimpleWebAuthn</strong> into your existing project with a step-by-step approach, making it easier to implement secure authentication.</p>
<h3 id="heading-how-simplewebauthn-works"><strong>How SimpleWebAuthn Works</strong></h3>
<p>WebAuthn uses <strong>public-key cryptography</strong> and a <strong>challenge-response mechanism</strong> for secure authentication. Instead of passwords, users authenticate with <strong>biometrics or security keys</strong>, ensuring only legitimate users can log in.</p>
<h4 id="heading-registration-credential-creation"><strong>Registration (Credential Creation)</strong></h4>
<p>During registration, the server generates a <strong>challenge</strong> and sends it to the frontend. The user verifies their identity using <strong>biometrics, a security key, or a device PIN</strong>. The authenticator (device or security key) then generates a <strong>key pair</strong> consisting of a <strong>public key</strong> and a <strong>private key</strong>. The <strong>public key</strong> and signed challenge are sent back to the server, where they are securely stored as the user’s credential.</p>
<h4 id="heading-authentication-login"><strong>Authentication (Login)</strong></h4>
<p>When a user attempts to log in, the server generates a <strong>new challenge</strong> and sends it to the frontend. The user verifies their identity using their previously registered authentication method. The authenticator <strong>signs</strong> the challenge using the <strong>private key</strong> stored on the device and returns the response. The server then verifies the signature using the stored <strong>public key</strong>. If the signature is valid, the user is authenticated successfully.</p>
<p>This process ensures that credentials are <strong>bound to the user’s device</strong> and cannot be phished or stolen like traditional passwords, making authentication significantly more secure.Prerequisites</p>
<p>Before starting, ensure you have:</p>
<ul>
<li><p><strong>A Node.js backend</strong> (Express preferred)</p>
</li>
<li><p><strong>A React frontend</strong> (or any other frontend framework)</p>
</li>
<li><p><strong>Basic knowledge of authentication</strong> (JWT, sessions, etc.)</p>
</li>
</ul>
<h2 id="heading-step-1-install-simplewebauthn">Step 1: Install SimpleWebAuthn</h2>
<p>To get started, install the required dependencies in your project.</p>
<h3 id="heading-backend-installation">Backend Installation:</h3>
<pre><code class="lang-plaintext">npm install @simplewebauthn/server
</code></pre>
<h3 id="heading-frontend-installation">Frontend Installation:</h3>
<pre><code class="lang-plaintext">npm install @simplewebauthn/browser
</code></pre>
<h2 id="heading-step-2-setting-up-backend-api">Step 2: Setting Up Backend API</h2>
<p>Create a <code>.env</code> file inside the <code>server</code> folder and define your WebAuthn settings.</p>
<h4 id="heading-deployment">Deployment:</h4>
<pre><code class="lang-plaintext">RP_ID=yourdomain.com  # Do not include 'http://', only the domain/subdomain name (e.g. example.com)
ORIGIN=http://example.com 
RP_NAME=random_name
</code></pre>
<h4 id="heading-localhost">Localhost:</h4>
<pre><code class="lang-plaintext">RP_ID=localhost
ORIGIN=http://localhost:5173 # Your localhost frontend
RP_NAME=random_name
</code></pre>
<p>Now, in your <code>index.ts</code> (or <code>server.ts</code>) main file, import <code>'dotenv/config'</code> to load environment variables:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-string">'dotenv/config'</span>;

<span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express;
const app=express();

app.use('/api/user,userRouter);
app.use('/api/passkey,passkeyRouter);

app.listen(3000,()=&gt;console.log("</span>Server listening on port <span class="hljs-number">3000</span><span class="hljs-string">");</span>
</code></pre>
<h3 id="heading-create-models">Create Models</h3>
<p>In <code>server/src/models</code>, define the necessary data models.</p>
<h4 id="heading-1-user-model">1. User Model</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>

<span class="hljs-keyword">const</span> userSchema = <span class="hljs-keyword">new</span> mongoose.Schema&lt;IUserDocument&gt;({
  username: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> },
  email: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span>, unique: <span class="hljs-literal">true</span>, match: [<span class="hljs-regexp">/^\S+@\S+\.\S+$/</span>] },
  password: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> },
  twoFactorAuth: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">boolean</span>, <span class="hljs-keyword">default</span>: <span class="hljs-literal">false</span> },
  passkey: [{ <span class="hljs-keyword">type</span>: mongoose.Schema.Types.ObjectId, ref: <span class="hljs-string">"Passkey"</span> }],
}, { timestamps: <span class="hljs-literal">true</span> });

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> User = mongoose.model&lt;IUserDocument&gt;(<span class="hljs-string">"User"</span>, userSchema);
</code></pre>
<h4 id="heading-2-challenge-model">2. Challenge Model</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>

<span class="hljs-keyword">const</span> ChallengeSchema = <span class="hljs-keyword">new</span> mongoose.Schema&lt;IChallenge&gt;(
  {
    userId: { <span class="hljs-keyword">type</span>: Schema.Types.ObjectId, ref: <span class="hljs-string">"User"</span>, required: <span class="hljs-literal">true</span> },
    payload: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> },
    createdAt: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">Date</span>, <span class="hljs-keyword">default</span>: <span class="hljs-built_in">Date</span>.now, expires: <span class="hljs-number">300</span> }, <span class="hljs-comment">// 5 minutes</span>
  },{ timestamps: <span class="hljs-literal">true</span> });

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Challenge = mongoose.model&lt;IChallenge&gt;(<span class="hljs-string">"Challenge"</span>,ChallengeSchema);
</code></pre>
<h4 id="heading-3-passkey-model">3. Passkey Model</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>

<span class="hljs-keyword">const</span> PasskeySchema = <span class="hljs-keyword">new</span> mongoose.Schema&lt;IPasskey&gt;({
  userId: { <span class="hljs-keyword">type</span>: Schema.Types.ObjectId, ref: <span class="hljs-string">"User"</span>, required: <span class="hljs-literal">true</span> },
  credentialID: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span>, unique: <span class="hljs-literal">true</span> },
  publicKey: { <span class="hljs-keyword">type</span>: Buffer, required: <span class="hljs-literal">true</span> },
  transports: { <span class="hljs-keyword">type</span>: [<span class="hljs-built_in">String</span>], <span class="hljs-built_in">enum</span>: [<span class="hljs-string">"usb"</span>, <span class="hljs-string">"nfc"</span>, <span class="hljs-string">"ble"</span>, <span class="hljs-string">"internal"</span>, <span class="hljs-string">"hybrid"</span>] },
  counter: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">Number</span>, required: <span class="hljs-literal">true</span>, <span class="hljs-keyword">default</span>: <span class="hljs-number">0</span> },
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Passkey = mongoose.model&lt;IPasskey&gt;(<span class="hljs-string">"Passkey"</span>, PasskeySchema);
</code></pre>
<h3 id="heading-create-controllers">Create Controllers</h3>
<p>In <code>server/src/controllers</code>, define define the necessary controllers.</p>
<h4 id="heading-1-registration-passkey-endpoint">1. Registration Passkey Endpoint</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> registerPasskey = <span class="hljs-keyword">async</span> (req: AuthRequest, res: Response) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> userId: <span class="hljs-built_in">string</span> = req._id!;
    <span class="hljs-keyword">const</span> user: UserInfo = <span class="hljs-keyword">await</span> User.findById(userId);
    <span class="hljs-keyword">const</span> userPasskeys: IPasskey[] = <span class="hljs-keyword">await</span> Passkey.find({ userId }); 
    <span class="hljs-comment">//Return an error if the user or passkey is missing.</span>

    <span class="hljs-keyword">const</span> options: PublicKeyCredentialCreationOptionsJSON =
      <span class="hljs-keyword">await</span> generateRegistrationOptions({
        rpName: process.env.RP_NAME <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>,
        rpID: process.env.RP_ID <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>,
        userID: isoUint8Array.fromUTF8String(userId),
        userName: user.username,
        excludeCredentials: userPasskeys.map(<span class="hljs-function">(<span class="hljs-params">passkey</span>) =&gt;</span> ({
          id: passkey.credentialID,
        })),
      }); <span class="hljs-comment">// Return an error if registration fails.</span>

    <span class="hljs-keyword">await</span> Challenge.create({ userId, payload: options.challenge });
    res.status(<span class="hljs-number">200</span>).json({ options, success: <span class="hljs-literal">true</span> });
  } <span class="hljs-keyword">catch</span> (error) {}
};
</code></pre>
<h4 id="heading-2-verification-passkey-endpoint">2. Verification Passkey Endpoint</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> verifyPasskey = <span class="hljs-keyword">async</span> (req: AuthRequest, res: Response) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { credential }: { credential: RegistrationResponseJSON } = req.body;
    <span class="hljs-keyword">const</span> userId: <span class="hljs-built_in">string</span> = req._id!;
    <span class="hljs-keyword">const</span> user: UserInfo = <span class="hljs-keyword">await</span> User.findById(userId);
    <span class="hljs-keyword">const</span> challenge: ChallengeInfo = <span class="hljs-keyword">await</span> Challenge.findOne({ userId });
    <span class="hljs-comment">//Return an error if the user or challenge is missing.</span>

    <span class="hljs-keyword">const</span> verificationResult: VerifiedRegistrationResponse =
      <span class="hljs-keyword">await</span> verifyRegistrationResponse({
        response: credential,
        expectedChallenge: challenge.payload,
        expectedOrigin: process.env.ORIGIN!,
        expectedRPID: process.env.RP_ID!,
      }); <span class="hljs-comment">// Return an error if verification fails.</span>

    <span class="hljs-keyword">const</span> {id,publicKey,counter,transports} = verificationResult.registrationInfo.credential;

    <span class="hljs-keyword">const</span> passkey: IPasskey = <span class="hljs-keyword">await</span> Passkey.create({
      userId,
      credentialID: id,
      publicKey: Buffer.from(publicKey),
      counter: counter,
      transports: transports,
    });

    <span class="hljs-keyword">await</span> Challenge.findOneAndDelete({ userId });
    user.passkeys?.push(passkey.id);
    <span class="hljs-keyword">await</span> user.save();

    res.status(<span class="hljs-number">200</span>).json({ message: <span class="hljs-string">"Registration successful"</span>, success: <span class="hljs-literal">true</span> });
  } <span class="hljs-keyword">catch</span> (error) {}
};
</code></pre>
<h4 id="heading-3-login-passkey-endpoint">3. Login Passkey Endpoint</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> loginWithPasskey = <span class="hljs-keyword">async</span> (req: AuthRequest, res: Response) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> userId: <span class="hljs-built_in">string</span> = req._id!;
    <span class="hljs-keyword">const</span> user: UserInfo = <span class="hljs-keyword">await</span> User.findById(userId);
    <span class="hljs-keyword">const</span> userPasskeys: IPasskey[] = <span class="hljs-keyword">await</span> Passkey.find({ userId }); 
    <span class="hljs-comment">//Return an error if the user or passkey is missing.</span>

    <span class="hljs-keyword">const</span> options: PublicKeyCredentialRequestOptionsJSON =
      <span class="hljs-keyword">await</span> generateAuthenticationOptions({
        rpID: process.env.RP_ID!,
        allowCredentials: userPasskeys.map(<span class="hljs-function">(<span class="hljs-params">passkey</span>) =&gt;</span> ({
          id: passkey.credentialID,
          transports: passkey.transports,
        })),
      }); <span class="hljs-comment">// Return an error if authentication fails.</span>

    <span class="hljs-keyword">await</span> Challenge.create({ userId, payload: options.challenge });
    res.status(<span class="hljs-number">200</span>).json({ options, success: <span class="hljs-literal">true</span> });
  } <span class="hljs-keyword">catch</span> (error) {}
};
</code></pre>
<h4 id="heading-4-login-verification-passkey-endpoint">4. Login Verification Passkey Endpoint</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> verifyWithPasskey = <span class="hljs-keyword">async</span> (req: AuthRequest, res: Response) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { credential }: { credential: AuthenticationResponseJSON } = req.body;
    <span class="hljs-keyword">const</span> userId = req._id!;
    <span class="hljs-keyword">const</span> user: UserInfo = <span class="hljs-keyword">await</span> User.findById(userId); 
    <span class="hljs-keyword">const</span> challenge: ChallengeInfo = <span class="hljs-keyword">await</span> Challenge.findOne({ userId });
    <span class="hljs-keyword">const</span> passkey: PasskeyInfo = <span class="hljs-keyword">await</span> Passkey.findOne({ userId, credentialID: credential.id });
    <span class="hljs-comment">////Return an error if the user, passkey or challenge is missing.</span>

    <span class="hljs-keyword">const</span> verificationResult: VerifiedAuthenticationResponse =
      <span class="hljs-keyword">await</span> verifyAuthenticationResponse({
        expectedChallenge: challenge.payload,
        expectedOrigin: process.env.ORIGIN!,
        expectedRPID: process.env.RP_ID!,
        response: credential,
        credential: {
          id: passkey.credentialID,
          publicKey: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Uint8Array</span>(Buffer.from(passkey.publicKey)),
          counter: passkey.counter,
          transports: passkey.transports,
        },
      });  <span class="hljs-comment">// Return an error if verification fails.</span>

    <span class="hljs-keyword">if</span> (!verificationResult.verified) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ message: <span class="hljs-string">"Authentication failed"</span>, success: <span class="hljs-literal">false</span> });

    <span class="hljs-comment">// Prevent replay attacks using counter</span>
    <span class="hljs-keyword">if</span> ( verificationResult.authenticationInfo?.newCounter &gt; <span class="hljs-number">0</span> &amp;&amp; verificationResult.authenticationInfo.newCounter &lt;= passkey.counter ){
      <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ message: <span class="hljs-string">"Counter replay detected"</span>, success: <span class="hljs-literal">false</span> });
    }
    <span class="hljs-comment">// Update stored counter with the new counter from the authenticator</span>
    passkey.counter = verificationResult.authenticationInfo.newCounter;
    <span class="hljs-keyword">await</span> passkey.save();

    <span class="hljs-keyword">await</span> Challenge.findOneAndDelete({ userId });
    <span class="hljs-keyword">const</span> token: <span class="hljs-built_in">string</span> = <span class="hljs-keyword">await</span> user.generateToken();

    res.status(<span class="hljs-number">200</span>).cookie(<span class="hljs-string">"token"</span>, token ).json({ message: <span class="hljs-string">"Login successful with passkey"</span>, success: <span class="hljs-literal">true</span> });
  } <span class="hljs-keyword">catch</span> (error) {}
};
</code></pre>
<h3 id="heading-create-routes">Create Routes</h3>
<p>In <code>server/src/routes</code>, define the required routes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> { isAuthenticated } <span class="hljs-keyword">from</span> <span class="hljs-string">"../middlewares/auth"</span>;
<span class="hljs-keyword">import</span> {loginWithPasskey,registerPasskey,verifyPasskey,verifyWithPasskey} <span class="hljs-keyword">from</span> <span class="hljs-string">"../controllers/passkey.controller"</span>;

<span class="hljs-keyword">const</span> router = express.Router();

router.route(<span class="hljs-string">"/register"</span>).post(isAuthenticated, registerPasskey);
router.route(<span class="hljs-string">"/verify"</span>).post(isAuthenticated, verifyPasskey);
router.route(<span class="hljs-string">"/login-passkey"</span>).post(isAuthenticated, loginWithPasskey);
router.route(<span class="hljs-string">"/verify-passkey"</span>).post(isAuthenticated, verifyWithPasskey);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router;
</code></pre>
<h2 id="heading-step-3-implementing-webauthn-on-frontend">Step 3: Implementing WebAuthn on Frontend</h2>
<h3 id="heading-1login-with-passkey">1.Login With Passkey</h3>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> handleRegisterPasskey = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/passkey/register"</span>, {
        method: <span class="hljs-string">"POST"</span>,
        headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
      });
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> data.json();

      <span class="hljs-keyword">const</span> registerPasskey = <span class="hljs-keyword">await</span> startRegistration({ optionsJSON: response.options });
      <span class="hljs-keyword">if</span> (!registerPasskey) <span class="hljs-keyword">return</span> toast.error(<span class="hljs-string">"Invalid while register passkey"</span>);

      <span class="hljs-keyword">const</span> verifyPasskey = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/passkey/verify"</span>, {
        method: <span class="hljs-string">"POST"</span>,
        headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
        body: <span class="hljs-built_in">JSON</span>.stringify({ credential: registerPasskey }),
      });

      <span class="hljs-keyword">if</span> (verifyPasskey) <span class="hljs-keyword">return</span> toast.success(<span class="hljs-string">"Passkey register successfully."</span>);
    } <span class="hljs-keyword">catch</span> (error) {}
  };
</code></pre>
<h3 id="heading-2verify-with-passkey">2.Verify With Passkey</h3>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> handleLoginWithPasskey = <span class="hljs-keyword">async</span> ({ message, user }: { message: <span class="hljs-built_in">string</span>; user: UserInfo;}) =&gt; {
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/passkey/login-passkey"</span>, {
      method: <span class="hljs-string">"POST"</span>,
      headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
    });
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> data.json();

    <span class="hljs-keyword">const</span> loginPasskey = <span class="hljs-keyword">await</span> startAuthentication({ optionsJSON: response.options });
    <span class="hljs-keyword">if</span> (!loginPasskey) <span class="hljs-keyword">return</span> toast.error(<span class="hljs-string">"Invalid while login passkey"</span>);

    <span class="hljs-keyword">const</span> verifyPasskeyResponse = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/passkey/verify-passkey"</span>, {
      method: <span class="hljs-string">"POST"</span>,
      headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
      body: <span class="hljs-built_in">JSON</span>.stringify({ credential: loginPasskey }),
    });
    <span class="hljs-keyword">const</span> verifyPasskey = <span class="hljs-keyword">await</span> verifyPasskeyResponse.json();

    <span class="hljs-keyword">if</span> (verifyPasskey.success) { <span class="hljs-comment">//change according to your code base</span>
      toast.success(message);
      dispatch(setUser(user)); 
      navigate(<span class="hljs-string">"/"</span>);
    }
  };
</code></pre>
<hr />
<h2 id="heading-videos-and-screenshots">Videos and Screenshots</h2>
<p><a target="_blank" href="https://drive.google.com/drive/folders/1VhzQiVkNvrmzy3Na49BtLJ0i7bKd-dym?usp=sharing">https://drive.google.com/drive/folders/1VhzQiVkNvrmzy3Na49BtLJ0i7bKd-dym?usp=sharing</a></p>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>By following these steps, you have successfully integrated WebAuthn authentication using SimpleWebAuthn. This enhances security by enabling <strong>passwordless authentication</strong>, improving user experience, and reducing phishing risks.</p>
<p>For more details on <strong>SimpleWebAuthn</strong>, check the <a target="_blank" href="https://simplewebauthn.dev/"><strong>official documentation</strong></a>. If you face issues, explore <strong>community discussions</strong> or connect with <strong>WebAuthn developers</strong> for support.</p>
]]></content:encoded></item></channel></rss>