> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ownid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Local Playground Guide

> Set up a fully functional OwnID Boost environment on your local machine.

This guide allows you to set up a fully functional OwnID Boost environment on your local machine. This setup includes a secure Node.js backend and a frontend that supports biometric registration and "Returning User" recognition.

## Prerequisites

Ensure you have the following ready before starting:

* **Node.js** installed on your machine
* **OwnID Application** created via the OwnID Admin Console
  * ([Get Started](https://docs.ownid.com/building-blocks/get-started))
* **ngrok** installed ([Download here](https://ngrok.com/download))

***

<Steps>
  <Step title="Project Initialization">
    Run these commands in your terminal to create the project structure:

    ```bash theme={null}
    mkdir ownid-demo
    cd ownid-demo
    npm init -y
    npm install express body-parser
    mkdir public
    ```
  </Step>

  <Step title="The Backend (server.js)">
    Create a file named `server.js` in your root folder. This contains the Handshake Endpoints and the Security Layer.

    <Warning>
      **Important:** Replace `YOUR_SHARED_SECRET_HERE` with the secret from your OwnID Console.
    </Warning>

    ```javascript server.js theme={null}
    const express = require('express');
    const bodyParser = require('body-parser');
    const path = require('path');
    const crypto = require('crypto');

    const app = express();
    const port = 3001;

    // --- CONFIGURATION ---
    const OWNID_SHARED_SECRET = "YOUR_SHARED_SECRET_HERE"; 

    // TUNNEL BYPASS: Ensures OwnID Cloud can reach your local server through ngrok/devtunnels
    app.use((req, res, next) => {
        res.setHeader('bypass-tunnel-reminder', 'true');
        res.setHeader('X-Tunnel-Skip-Anti-Phishing-Page', 'true');
        next();
    });

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

    function hashPassword(password) {
        return crypto.createHash('sha256').update(password || '').digest('hex');
    }

    // Mock Database
    let users = [{ 
        email: "test@example.com", 
        passwordHash: hashPassword("password123"), 
        firstName: "Test",
        lastName: "User",
        ownIdData: null 
    }];

    // SECURITY: HMAC Signature Verification
    function verifyOwnIDSignature(req, res, next) {
        if (OWNID_SHARED_SECRET === "YOUR_SHARED_SECRET_HERE") return next();
        const signature = req.headers['ownid-signature'];
        const timestamp = req.headers['ownid-timestamp'];
        if (!signature || !timestamp) return res.status(401).send("Missing Headers");

        const dataToSign = JSON.stringify(req.body) + "." + timestamp;
        const generated = crypto.createHmac('sha256', Buffer.from(OWNID_SHARED_SECRET, 'base64'))
                                .update(dataToSign).digest('base64');
        
        if (generated !== signature) return res.status(401).send("Invalid Signature");
        next();
    }

    // --- OWNID HANDSHAKE ENDPOINTS ---

    app.post('/getOwnIDDataByLoginId', verifyOwnIDSignature, (req, res) => {
        const loginId = req.body.loginId?.toLowerCase().trim();
        const user = users.find(u => u.email === loginId);
        console.log(`🔍 Handshake Fetch: ${loginId} | Found: ${!!user?.ownIdData}`);
        res.json({ ownIdData: user ? user.ownIdData : null });
    });

    app.post('/setOwnIDDataByLoginId', verifyOwnIDSignature, (req, res) => {
        const loginId = req.body.loginId?.toLowerCase().trim();
        const user = users.find(u => u.email === loginId);
        if (user) {
            user.ownIdData = req.body.data;
            console.log(`💾 Handshake Saved: ${loginId}`);
            return res.sendStatus(200);
        }
        res.status(404).send('Not Found');
    });

    app.post('/getSessionByLoginId', (req, res) => res.json({ token: "session-" + Date.now() }));

    // --- APP ROUTES ---

    app.post('/register', (req, res) => {
        const { email, password, firstName, lastName, ownIdData } = req.body;
        const lowerEmail = email.toLowerCase().trim();
        users = users.filter(u => u.email !== lowerEmail); // Reset if exists
        users.push({ 
            email: lowerEmail, 
            passwordHash: hashPassword(password || "123"),
            firstName: firstName || '',
            lastName: lastName || '',
            ownIdData: ownIdData || null 
        });
        console.log(`👤 Registered: ${lowerEmail}`);
        res.status(200).json({ firstName: firstName || '', lastName: lastName || '' });
    });

    app.post('/login', (req, res) => {
        const { email, isOwnID, password } = req.body;
        const user = users.find(u => u.email === email?.toLowerCase().trim());
        
        // Any password works for local testing
        if (user && (isOwnID || password)) {
            console.log(`✅ Login Success: ${email}`);
            return res.status(200).json({ firstName: user.firstName, lastName: user.lastName });
        }
        res.status(401).send("Fail");
    });

    app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'public', 'login.html')));

    // Landing Page Dashboard
    app.get('/index.html', (req, res) => res.send(`
        <!DOCTYPE html>
        <html>
        <head>
            <title>Dashboard</title>
            <style>
                body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background: #f8f9fa; }
                .card { background: white; padding: 40px; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.05); text-align: center; }
                a { display: inline-block; margin-top: 20px; padding: 10px 20px; background: #e74c3c; color: white; text-decoration: none; border-radius: 8px; font-weight: bold; }
            </style>
        </head>
        <body>
            <div class="card">
                <h1 style="color: #28a745; margin-top: 0;">✅ Login Successful</h1>
                <h3 style="color: #333;">Welcome, <span id="user-name"></span>!</h3>
                <a href='/' onclick="localStorage.clear()">Logout</a>
            </div>
            <script>
                const fn = localStorage.getItem('firstName') || '';
                const ln = localStorage.getItem('lastName') || '';
                const fullName = (fn + ' ' + ln).trim();
                document.getElementById('user-name').innerText = fullName || 'User';
            </script>
        </body>
        </html>
    `));

    app.listen(port, '0.0.0.0', () => console.log(`🚀 Server running on http://localhost:${port}`));
    ```
  </Step>

  <Step title="The Frontend">
    Create these two files inside the `public` folder.

    <Info>Replace `YOUR_APP_ID` with the App ID from your OwnID Console.</Info>

    <CodeGroup>
      ```html public/register.html theme={null}
      <!DOCTYPE html>
      <html>
      <body>
          <h2>Create Account</h2>
          <input type="text" id="firstName" placeholder="First Name">
          <input type="text" id="lastName" placeholder="Last Name">
          <input type="email" id="email" placeholder="Email">
          <input type="password" id="password" placeholder="Password">
          <div id="ownid-container" style="margin-bottom: 10px;"></div>
          <button id="registerBtn">Standard Register</button>
          <p><a href="/">Go to Login</a></p>

          <script>
              ((o,w,n,i,d)=>{o[i]=o[i]||(async(...a)=>((o[i].q=o[i].q||[]).push(a),{error:null,data:null})),
              (d=w.createElement("script")).src='[https://cdn.ownid.com/sdk/'+n,d.async=1,w.head.appendChild(d)](https://cdn.ownid.com/sdk/'+n,d.async=1,w.head.appendChild(d))})
              (window,document,'YOUR_APP_ID','ownid');

              function doRegister(ownIdData = null) {
                  fetch('/register', {
                      method: 'POST',
                      headers: { 'Content-Type': 'application/json' },
                      body: JSON.stringify({ 
                          firstName: document.getElementById("firstName").value,
                          lastName: document.getElementById("lastName").value,
                          email: document.getElementById("email").value,
                          password: document.getElementById("password").value,
                          ownIdData: ownIdData 
                      })
                  }).then(res => res.json()).then(data => {
                      localStorage.setItem('firstName', data.firstName);
                      localStorage.setItem('lastName', data.lastName);
                      window.location.href = '/index.html';
                  }).catch(() => alert("Registration failed"));
              }

              document.getElementById('registerBtn').addEventListener('click', () => doRegister());

              ownid("register", {
                  loginIdField: document.getElementById("email"),
                  passwordField: document.getElementById("password"),
                  element: document.getElementById("ownid-container"),
                  onRegister: (data) => doRegister(data.data)
              });
          </script>
      </body>
      </html>
      ```

      ```html public/login.html theme={null}
      <!DOCTYPE html>
      <html>
      <body>
          <h2>Login</h2>
          <input type="email" id="email" placeholder="Email">
          <input type="password" id="password" placeholder="Password">
          <div id="ownid-container" style="margin-bottom: 10px;"></div>
          <button id="loginBtn">Standard Login</button>
          <p><a href="/register.html">Create account</a></p>

          <script>
              ((o,w,n,i,d)=>{o[i]=o[i]||(async(...a)=>((o[i].q=o[i].q||[]).push(a),{error:null,data:null})),
              (d=w.createElement("script")).src='[https://cdn.ownid.com/sdk/'+n,d.async=1,w.head.appendChild(d)](https://cdn.ownid.com/sdk/'+n,d.async=1,w.head.appendChild(d))})
              (window,document,'YOUR_APP_ID','ownid');

              const emailField = document.getElementById("email");
              const passwordField = document.getElementById("password");

              function handleLogin(payload) {
                  fetch('/login', {
                      method: 'POST',
                      headers: { 'Content-Type': 'application/json' },
                      body: JSON.stringify(payload)
                  }).then(res => {
                      if (res.ok) {
                          res.json().then(data => {
                              localStorage.setItem('firstName', data.firstName);
                              localStorage.setItem('lastName', data.lastName);
                              window.location.href = '/index.html';
                          });
                      } else {
                          alert('Invalid credentials');
                      }
                  });
              }

              document.getElementById('loginBtn').addEventListener('click', () => {
                  handleLogin({ 
                      email: emailField.value, 
                      password: passwordField.value, 
                      isOwnID: false 
                  });
              });

              ownid("login", {
                  loginIdField: emailField,
                  passwordField: passwordField,
                  element: document.getElementById("ownid-container"),
                  onLogin: (data) => {
                      handleLogin({ 
                          email: data.loginId || emailField.value, 
                          isOwnID: true 
                      });
                  }
              });

              // Trigger Handshake when user stops typing
              emailField.addEventListener('blur', () => ownid("refresh"));
          </script>
      </body>
      </html>
      ```
    </CodeGroup>
  </Step>

  <Step title="Running Locally">
    <img width="500" src="https://mintcdn.com/ownid/vB8sMNDo6U3bB1sG/images/tunnel_diagram.png?fit=max&auto=format&n=vB8sMNDo6U3bB1sG&q=85&s=8961925a28d6e8cd6278acba3f585c38" alt="Tunnel Diagram" data-path="images/tunnel_diagram.png" />

    In the OwnID integration, the end-user accesses both OwnID and your local development server through their browser. To allow this communication, we need to:

    1. Whitelist the local origin domain (e.g., `localhost`)
    2. Allow the OwnID Server to reach your local development server.

    To achieve this, configure the following in your **OwnID Admin Console**.

    Navigate to the app's main page and add the local origin to the **Allowed Domains**.

    <img width="500" src="https://mintcdn.com/ownid/-VBqqlNX4RpmYITg/images/allowed_domain.png?fit=max&auto=format&n=-VBqqlNX4RpmYITg&q=85&s=7a6331485edb1750f71882b46c8466fe" alt="Allowed Domain" data-path="images/allowed_domain.png" />

    In the app's **Integration / Backend** settings, set the base URL to target a public tunnel that leads to your development server.

    <img width="500" src="https://mintcdn.com/ownid/-VBqqlNX4RpmYITg/images/base_url.png?fit=max&auto=format&n=-VBqqlNX4RpmYITg&q=85&s=67dbb0a747eff2fa65598e655ed7578e" alt="Base Url" data-path="images/base_url.png" />

    ### Creating a Local Tunnel Using ngrok

    Start your local server. It will run on port `3001`.

    ```bash theme={null}
    node server.js
    ```

    Start ngrok, targeting your server's port:

    ```bash theme={null}
    ngrok http 3001
    ```
  </Step>

  <Step title="Verify the &#x22;Returning User&#x22; Flow">
    1. Open your generated ngrok URL in an Incognito/Private window.
    2. Navigate to **Create account**, enter a new email, and complete the biometric enrollment.
    3. You will be automatically redirected to the Login page.
    4. Type the email you just registered and click outside of the input box.

    **The Success:** The widget will immediately switch to a Fingerprint icon. This indicates that your server successfully provided the biometric data via the Handshake.
  </Step>
</Steps>
