DEV Community

susheel kumar
susheel kumar

Posted on

Introducing Sakshsky-Auth: Secure Passwordless Email Authentication for Node.js

In today's fast-paced digital world, traditional password-based authentication is increasingly seen as a vulnerability—prone to breaches, phishing, and user frustration. Enter passwordless authentication, a modern approach that leverages secure, one-time verification methods like email codes. At Sakshsky, we're excited to release sakshsky-auth, a lightweight Node.js package designed to make implementing robust, tamper-resistant email-based authentication simple and secure.

Whether you're building a web app, API, or internal tool, sakshsky-auth handles the heavy lifting: generating verification codes, binding them to device fingerprints, and verifying incoming emails with built-in IMAP/POP3 monitoring. Let's dive into what makes this package a game-changer for developers prioritizing security without complexity.

Why Passwordless Authentication?

Passwords are a pain point. Users forget them, attackers crack them, and managing them securely requires hashing, salting, and constant updates. Passwordless systems, like those used by companies such as Slack or GitHub, shift the burden to trusted channels like email or biometrics.

Sakshsky-auth focuses on email verification, enhanced with device fingerprinting to prevent common attacks like code interception or replay. It's ideal for apps where email is a primary identifier, offering a seamless user experience: Enter your email, get a code, send it back, and you're in—no passwords required.

Key Features

Sakshsky-auth is built with security and ease-of-use in mind. Here's what sets it apart:

  • Passwordless Flow: Users initiate login with their email, receive a pre-filled verification email, and send it to complete auth.
  • Device Fingerprinting: Collects session data (IP, UA, browser, etc.) to create a unique "fingerprint" bound to the code, reducing cross-device tampering risks.
  • Tamper Detection: Uses salted SHA-256 hashes to verify email integrity—any alteration to the code or hash fails authentication.
  • Built-in Email Monitoring: Supports IMAP and POP3 for real-time or polled inbox checking, with auto-protocol detection.
  • Customizable Code Generation: Use the default UUID or provide your own generator for shorter/OTP-style codes.
  • MongoDB Integration: Stores temporary verification records via Mongoose for easy expiry and cleanup.
  • Socket.IO Support: Real-time notifications for successful verifications, perfect for live apps.

The package is modular, lightweight (under 50KB installed), and has no external runtime dependencies beyond what's needed for your stack.

How It Works Under the Hood

  1. Initiation: On login request, the package generates a code, collects a device fingerprint, salts and hashes it, and prepares a pre-filled email (using mailto: for client-side compose).
  2. Email Sending: The user sends the email with the code and hash.
  3. Verification: The package monitors the inbox (IMAP/IDLE for real-time or POP3/polling), parses incoming emails, extracts code/hash, rebuilds the hash from stored data, and checks for matches. On success, it triggers a callback (e.g., Socket.IO emit) and deletes the record.
  4. Security Checks: Expiry, sender match, and hash validation ensure safety.

This flow minimizes attack surfaces while keeping things user-friendly.

Installation and Quick Start

Get started in minutes:

npm install sakshsky-auth
Enter fullscreen mode Exit fullscreen mode

In your Node.js app (e.g., Express):

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const mongoose = require('mongoose');
const path = require('path');
const { sakshskyInitLoginHandler, sakshskyStartAuthMonitor } = require('sakshsky-auth');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/emailAuth');

// Define schemas
const verificationSchema = new mongoose.Schema({
  email: String,
  code: String,
  expiry: Date,
  socketId: String,
  hashedFingerprint: String,
  salt: String
});
const Verification = mongoose.model('Verification', verificationSchema);

// Login initiation route
app.post('/api/init-login', async (req, res) => {
  const serverEmail = 'verify@yourdomain.com'; // Your inbound email
  await sakshskyInitLoginHandler(req, res, serverEmail);
});

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

// Dashboard (protect in production)
app.get('/dashboard', (req, res) => {
  res.send('Logged in!');
});

// Email config
const emailConfig = {
  host: 'imap.gmail.com',
  port: 993,
  user: 'your-inbox@gmail.com',
  pass: 'app-password'
};

// Start monitor
sakshskyStartAuthMonitor(emailConfig, (verification) => {
  io.to(verification.socketId).emit('verified', { email: verification.email });
});

server.listen(3000, () => console.log('Server on port 3000'));
Enter fullscreen mode Exit fullscreen mode

Detailed Example: Copy-Paste and Run

To help you get started quickly, here's a complete, copy-pasteable example. This includes a basic Express server, MongoDB setup, and a simple frontend HTML file. Save the server code as server.js, the HTML as public/index.html, and run node server.js.

Server Code (server.js)

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const path = require('path');
const mongoose = require('mongoose');
const { sakshskyInitLoginHandler, sakshskyStartAuthMonitor } = require('sakshsky-auth');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/emailAuth', { useNewUrlParser: true, useUnifiedTopology: true })
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.error('MongoDB error:', err));

// Define verification schema
const verificationSchema = new mongoose.Schema({
  email: String,
  code: String,
  expiry: Date,
  socketId: String,
  hashedFingerprint: String,
  salt: String
});
const Verification = mongoose.model('Verification', verificationSchema);

// Login initiation route
app.post('/api/init-login', async (req, res) => {
  const serverEmail = 'your-server-email@example.com'; // Replace with your inbound email
  await sakshskyInitLoginHandler(req, res, serverEmail);
});

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

// Dashboard example
app.get('/dashboard', (req, res) => {
  res.send('Welcome to the dashboard! You are logged in.');
});

// Email config (replace with your details)
const emailConfig = {
  host: 'imap.gmail.com',
  port: 993,
  user: 'your-inbox@gmail.com',
  pass: 'your-app-password' // Use Gmail app password
};

// Start auth monitor
sakshskyStartAuthMonitor(emailConfig, (verification) => {
  io.to(verification.socketId).emit('verified', { email: verification.email });
});

server.listen(3000, () => console.log('Server running on http://localhost:3000'));
Enter fullscreen mode Exit fullscreen mode

Frontend Code (public/index.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Sakshsky Auth Example</title>
  <script src="/socket.io/socket.io.js"></script>
</head>
<body>
  <h1>Passwordless Email Login</h1>
  <input type="email" id="email" placeholder="Your Email" required>
  <button onclick="initLogin()">Login</button>

  <script>
    const socket = io();
    function initLogin() {
      const email = document.getElementById('email').value;
      if (!email) return alert('Enter email');
      fetch('/api/init-login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, socketId: socket.id })
      }).then(res => res.json()).then(data => {
        window.location.href = `mailto:${data.toEmail}?subject=${encodeURIComponent(data.subject)}&body=${encodeURIComponent(data.body)}`;
        socket.on('verified', (data) => {
          alert(`Logged in as ${data.email}`);
          window.location.href = '/dashboard';
        });
      }).catch(err => alert('Error'));
    }
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Run node server.js, visit http://localhost:3000, enter an email, send the pre-filled message, and watch for the verification alert!

Security Considerations

While robust, email auth has limitations (e.g., inbox compromise). Sakshsky-auth mitigates with hashes and fingerprints, but for high-stakes apps, combine with 2FA or WebAuthn. Always use HTTPS, rate-limit requests, and monitor for anomalies.

Future Plans

We're planning support for SMS fallback, WebAuthn integration, and customizable fingerprints. Feedback welcome on GitHub!

At Sakshsky, we're committed to secure, simple tools. Try sakshsky-auth today and let us know what you think!

Top comments (0)