Meaning of passwordless authentication for beginners (and let’s make it)




🧠 What does passwordless mean?

Passwordless authentication means the user doesn’t type or even have a password.
Instead, they log in using a secure credential stored on their device — for example:

  • a biometric (fingerprint, Face ID),
  • a PIN, or
  • a hardware key (like YubiKey).
    So the “something you know” (password) is replaced by something you have or are.



🔐 What is WebAuthn?

WebAuthn (Web Authentication API) is a modern W3C web standard created by FIDO Alliance and major browsers (Google, Apple, Mozilla).

It allows websites to use:

  • Public-key cryptography instead of passwords.
  • Secure devices (like your phone or laptop’s TPM or Secure Enclave) to generate and store credentials.

👉 When you register or log in with WebAuthn:

  1. Your browser talks to your device (called an authenticator).
  2. The device creates a key pair:
    • Public key → sent to the server
    • Private key → safely stored in the device, never leaves it
  3. When logging in, your device signs a challenge with the private key — proving it’s really you.



🧩 In short:

Passwordless with WebAuthn = login using cryptographic keys stored securely on your device, verified by your biometrics — no password ever.

It’s the same tech behind Passkeys, which are basically user-friendly WebAuthn credentials synced through your OS (Apple, Google, Microsoft).



Coding time

Let’s make something like passwordless login and registration system in Node.js. For this we need a auth-verify (library for making passkeys use it for passwordless logins)



Step 1:

Let’s install essential packages:

npm i express auth-verify path
Enter fullscreen mode

Exit fullscreen mode



Step 2:

Making web app for asking user getting passkeys:

const express = require('express');
const path = require('path');
const AuthVerify = require('auth-verify');

const app = express();
const PORT = 3000;

// Middleware
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));

// Serve main page
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'index.html'));
});

// Start server
app.listen(PORT, ()=> console.log(`🚀 Server running at http://localhost:${PORT}`)))
Enter fullscreen mode

Exit fullscreen mode



Step 3:

Let’s initialize auth-verify for making passkeys

// Create AuthVerify instance
const auth = new AuthVerify({
  passExp: "2m",          // Challenge expiration
  rpName: "MyApp",        // Display name of your app
  storeTokens: "memory",  // Use in-memory store (good for dev)
});

// Example user (you can use a database later)
const user = {
  id: "user1",
  username: "john_doe",
  credentials: [], // stores WebAuthn credentials
};
Enter fullscreen mode

Exit fullscreen mode



Step 4:

We’ll make API routes for making passkey. It consisted of two steps:

  1. We’ll make passkey and we’ll send it client
  2. Client respond our passkey option request and if it successful we can save it in db or memory for user data.
// 👉 Step 1: Start registration
app.post('/api/register/start', async (req, res) => {
 await auth.passkey.register(user);

    // get WebAuthn options for the client
    const options = auth.passkey.getOptions();
    console.log(options);

  // Send to frontend (don’t wrap inside `{ options }`)

  res.json(options);
});


// 👉 Step 2: Finish registration
app.post('/api/register/finish', async (req, res) => {
  const clientResponse = req.body; // frontend sends credential data

  try {
    const result = await auth.passkey.finish(clientResponse, user);

    if (result.success) {
      // Save the credential to user’s list
      user.credentials.push(result.credential);
      return res.json({ success: true, message: "Passkey registered successfully!" });
    }

    res.status(400).json({ success: false, message: "Verification failed" });
  } catch (err) {
    console.error("Error verifying passkey:", err);
    res.status(500).json({ success: false, message: "Server error" });
  }
});
Enter fullscreen mode

Exit fullscreen mode



Step 5:

Now we’ll handle frontend (client) part because when we send passkey options browser should ask where to save our passkey (maybe in flash drive or phone). For handling this we need a auth-verify client we can simply import by source file:


  "https://cdn.jsdelivr.net/gh/jahongir2007/auth-verify/auth-verify.client.js">
Enter fullscreen mode

Exit fullscreen mode

</span>


  </span>Getting passkey<span class="nt"/>
<span class="nt"/>
<span class="nt"/>
  <span class="nt"/>Getting passkey<span class="nt"/>
  <span class="nt"><button> <span class="na">id=</span><span class="s">"register"</span><span class="nt">></span>Register Passkey<span class="nt"/></button></span>

  <span class="c"><!-- ✅ Load client helper --></span>
  <span class="nt"><script><![CDATA[<span class="na">src=]]></script></span><span class="s">"https://cdn.jsdelivr.net/gh/jahongir2007/auth-verify/auth-verify.client.js"</span><span class="nt">></span>

  <span class="nt"><script/></span>
    <span class="kd">const</span> <span class="nx">auth</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AuthVerify</span><span class="p">({</span>
      <span class="na">apiBase</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://localhost:3000</span><span class="dl">"</span>
    <span class="p">});</span>

    <span class="kd">const</span> <span class="nx">registerBtn</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#register</span><span class="dl">"</span><span class="p">);</span>

    <span class="nx">registerBtn</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="k">async </span><span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
      <span class="k">try</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">publicKey</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">auth</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/register/start</span><span class="dl">'</span><span class="p">).</span><span class="nf">data</span><span class="p">();</span> <span class="c1">// getting passkey from backend</span>
        <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">publicKey</span><span class="p">);</span>

        <span class="kd">const</span> <span class="nx">issuedPublicKey</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">auth</span><span class="p">.</span><span class="nf">issue</span><span class="p">(</span><span class="nx">publicKey</span><span class="p">);</span> <span class="c1">// browser here asks from user where to save the passkey</span>

        <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">auth</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/register/finish</span><span class="dl">'</span><span class="p">).</span><span class="nf">data</span><span class="p">(</span><span class="nx">issuedPublicKey</span><span class="p">);</span> <span class="c1">//sending passkey new data to backend</span>

        <span class="nf">alert</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">message</span> <span class="o">||</span> <span class="dl">"</span><span class="s2">Registration complete!</span><span class="dl">"</span><span class="p">);</span>
      <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
        <span class="nf">alert</span><span class="p">(</span><span class="dl">"</span><span class="s2">Something went wrong: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">err</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">});</span>
  <span class="nt"/>
<span class="nt"/>
<span class="nt"/>
</span></code></pre>
<div class="highlight__panel js-actions-panel">
<div class="highlight__panel-action js-fullscreen-code-action">
    <svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-on"><title>Enter fullscreen mode
    

Exit fullscreen mode



The result:

The result



Conclusion

I hope you now fully understood about passwordless authentication



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *