
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
auth-verify
Advanced tools
A simple Node.js library for sending and verifying OTP via email, SMS and Telegram bot. And generating TOTP codes and QR codes. And handling JWT with Cookies. And also handling passwordless logins with passkeys/webauth. And handling magiclink passwordless
AuthVerify is a modular authentication library for Node.js, providing JWT, OTP, TOTP, Passkeys (WebAuthn), Magic Links, Sessions, and OAuth helpers. You can easily register custom senders for OTPs or notifications. Auth-verify now supports REST API for generating, sending OTP with email/SMS and verifying it.
# from npm (when published)
npm install auth-verify
# or locally during development
# copy the package into your project and `require` it`
AuthVerify (entry): constructs and exposes .jwt, .otp, (optionally) .session, .totp and .oauth managers.JWTManager: sign, verify, decode, revoke tokens. Supports storeTokens: "memory" | "redis" | "none" and middleware with custom cookie, header, and token extraction.OTPManager: generate, store, send, verify, resend OTPs. Supports storeTokens: "memory" | "redis" | "none". Supports email, SMS helper, Telegram bot, and custom dev senders.TOTPManager: generate, verify uri, codes and QR codes.SessionManager: simple session creation/verification/destroy with memory or Redis backend.OAuthManager: Handle OAuth 2.0 logins for 20+ providers.PasskeyManager: Handle passwordless login and registration using WebAuthn/passkey.MagicLinkManager: Handle passwordless login with magic link generation and verification.CrpytoManager: Handle hashing passwords or another data with algorithms like: "pbkdf2" and "scrypt"const AuthVerify = require('auth-verify');
const auth = new AuthVerify({
jwtSecret: 'your_jwt_secret',
cookieName: 'jwt_token',
otpExpiry: 300, // in seconds
storeTokens: 'memory', // or 'redis'
redisUrl: 'redis://localhost:6379',
totp: { digits: 6, step: 30, alg: 'SHA1' },
rpName: 'myApp',
passExp: '2m',
mlSecret: 'ml_secret',
mlExpiry: '5m',
appUrl: 'https://yourapp.com'
});
| Option | Default | Description |
|---|---|---|
jwtSecret | "jwt_secret" | Secret key for JWT signing |
cookieName | "jwt_token" | Cookie name for JWT storage |
otpExpiry | 300 | OTP expiration in seconds |
storeTokens | "memory" | Token storage type (memory or redis) |
redisUrl | undefined | Redis connection string if using Redis |
totp | { digits: 6, step: 30, alg: 'SHA1' } | TOTP configuration |
rpName | "auth-verify" | Relying party name for Passkeys |
passExp | "2m" | Passkey expiration duration |
mlSecret | "ml_secret" | Magic link secret |
mlExpiry | "5m" | Magic link expiration duration |
appUrl | "https://yourapp.com" | App base URL for Magic Links |
AuthVerify includes a powerful jwt manager that simplifies JSON Web Token (JWT) authentication.
It supports automatic cookie handling, memory or Redis token storage, and an Express middleware for route protection.
const AuthVerify = require('auth-verify');
const auth = new AuthVerify({
jwtSecret: 'super_secret_key',
cookieName: 'auth_token',
storeTokens: 'redis', // or 'memory'
redisUrl: 'redis://localhost:6379',
useAlg: 'HS256', // or any supported algorithm
});
After initialization, the JWT system is accessible via auth.jwt.
| Method | Description |
|---|---|
auth.jwt.sign(payload, expiry?, options?) | Creates a JWT token and optionally sets it in a cookie |
auth.jwt.verify(tokenOrReq) | Verifies a token or extracts it automatically from a request |
auth.jwt.decode(token) | Decodes JWT payload without verification |
auth.jwt.revoke(token, revokeTime?) | Revokes a token immediately or after a timeout |
auth.jwt.isRevoked(token) | Checks whether a token has been revoked |
auth.jwt.protect(options?) | Express middleware to protect API routes |
auth.jwt.readCookie(req, name) | Reads a JWT from cookies manually |
auth.jwt.issue(user) | Issues an access token and a refresh token |
auth.jwt.refresh(refreshToken) | Refreshes an access token using a valid refresh token |
auth.jwt.sign(payload, expiry?, options?)Signs a new JWT token. Can automatically store it in memory/Redis and set an HTTP-only cookie for browser clients.
const token = await auth.jwt.sign(
{ id: 'user_123', role: 'admin' },
'2h', // expiry time
{ res } // Express res object to auto-set cookie
);
| Name | Type | Default | Description |
|---|---|---|---|
payload | object | — | Data to include in the JWT |
expiry | string | number | '1h' | Expiration time (5m, 2h, 1d, etc.) |
options.res | Response | — | Express response object (sets cookie automatically) |
options.secure | boolean | true | If false, cookie is not secure (for localhost testing) |
Promise<string> → The generated JWT token.
auth.jwt.verify(input)Verifies and decodes a token.
You can pass either a raw JWT token string or an Express request object.
When passing req, the library automatically extracts the token from:
Authorization header (Bearer <token>)auth_token by default)// 1️⃣ Verify a token string
const decoded = await auth.jwt.verify(token);
// 2️⃣ Verify directly from a request
const decoded = await auth.jwt.verify(req);
Promise<object> → Decoded payload if valid, throws error if invalid or revoked.
auth.jwt.decode(token)Decodes a token without verifying the signature (useful for inspection or debugging).
const data = await auth.jwt.decode(token);
Returns: Decoded object or null.
auth.jwt.issue(user)Issues a new access token and a refresh token for a user.
const { accessToken, refreshToken } = auth.jwt.issue({ id: 'user_123' });
Parameters:
| Name | Type | Description |
|---|---|---|
| user | object | User object with id property |
| Returns: |
{ accessToken: string, refreshToken: string }
auth.jwt.refresh(refreshToken)Refreshes an access token using a valid refresh token.
const newTokens = auth.jwt.refresh(refreshToken);
Parameters:
| Name | Type | Description |
|---|---|---|
| refreshToken | string | A valid refresh token string |
| Returns: |
{ accessToken: string, refreshToken: string }
auth.jwt.revoke(token, revokeTime?)Revokes a token immediately or after a specified duration.
await auth.jwt.revoke(token); // revoke now
await auth.jwt.revoke(token, '5m'); // revoke after 5 minutes
If using:
memory: the token is removed from internal store.redis: the key is deleted or set to expire.auth.jwt.isRevoked(token)Checks if a token is revoked (missing in memory or Redis).
const revoked = await auth.jwt.isRevoked(token);
if (revoked) console.log('Token is no longer valid.');
Returns: boolean
auth.jwt.protect(options)Express middleware for protecting routes that require authentication.
app.get('/dashboard', auth.jwt.protect(), (req, res) => {
res.json({ user: req.user });
});
Or with extra security options:
app.get('/admin',
auth.jwt.protect({
requiredRole: 'admin',
attachProperty: 'sessionUser',
onError: (err, req, res) => res.status(403).json({ error: err.message })
}),
(req, res) => {
res.json({ message: `Welcome ${req.sessionUser.id}` });
});
| Name | Type | Default | Description |
|---|---|---|---|
attachProperty | string | 'user' | Where decoded data is attached on the request |
requiredRole | string | null | Restricts route access by user role |
cookieName | string | Inherited from AuthVerify | Cookie name to extract token |
headerName | string | 'authorization' | Header name to look for JWT |
extractor | function | — | Custom token extraction logic |
onError | function | — | Custom error handler (err, req, res) |
auth.jwt.readCookie(req, name)Manually extract a JWT from a cookie:
const token = auth.jwt.readCookie(req, 'auth_token');
Returns: string | null
const express = require('express');
const AuthVerify = require('auth-verify');
const auth = new AuthVerify({
jwtSecret: 'supersecret',
storeTokens: 'memory',
});
const app = express();
app.use(express.json());
// Login: issue JWT
app.post('/login', async (req, res) => {
const token = await auth.jwt.sign({ id: 'u1', role: 'admin' }, '2h', { res });
res.json({ token });
});
// Protected route
app.get('/me', auth.jwt.protect(), (req, res) => {
res.json({ message: `Welcome, ${req.user.id}` });
});
// Logout: revoke token
app.post('/logout', async (req, res) => {
const token = auth.jwt.readCookie(req, 'auth_token');
await auth.jwt.revoke(token);
res.json({ success: true, message: 'Logged out' });
});
app.listen(3000, () => console.log('✅ Auth server running on port 3000'));
cookie-parser or built-in cookie reader.auth-verifyThe OTPManager handles one-time passwords (OTP) for multi-channel authentication:
const AuthVerify = require("auth-verify");
const auth = new AuthVerify();
// Access OTP manager
const otp = auth.otp; // internally uses OTPManager
| Option | Type | Default | Description |
|---|---|---|---|
otpExpiry | string/number | 300 | OTP expiration time, e.g., "5m" or "30s" |
storeTokens | string | "memory" | Storage type: "memory", "redis", or "none" |
otpHash | string | "sha256" | Hashing algorithm for OTP (optional) |
sender | object | null | Email/SMS/WhatsApp/Telegram sender configuration |
redisUrl | string | "redis://localhost:6379" | Redis connection URL (if storeTokens = 'redis') |
otp.sender({
via: "email", // or "sms", "telegram", "whatsapp"
service: "gmail", // email service
sender: "your_email@gmail.com",
pass: "your_app_password",
host: "smtp.gmail.com",
port: 587,
secure: false
});
// or you can use otp.setSender({...});
otp.sender({
via: "email", // or "sms", "telegram", "whatsapp"
sender: "your_email@gmail.com",
pass: "your_app_password",
host: "smtp.gmail.com",
port: 587,
secure: false
})
otp.sender({
via: "email",
service: "api",
sender: "your_email@gmail.com",
apiService: "resend", // "mailgun", "sendgrid"
apiKey: "your-api-key"
})
For
"mailgun"you should also add also your domaindomain: "your-domain.com"
Auth-Verify supports sending OTPs via SMS using 30+ global SMS providers.
Before sending SMS, you must configure your sender:
otp.sender({
via: "sms", // REQUIRED — "sms" or "email" or "telegram"
provider: "twilio", // REQUIRED — choose provider
apiKey: process.env.TWILIO_SID, // varies per provider
apiSecret: process.env.TWILIO_TOKEN, // varies per provider
sender: "+15005550006", // sender phone or ID
mock: false // optional: true = no real SMS sent
});
const smsResponse = await otp.send("+998901234567");
console.log(smsResponse);
{
"status": "SENT",
"provider": "Twilio",
"to": "+998901234567"
}
otp.sender({
via: "sms",
provider: "twilio",
mock: true
});
Console output:
📱 [Mock SMS via twilio]
→ To: +998901234567
→ Message: Your OTP is 123456
| Provider | Config Example | Notes |
|---|---|---|
| Twilio | apiKey = SID, apiSecret = TOKEN, sender = +15005550006 | Global |
| Vonage / Nexmo | apiKey, apiSecret, sender | Global |
| Infobip | apiKey, sender | Global / EU strong |
| Eskiz | email, password, sender | Uzbekistan |
| PlayMobile | apiKey, apiSecret, sender | Uzbekistan |
| MSG91 | apiKey, templateId = apiSecret | India |
| Telesign | apiKey, apiSecret | Global |
| SMS.ru | apiKey | Russia |
| TextLocal | apiKey, sender | India/UK |
| ClickSend | apiKey, apiSecret, sender | Global |
| Sinch | apiKey, apiSecret | Global |
| Telnyx | apiKey, sender | Global |
| NetGSM | apiKey, apiSecret, sender | Turkey |
| KaveNegar | apiKey | Iran |
| Unifonic | apiKey, sender | Saudi Arabia |
| Alibaba Cloud SMS | apiKey, template_id, region, sender | China |
| Firebase | client SDK only | OTP must be sent client-side |
| CheapGlobalsms | apiKey, apiSecret, sender | Global |
| Africa's Talking | apiKey, sender | Africa |
| MessageBird | apiKey, sender | Global |
| SMSAPI | apiKey, sender | Europe |
| Clickatell | apiKey, sender | Global |
| Plivo | apiKey, apiSecret, sender | Global |
| Vibes | apiKey | US |
| SMS Gateway Hub | apiKey, sender | India |
| TextMagic | apiKey, apiSecret, sender | Global |
All providers use the same
sendSMShelper, just change provider and API credentials. How to use this table:// Using eskiz provider (for example) otp.sender({ provider: 'eskiz', // (see Provider column) email: "YOUR_EMAIL_HERE", // (see Config Example column) password: "YOUR_PASSWORD_HERE", // (see Config Example column) sender: "YOUR_SENDER_NAME_HERE" // (see Config Example column) });
try {
const result = await auth.otp.send("+998901234567");
} catch (err) {
console.error("SMS failed:", err.message);
}
otp.sender({
via: 'telegram',
token: '123:ABC', // bot token
// call auth.otp.setupTelegramBot(token) to start the bot
});
otp.sender({
via: 'whatsapp',
phoneId: "YOUR_PHONE_ID",
token: "YOUR_WHATSAPP_TOKEN"
});
otp.generate(6); // 6-digit OTP
otp.generate(6, (err, code) => {
console.log(code);
});
otp.generate(6).set("user@example.com");
await otp.set("user@example.com");
await otp.send("user@example.com", {
subject: "Your OTP Code",
text: "Your OTP is 123456",
html: "<b>123456</b>"
});
// or simply
await otp.send("user@example.com");
Supports channels:
via | Notes |
|---|---|
| Configured with Gmail/SMTP | |
| sms | Uses sendSMS helper or custom provider |
| telegram | Sends OTP via bot, requires botToken |
| Uses WhatsApp Business API |
Callback style:
otp.send("user@example.com", options, (err, info) => {
if(err) console.error(err);
else console.log(info);
});
// Promise style
const result = await otp.verify("user@example.com", "123456");
//const result = await otp.verify({ check: "user@example.com", code: "123456" }); or you can use this style
// Callback style
otp.verify("user@example.com", "123456", (err, success) => {
if(err) console.error(err.message);
else console.log("✅ OTP verified");
});
OTP expiredInvalid OTPMax attempts reachedotp.cooldown("30s"); // cooldown before OTP can be resent
"30s", "2m", "1h", or millisecondsresend() methodconst code = await otp.resend("user@example.com");
otp.resend("user@example.com", (err, code) => {
if(err) console.error(err.message);
else console.log("Resent OTP:", code);
});
await otp.setupTelegramBot("YOUR_BOT_TOKEN");
#sendEmail(reciever, options) → Sends email OTP#sendSMS(reciever, options) → Sends SMS OTP#sendWhatsApp(reciever, options) → Sends WhatsApp OTPUsually not called directly — use
otp.send()instead.
const AuthVerify = require("auth-verify");
const auth = new AuthVerify({ otpExpiry: "5m", storeTokens: "memory" });
// Set sender
auth.otp.setSender({
via: "email",
service: "gmail",
sender: process.env.EMAIL,
pass: process.env.EMAIL_PASS
});
// Generate and send OTP
await auth.otp.send("user@example.com", { subject: "Verify your account" });
// Verify OTP
try {
await auth.otp.verify({ check: "user@example.com", code: "123456" });
console.log("✅ OTP verified!");
} catch (err) {
console.error(err.message);
}
// Resend OTP if needed
const newCode = await auth.otp.resend("user@example.com");
console.log("Resent OTP:", newCode);
// GET /api/totp/secret
app.get("/api/totp/secret", (req, res) => {
const secret = auth.totp.secret(); // Base32 secret
res.json({ success: true, secret });
});
const response = await fetch("http://localhost:3000/api/totp/secret");
const data = await response.json();
console.log(data.secret); // "JBSWY3DPEHPK3PXP"
const auth = new AuthVerify({ apiBase: "http://localhost:3000" });
const res = auth.get("/api/secret/totp/secret").data();
console.log(res.secret); // "JBSWY3DPEHPK3PXP"
secret, label (user email), issuer (app name), algorithm, digits, and period.// POST /api/totp/uri
app.post("/api/totp/uri", (req, res) => {
const { secret, label, issuer } = req.body;
const uri = auth.totp.uri({ secret, label, issuer });
res.json({ success: true, uri });
});
const { uri } = await fetch("http://localhost:3000/api/totp/uri", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
secret: "JBSWY3DPEHPK3PXP",
label: "user@example.com",
issuer: "MyApp"
})
}).then(r => r.json());
console.log(uri);
// otpauth://totp/MyApp:user@example.com?secret=...
const auth = new AuthVerify({ apiBase: "http://localhost:3000" });
const uri = auth.post("/api/totp/uri").data({secret: "JBSWY3DPEHPK3PXP", label: "user@example.com", issuer: "MyApp"});
console.log(uri); // otpauth://totp/MyApp:user@example.com?secret=...
// POST /api/totp/qrcode
app.post("/api/totp/qrcode", async (req, res) => {
const { uri } = req.body;
const qr = await auth.totp.qr(uri); // returns base64 data URL
res.json({ success: true, qr });
});
const { qr } = await fetch("http://localhost:3000/api/totp/qrcode", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ uri })
}).then(r => r.json());
document.getElementById("totp-qrcode").src = qr;
const auth = new AuthVerify({
apiBase: "http://localhost:3000",
qrEl: document.getElementById("totp-qrcode")
});
auth.get("/api/totp/qrcode").qr();
window allows ±1 or more steps to account for clock skew.// POST /api/totp/verify
app.post("/api/totp/verify", (req, res) => {
const { secret, code, window } = req.body;
const valid = auth.totp.verify(secret, code, window);
res.json({ success: true, valid });
});
const userCode = prompt("Enter your TOTP code:");
const { valid } = await fetch("http://localhost:3000/api/totp/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
secret: "JBSWY3DPEHPK3PXP",
code: userCode
})
}).then(r => r.json());
if(valid) alert("✅ Verified!");
else alert("❌ Invalid TOTP code");
const auth = new AuthVerify({ apiBase: "http://localhost:3000" });
const valid = auth.post("/api/totp/verify").verify(userCode);
if(valid) alert("✅ Verified!");
else alert("❌ Invalid TOTP code");
| Endpoint | Method | Description | Payload | Response |
|---|---|---|---|---|
/api/totp/secret | GET | Generate a Base32 secret | None | { success: true, secret: "..." } |
/api/totp/uri | POST | Convert secret to otpauth URI | { secret, label, issuer } | { success: true, uri: "..." } |
/api/totp/qrcode | POST | Generate QR code from URI | { uri } | { success: true, qr: "data:image/png;base64,..." } |
/api/totp/verify | POST | Verify user TOTP code | { secret, code, window? } | { success: true, valid: true/false } |
This guide explains how to integrate Passkey (WebAuthn) authentication using the AuthVerify ecosystem — including both:
PasskeyManager (Node.js)window.AuthVerify wrapper (Browser)Import and configure AuthVerify:
const express = require("express");
const AuthVerify = require("auth-verify");
const app = express();
app.use(express.json());
const auth = new AuthVerify({
rpName: "AuthVerifyApp", // Display name in browser prompt
storeTokens: "memory", // or "redis"
passExp: "2m", // Challenge expiration
});
POST /api/register/startGenerate registration challenge for a new user.
app.post("/api/register/start", async (req, res) => {
const user = req.body.user; // e.g. { id: "u123", username: "john_doe" }
await auth.passkey.register(user);
res.json(auth.passkey.getOptions());
});
auth.issue()can be used for saving passkey in any device
POST /api/register/finishVerify attestation and save credential.
app.post("/api/register/finish", async (req, res) => {
const result = await auth.passkey.finish(req.body);
res.json(result);
});
Example successful response:
{
"status": "ok",
"user": {
"id": "u123",
"username": "john_doe",
"credentials": [
{
"id": "AaBbCcDdEe...",
"publicKey": "-----BEGIN PUBLIC KEY-----...",
"signCount": 0
}
]
},
"credentialId": "AaBbCcDdEe..."
}
POST /api/login/startGenerate login challenge for existing user.
app.post("/api/login/start", async (req, res) => {
const user = req.body.user; // same user object used at registration
await auth.passkey.login(user);
res.json(auth.passkey.getOptions());
});
POST /api/login/finishVerify user assertion (digital signature).
app.post("/api/login/finish", async (req, res) => {
const result = await auth.passkey.finish(req.body);
res.json(result);
});
Successful login:
{
"status": "ok",
"user": { "id": "u123", "username": "john_doe" }
}
Include your frontend wrapper (already built as window.AuthVerify):
<script src="https://cdn.jsdelivr.net/gh/jahongir2007/auth-verify/auth-verify.client.js"></script>
<script>
const auth = new AuthVerify({ apiBase: "http://localhost:3000" });
</script>
.post(url) / .get(url)Set endpoint for POST/GET requests before calling .data().
.data(payload)Send JSON to backend and return response.
/api/register/startSends { user } and gets WebAuthn challenge/options.
Calls navigator.credentials.create({ publicKey })
Prompts user for biometric or security key registration.
/api/register/finishSends credential data (clientDataJSON, attestationObject, etc.)
Validates and stores public key in user credentials.
/api/login/startSends { user, login: true } to get challenge and allowCredentials.
Calls navigator.credentials.get({ publicKey }).
/api/login/finishSends credential signature data (authenticatorData, signature, etc.)
Verifies signature using stored public key.
| Layer | Method | Description |
|---|---|---|
| Backend | passkey.register(user) | Start registration |
| Backend | passkey.getOptions() | Return challenge for browser |
| Backend | passkey.finish(clientResponse) | Finish registration/login |
navigator.credentials requires secure origin)user.id (string, not numeric)passExp)JWTManager for session generation after successful loginThis client is designed to interact with the backend AuthVerify API for passkeys / WebAuthn credentials. It does not require body-parser or any server-side logic on the frontend.
<!-- ✅ Import auth-verify client -->
<script src="https://cdn.jsdelivr.net/gh/jahongir2007/auth-verify/auth-verify.client.js"></script>
const auth = new AuthVerify({
apiBase: "http://localhost:3000",
qrEl: document.getElementById("qr") // optional, for QR codes
});
| Option | Type | Default | Description |
|---|---|---|---|
apiBase | string | 'http://localhost:3000' | Backend API base URL |
qrEl | HTMLImageElement | null | Optional element to display QR codes |
post(url)Sets the POST endpoint for the next request.
auth.post("/start-passkey");
get(url)Sets the GET endpoint for the next request.
auth.get("/fetch-qr");
qr()Fetches a QR code from the backend and renders it in qrEl.
await auth.get("/fetch-qr").qr();
qrEl exists, its src will be set to the QR image returned by the backend.data(payload)Sends a POST request with JSON payload to the set endpoint.
const response = await auth.post("/verify-otp").data({ code: "123456" });
console.log(response);
header()Returns an authorization header if jwt exists.
const headers = auth.header();
// { Authorization: 'Bearer <token>' }
verify(code)Shortcut for sending OTP / code to backend.
const result = await auth.verify("123456");
base64urlToUint8Array(base64url)Helper to decode Base64URL strings (used for WebAuthn challenges).
const arr = auth.base64urlToUint8Array("BASE64URL_STRING");
issue(publicKey)Creates a WebAuthn credential on the client (passkey).
// 1️⃣ Get registration options from backend
const publicKey = await auth.post("/start-passkey").data({ user: { id: "user123", name: "Alice" } });
// 2️⃣ Issue credential in browser
const credentialData = await auth.issue(publicKey);
// 3️⃣ Send credential back to backend
const result = await auth.post("/finish-passkey").data(credentialData);
console.log(result);
| Step | Description |
|---|---|
| 1 | Fetch publicKey options from backend |
| 2 | Decode challenge & user ID, create credential with navigator.credentials.create() |
| 3 | Convert ArrayBuffers to Base64 and return structured object |
| 4 | Send credential to backend via post() |
{
id: "...",
rawId: "...",
type: "public-key",
response: {
clientDataJSON: "...",
attestationObject: "..."
}
}
(async () => {
const auth = new AuthVerify({ apiBase: "http://localhost:3000", qrEl: document.getElementById("qr") });
// Display QR from backend
await auth.get("/fetch-qr").qr();
// Create a passkey
const publicKey = await auth.post("/start-passkey").data({ user: { id: "user123", name: "Alice" } });
const credential = await auth.issue(publicKey);
// Send back to backend
const result = await auth.post("/finish-passkey").data(credential);
console.log(result);
})();
<img id="qrImage" />
<div id="response"></div>
<button id="getQRBtn">Get QR</button>
<button id="sendBtn">Send Data</button>
<script src="https://cdn.jsdelivr.net/gh/jahongir2007/auth-verify/auth-verify.client.js"></script>
<script>
const qrImage = document.getElementById('qrImage');
const responseDiv = document.getElementById('response');
const auth = new AuthVerify({ apiBase: 'http://localhost:3000', qrEl: qrImage });
document.getElementById('getQRBtn').addEventListener('click', () => auth.get('/api/qr').qr());
document.getElementById('sendBtn').addEventListener('click', async () => {
const payload = { name: 'Jahongir' };
const result = await auth.post('/api/sign-jwt').data(payload);
responseDiv.textContent = JSON.stringify(result, null, 2);
});
</script>
auth.get('/api/qr').qr() after page loadsauth.header() for any authenticated request/api/qr, /api/verify-totp, /api/sign-jwtchallenge, user, allowCredentials) in Base64URL format.user.id and challenge must be Base64URL encoded on backend.auth-verifyThe OAuthManager in auth-verify provides an easy and unified way to integrate popular social logins such as Google, GitHub, Facebook, Twitter (X), LinkedIn, and others.
Each provider offers two main methods:
redirect(res) → Redirects users to provider’s authorization pagecallback(code) → Exchanges authorization code for access token and retrieves user dataconst AuthVerify = require("auth-verify");
const auth = new AuthVerify();
Then access:
auth.oauth
| Option | Type | Default | Description |
|---|---|---|---|
providers | object | {} | Register custom OAuth providers dynamically. |
The following providers are built-in and ready to use:
| Provider | Method | OAuth Version |
|---|---|---|
auth.oauth.google() | v2 | |
auth.oauth.facebook() | v2 | |
| GitHub | auth.oauth.github() | v2 |
| Twitter (X) | auth.oauth.x() | v2 |
auth.oauth.linkedin() | v2 | |
| Apple | auth.oauth.apple() | v2 |
| Discord | auth.oauth.discord() | v2 |
| Slack | auth.oauth.slack() | v2 |
| Microsoft | auth.oauth.microsoft() | v2 |
| Telegram | auth.oauth.telegram() | Deep Link |
auth.oauth.whatsapp() | Deep Link | |
auth.oauth.reddit() | v2 | |
| Yandex | auth.oauth.yandex() | v2 |
| Tumblr | auth.oauth.tumblr() | v2 |
| Mail.ru | auth.oauth.mailru() | v2 |
| VK | auth.oauth.vk() | v2 |
| Yahoo | auth.oauth.yahoo() | v2 |
app.get("/auth/google", (req, res) => {
auth.oauth.google({
clientId: process.env.GOOGLE_CLIENT_ID,
redirectUri: "http://localhost:3000/auth/google/callback"
}).redirect(res);
});
app.get("/auth/google/callback", async (req, res) => {
try {
const data = await auth.oauth.google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
redirectUri: "http://localhost:3000/auth/google/callback"
}).callback(req.query.code);
res.json({ success: true, user: data });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
Each provider returns { redirect, callback }.
Below are provider-specific examples and scopes.
Scopes: openid email profile
auth.oauth.google({
clientId,
clientSecret,
redirectUri
});
Returns user:
{
"email": "user@gmail.com",
"name": "John Doe",
"picture": "https://...",
"access_token": "ya29.a0..."
}
Scopes: email, public_profile
auth.oauth.facebook({ clientId, clientSecret, redirectUri });
Returns user:
{
"id": "123456789",
"name": "John Doe",
"email": "john@example.com",
"picture": { "data": { "url": "https://..." } },
"access_token": "EAAJ..."
}
Scopes: user:email
auth.oauth.github({ clientId, clientSecret, redirectUri });
Returns user:
{
"login": "johndoe",
"id": 123456,
"email": "john@example.com",
"avatar_url": "https://github.com/images/...",
"access_token": "gho_..."
}
Scopes: tweet.read users.read offline.access
auth.oauth.x({ clientId, clientSecret, redirectUri });
Returns user:
{
"data": { "id": "12345", "name": "John Doe", "username": "johndoe" },
"access_token": "2.abc..."
}
Scopes: r_liteprofile r_emailaddress
auth.oauth.linkedin({ clientId, clientSecret, redirectUri });
Returns user:
{
"id": "A1B2C3D4",
"name": "John Doe",
"email": "john@example.com",
"access_token": "AQW..."
}
Scopes: name email
auth.oauth.apple({ clientId, clientSecret, redirectUri });
Returns:
{
"access_token": "eyJraWQiOi...",
"id_token": "...",
"refresh_token": "...",
"expires_in": 3600
}
Scopes: identify email
auth.oauth.discord({ clientId, clientSecret, redirectUri });
Returns:
{
"id": "123456789",
"username": "john",
"email": "john@example.com",
"access_token": "abc123..."
}
Scopes: identity.basic identity.email
auth.oauth.slack({ clientId, clientSecret, redirectUri });
Returns:
{
"ok": true,
"access_token": "xoxp-...",
"authed_user": { "id": "U1234", "scope": "identity.basic,identity.email" }
}
Scopes:* User.Read
auth.oauth.microsoft({ clientId, clientSecret, redirectUri });
Returns token:
{
"token_type": "Bearer",
"expires_in": 3599,
"access_token": "EwB4A8l6..."
}
auth.oauth.telegram({ botId: "YourBotName", redirectUri });
Note: Telegram handles authentication through deep links. Returns:
{ "message": "Telegram login uses deep link auth" }
auth.oauth.whatsapp({ phoneNumberId: "1234567890", redirectUri });
Note: Usually handled via QR code or direct chat. Returns:
{ "message": "WhatsApp login uses QR/deep link auth" }
Scopes: identity
auth.oauth.reddit({ clientId, clientSecret, redirectUri });
Returns user:
{
"name": "johndoe",
"id": "t2_123abc",
"icon_img": "https://styles.redditmedia.com/...",
"access_token": "abc123..."
}
Scopes: login:email login:info
auth.oauth.yandex({ clientId, clientSecret, redirectUri });
Returns user:
{
"id": "1234567",
"display_name": "John Doe",
"emails": ["john@yandex.ru"],
"default_email": "john@yandex.ru",
"access_token": "y0_AgAAA..."
}
Scopes: basic write offline_access
auth.oauth.tumblr({ clientId, clientSecret, redirectUri });
Returns user:
{
"name": "johndoe",
"blogs": [{ "name": "myblog", "title": "My Tumblr Blog" }],
"access_token": "xyz..."
}
Scopes: userinfo.email
auth.oauth.mailru({ clientId, clientSecret, redirectUri });
Returns user:
{
"id": "123456",
"email": "user@mail.ru",
"name": "John Doe",
"access_token": "abc123..."
}
Scopes: email
auth.oauth.vk({ clientId, clientSecret, redirectUri });
Returns user:
{
"id": 987654,
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"access_token": "vk1.a.abc..."
}
Scopes: openid profile email
auth.oauth.yahoo({ clientId, clientSecret, redirectUri });
Returns user:
{
"sub": "12345",
"email": "john@yahoo.com",
"name": "John Doe",
"access_token": "y0_AgA..."
}
You can register your own provider logic:
auth.oauth.register("custom", ({ clientId, clientSecret, redirectUri }) => ({
redirect(res) {
res.redirect("https://custom-oauth.com/auth");
},
async callback(code) {
return { code, message: "Custom provider handled!" };
},
}));
// Usage:
auth.oauth.use("custom", { clientId, clientSecret, redirectUri });
Each callback() method may throw:
OAuth Error: invalid_clientOAuth Error: invalid_grantOAuth Error: unauthorized_clientOAuth Error: access_denied
Always wrap in try/catch:try {
const user = await auth.oauth.google(...).callback(code);
} catch (err) {
console.error("Login failed:", err.message);
}
redirect(res) is server-side only (Node.js Express compatible).auth-verify unified wrapper.The Magic Link Manager allows developers to implement secure, passwordless login using email-based links. Built directly into the AuthVerify SDK, it supports Gmail, custom SMTP, and token storage via Memory or Redis.
const AuthVerify = require('auth-verify');
const auth = new AuthVerify({
mlSecret: 'super_secret_key',
mlExpiry: '5m',
appUrl: 'http://localhost:3000',
storeTokens: 'memory'
});
Before sending links, you must set up your email transport.
await auth.magic.sender({
service: 'gmail',
sender: 'yourapp@gmail.com',
pass: 'your_gmail_app_password'
});
await auth.magic.sender({
host: 'smtp.mailgun.org',
port: 587,
secure: false,
sender: 'noreply@yourdomain.com',
pass: 'your_smtp_password'
});
✅ Both Gmail and any SMTP provider are supported. Use app passwords or tokens instead of your real password!
Send a secure, expiring link to the user’s email:
await auth.magic.send('user@example.com', {
subject: 'Your Secure Login Link ✨',
html: `<p>Click below to sign in:</p>
<a href="{{link}}">Login Now</a>`
});
The
{{link}}placeholder will automatically be replaced with the generated magic link.
If you just want to create a link (not send it yet):
const token = await auth.magic.generate('user@example.com');
console.log(token);
Then make your own URL:
const link = `http://localhost:3000/auth/verify?token=${token}`;
Typically used in your backend /auth/verify route:
app.get('/auth/verify', async (req, res) => {
const { token } = req.query;
try {
const user = await auth.magic.verify(token);
res.json({ success: true, user });
} catch (err) {
res.status(400).json({ success: false, message: err.message });
}
});
auth.magic.generate() → creates a short-lived JWT with the user’s email.auth.magic.send() → sends a secure login link by email.auth.magic.verify() → decodes & validates the token, optionally checks store.| Mode | Description | Best For |
|---|---|---|
memory (default) | Uses in-memory Map() | Single server / small projects |
redis | Uses Redis with TTL | Scalable, multi-server apps |
Example using Redis:
const auth = new AuthVerify({
storeTokens: 'redis',
redisUrl: 'redis://localhost:6379'
});
You can also use Node-style callbacks if you prefer:
auth.magic.send('user@example.com', (err) => {
if (err) console.error('❌ Failed to send link:', err);
else console.log('✅ Magic link sent!');
});
const express = require('express');
const bodyParser = require('body-parser');
const { AuthVerify } = require('auth-verify');
const app = express();
app.use(bodyParser.json());
const auth = new AuthVerify({
mlSecret: 'supersecretkey',
appUrl: 'http://localhost:3000',
storeTokens: 'memory'
});
auth.magic.sender({
service: 'gmail',
sender: 'yourapp@gmail.com',
pass: 'your_app_password'
});
// Send link
app.post('/auth/send', async (req, res) => {
const { email } = req.body;
await auth.magic.send(email);
res.json({ message: 'Magic link sent!' });
});
// Verify link
app.get('/auth/verify', async (req, res) => {
try {
const user = await auth.magic.verify(req.query.token);
res.json({ message: 'Login successful!', user });
} catch (err) {
res.status(400).json({ message: err.message });
}
});
app.listen(3000, () => console.log('🚀 Server running on port 3000'));
| Feature | Supported |
|---|---|
| Gmail & SMTP | ✅ |
| Memory & Redis Token Store | ✅ |
| Token Expiry | ✅ |
| Callback & Promise APIs | ✅ |
| HTML Custom Email | ✅ |
auth.magic is built for modern SaaS, fintech, and crypto apps that need passwordless, secure, and user-friendly authentication.
There are two ways to use Telegram flow:
Use the built-in senderConfig.via = 'telegram' and call auth.otp.setupTelegramBot(botToken) — this starts a polling bot that asks users to share their phone via /start, and then matches the phone to in-memory/Redis OTP records and replies with the code.
Developer-supplied custom sender (see below) — you can create your own bot and call it from auth.use(...).send(...) or register via auth.register.sender().
Important: Only one bot using long polling must be running per bot token — if you get 409 Conflict it's because another process or instance is already polling that bot token.
You can register custom senders and use them:
// register a named sender function
auth.register.sender('consoleOtp', async ({ to, code }) => {
console.log(`[DEV SEND] send to ${to}: ${code}`);
});
// use it later (chainable)
await auth.use('consoleOtp').send({ to: '+998901234567', code: await auth.otp.generate(5).code });
When a custom sender is registered, auth.otp.message() will first attempt the customSender before falling back to built-in providers.
auth-verifyThe session manager of auth-verify provides a simple way to create, verify, and destroy user sessions in either memory or Redis storage.
const AuthVerify = require('auth-verify');
const auth = new AuthVerify({ storeTokens: 'redis', redisUrl: "redis://localhost:6379" });
| Option | Type | Default | Description |
|---|---|---|---|
storeTokens | string | 'memory' | Storage type for sessions: 'memory' or 'redis' |
redisUrl | string | "redis://localhost:6379" | Redis connection URL (required if storeTokens: 'redis') |
create(userId, options)Create a new session for a user. Parameters:
| Name | Type | Required | Description | |
|---|---|---|---|---|
userId | string | ✅ | Unique ID of the user | |
options | object | ❌ | Optional settings: `{ expiresIn: number | string }` |
expiresIn formats:
"30s", "5m", "2h", "1d"Promise<string> → The session ID (UUID)
// Memory storage
const auth = new AuthVerify({ storeTokens: 'memory' });
const sessionId = await auth.session.create("user123", { expiresIn: "2h" });
console.log(sessionId); // "550e8400-e29b-41d4-a716-446655440000"
verify(sessionId)Verify if a session is valid.
| Name | Type | Required | Description |
|---|---|---|---|
sessionId | string | ✅ | The session ID to verify |
Promise<string> → Returns the userId if session is valid
"Session not found or expired""Session expired"const userId = await auth.session.verify(sessionId);
console.log(userId); // "user123"
destroy(sessionId)Invalidate (destroy) a session manually.
| Name | Type | Required | Description |
|---|---|---|---|
sessionId | string | ✅ | The session ID to destroy |
Promise<void>
await auth.session.destroy(sessionId);
console.log("Session destroyed");
expiresIn wisely — shorter times improve security but may require more frequent re-login.It supports both PBKDF2 and scrypt algorithms for password or data hashing.
The CryptoManager class helps developers easily hash and verify passwords or any sensitive data
using strong cryptographic functions. It supports both Promise and callback styles.
const AuthVerify = require('auth-verify');
// Default: pbkdf2 algorithm
const auth = new AuthVerify({
hashAlg: 'pbkdf2', // or 'scrypt'
iterations: 100000,
keyLen: 64
});
async hash(data, [callback])
Hashes a string (like a password) using the specified algorithm and salt. You can use Promise or callback style.
| Name | Type | Description |
|---|---|---|
data | string | The input data to hash (e.g. password) |
callback | function? | Optional Node-style callback (err, result) |
An object containing:
{
hashAlg: "pbkdf2" | "scrypt",
salt: "<random hex string>",
hash: "<hashed data in hex>"
}
const result = await auth.crypto.hash("myPassword123");
console.log(result);
// { hashAlg: 'pbkdf2', salt: '...', hash: '...' }
auth.crypto.hash("myPassword123", (err, result) => {
if (err) return console.error(err);
console.log(result);
});
async verify(data, { hashAlg, salt, hash }, [callback])
Verifies whether a given input matches a stored hash.
| Name | Type | Description |
|---|---|---|
data | string | The plaintext input (e.g. user password) |
{ hashAlg, salt, hash } | object | The hash object from .hash() |
callback | function? | Optional Node-style callback (err, isValid) |
A boolean value:
true → data matchesfalse → mismatchconst result = await cryptoManager.hash("secret123");
const isValid = await cryptoManager.verify("secret123", result);
console.log(isValid); // true
const original = await cryptoManager.hash("secret123");
cryptoManager.verify("secret123", original, (err, valid) => {
if (err) throw err;
console.log(valid); // true
});
| Method | Description | Returns | Async |
|---|---|---|---|
hash(data, [callback]) | Hashes input using PBKDF2 or scrypt | { hashAlg, salt, hash } | ✅ |
verify(data, { hashAlg, salt, hash }, [callback]) | Verifies input against hash | boolean | ✅ |
(async () => {
const manager = new CryptoManager({ hashAlg: 'scrypt' });
// Hash password
const hashData = await manager.hash('MyStrongPassword');
console.log('Stored hash:', hashData);
// Verify password
const match = await manager.verify('MyStrongPassword', hashData);
console.log('Password valid:', match);
})();
helpers/helper.js exposes utility functions used by managers:
generateSecureOTP(length, hashAlgorithm) — returns secure numeric OTP stringparseTime(strOrNumber) — converts '1h' | '30s' | number into millisecondsresendGeneratedOTP(params) — helper to send email via nodemailer (used by resend)sendSMS(params) — helper for sending SMS using supported providers or mockhost, port and secure in setSender().auth-verify/
├─ README.md
├─ package.json
├─ index.js // exports AuthVerify
├─ src/
│ ├─ jwt/
| | ├─ index.js
| | ├─ cookie/index.js
│ ├─ /otp/index.js
│ ├─ /magiclink/index.js
│ ├─ /crypto/index.js
│ ├─ totp/
| | ├─ index.js
| | ├─ base32.js
│ ├─ /session/index.js
| ├─ /oauth/index.js
│ └─ helpers/helper.js
├─ rest-api/
│ ├─ public/index.html
│ ├─ index.js
├─ babel.config.js
├─ auth-verify.client.js
Contributions welcome! Open issues / PRs for bugs, improvements, or API suggestions.
MIT © 2025 - 2026 — Jahongir Sobirov
FAQs
A simple Node.js library for sending and verifying OTP via email, SMS and Telegram bot. And generating TOTP codes and QR codes. And handling JWT with Cookies. And also handling passwordless logins with passkeys/webauth. And handling magiclink passwordless
We found that auth-verify demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.