Password Generator API: Secure Temp Passwords in Node

Generate temporary passwords with a password generator API, hash them with argon2, and check strength in Node.js. Copy-paste code inside.

  • 7 min read
  • Published March 4, 2026
  • Updated September 13, 2026
Article featured image

You need to hand an invited user a temporary password, and “Welcome123!” isn’t it. Here’s how to do it with a password generator API in Node.js: generate, hash, store, force a change on first login, and run a strength check on passwords your admins set by hand.

The minimal request

One GET request. count, length and complexity are the three parameters you’ll touch most:

curl "https://api.apiverve.com/v1/passwordgenerator?count=10&length=30&complexity=strong" \
  -H "x-api-key: YOUR_API_KEY"

Real response, trimmed to 2 of the 10 passwords:

{
  "status": "ok",
  "error": null,
  "data": {
    "passwords": [
      {
        "password": "aOwqDh0ZK*P;!M|%oiFYJ!IK%TMXQO",
        "length": 30,
        "metadata": { "type": "random", "complexity": "strong" },
        "analysis": null
      },
      {
        "password": "Wo:)*V2nMakoVI78[fKu|QDI.E45p|",
        "length": 30,
        "metadata": { "type": "random", "complexity": "strong" },
        "analysis": null
      }
    ],
    "count": 10
  }
}

count goes from 1 to 100, length from 4 to 128. complexity accepts simple, medium, strong or maximum. (analysis stays null unless you pass analyze=true, which is a premium option. metadata.complexity is premium too, so don’t write code that depends on it.)

And the Password Strength API, same auth:

curl "https://api.apiverve.com/v1/passwordstrength?password=Password123" \
  -H "x-api-key: YOUR_API_KEY"
{
  "status": "ok",
  "error": null,
  "data": {
    "strength": "weak",
    "score": 1,
    "passwordInfo": {
      "length": 11,
      "hasSpecialCharacters": false,
      "hasNumbers": true,
      "hasLowercase": true,
      "hasUppercase": true,
      "hasSpaces": false,
      "hasSequentialChars": true,
      "hasRepeatedChars": false
    },
    "recommendations": [
      "Use at least 12 characters",
      "Add special characters",
      "Avoid sequential characters (e.g. abc, 123)"
    ],
    "entropyBits": 65.5
  }
}

score runs 0 to 3, matching the labels too weak, weak, medium and strong. recommendations and entropyBits are premium fields, so treat them as optional.

Step 1: A generator helper

Node 18+ ships fetch, so no HTTP library needed. Two choices matter for a temporary password someone will actually type:

  • complexity=medium gives you upper, lower and digits, no symbols. Nobody wants to type |%; off a phone screen.
  • excludeSimilar=true drops i l 1 L o 0 O, the characters people misread.

Compensate for the smaller character set with length. Sixteen characters is a sane floor.

// passwords.js
const API = 'https://api.apiverve.com/v1';
const headers = { 'x-api-key': process.env.APIVERVE_API_KEY };

export async function generateTempPassword(length = 16) {
  const params = new URLSearchParams({
    count: '1',
    length: String(length),
    complexity: 'medium',
    excludeSimilar: 'true',
  });

  const res = await fetch(`${API}/passwordgenerator?${params}`, {
    headers,
    signal: AbortSignal.timeout(5000),
  });
  const body = await res.json().catch(() => null);

  if (!res.ok || body?.status !== 'ok') {
    throw new Error(`passwordgenerator failed: HTTP ${res.status} ${body?.error ?? ''}`);
  }
  return body.data.passwords[0].password;
}

Note what’s missing: any console.log(password). Keep it that way.

Step 2: An Express endpoint for invited users

An admin invites someone. You generate a password, hash it with argon2, store only the hash, flag the account so the first login forces a change, and show the plaintext to the admin exactly once.

npm install express argon2
// invite.js
import express from 'express';
import argon2 from 'argon2';
import { generateTempPassword } from './passwords.js';
import { db } from './db.js'; // your data layer

const app = express();
app.use(express.json());

app.post('/admin/invites', requireAdmin, async (req, res) => {
  const { email } = req.body;

  let tempPassword;
  try {
    tempPassword = await generateTempPassword(16);
  } catch (err) {
    console.error('invite: generator unavailable', err.message);
    tempPassword = localFallbackPassword(16);
  }

  const passwordHash = await argon2.hash(tempPassword); // argon2id by default

  await db.users.create({
    email,
    passwordHash,
    mustChangePassword: true,
    tempPasswordExpiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
  });

  res.set('Cache-Control', 'no-store');
  res.status(201).json({ email, tempPassword });
});

On login, verify with argon2.verify(user.passwordHash, submitted), then reject if tempPasswordExpiresAt has passed and redirect to a change-password screen while mustChangePassword is true. A temp password that quietly becomes permanent defeats the whole exercise.

Also check your request logging middleware. Plenty of setups log full response bodies at debug level, and this response contains a live credential.

Step 3: Fall back when the API is down

An invite flow shouldn’t die because a dependency blinked. Node’s crypto module has randomInt, which is backed by a CSPRNG and avoids modulo bias, so the fallback is short:

import { randomInt } from 'node:crypto';

// Mirrors medium + excludeSimilar
const CHARS = 'abcdefghjkmnpqrstuvwxyzABCDEFGHIJKMNPQRSTUVWXYZ23456789';

export function localFallbackPassword(length = 16) {
  return Array.from({ length }, () => CHARS[randomInt(CHARS.length)]).join('');
}

On the API side, follow the status code rather than guessing. A 401 means the key is wrong, so don’t retry. A 429 is either rate limiting or an empty credit balance. The error handling docs cover how to tell those apart, and there’s a longer write-up in API error handling patterns.

Step 4: Check admin-set passwords

Sometimes an admin types a password instead of generating one. That’s where the strength check is worth a request. URLSearchParams matters here: a password containing & or # breaks a hand-built query string.

export async function checkStrength(password) {
  const params = new URLSearchParams({ password });
  const res = await fetch(`${API}/passwordstrength?${params}`, {
    headers,
    signal: AbortSignal.timeout(5000),
  });
  const body = await res.json().catch(() => null);
  if (!res.ok || body?.status !== 'ok') {
    throw new Error(`passwordstrength failed: HTTP ${res.status}`);
  }

  const { score, strength, passwordInfo, recommendations } = body.data;
  const problems = [];
  if (passwordInfo.length < 12) problems.push('Use at least 12 characters');
  if (passwordInfo.hasSequentialChars) problems.push('No runs like abc or 123');
  if (passwordInfo.hasRepeatedChars) problems.push('No runs like aaa');

  const longEnough = passwordInfo.length >= 20;
  if (score < 3 && !longEnough) problems.push('Mix all four character types, or go 20+ characters');

  return {
    ok: problems.length === 0,
    strength,
    problems: problems.length ? problems : recommendations ?? [],
  };
}

Fair warning about the scale: the score counts character types (lowercase, uppercase, digits, symbols) with a length floor. It hits 3 only when all four types are present, so a 30-character letters-and-digits password still comes back weak, including the ones Step 1 produces. That’s why the gate above accepts either a score of 3 or 20+ characters, instead of trusting the label alone.

Where this belongs, and where it doesn’t

Be clear about what you’re sending. The strength check is a GET, so the password travels in the query string. TLS encrypts it in transit, but anything on your side that records outbound URLs (HTTP client debug logs, APM traces, proxies) can capture it. Scrub those, or don’t make the call.

Our recommendation:

  • Generated and admin-set passwords: fine to generate or check server-side. Temporary passwords for invites, initial passwords for provisioned accounts, credentials for test and staging users.
  • Passwords your end users type: plenty of security teams won’t allow those to reach any third party, and that’s a reasonable policy. Use a client-side strength library in the browser instead, so the password never leaves the device. Save the server round-trip for the cases above.
  • Stick with the default type=random for anything that guards access. passphrase and pronounceable are handy for readable test data, but a random string from a wide character set is the safer credential.
  • Length beats symbols. A 20-character letters-and-digits password has a larger search space than a 10-character one with every symbol on the keyboard. Once people stop typing passwords by hand, crank the length.
  • Never log a password. Not the temp one, not the hash input, not in a failed-request dump.

For the rest of the pre-launch list (rate limits, key rotation, secret handling), see the API security checklist.

Next steps

  • Generate credentials with the Password Generator API. The excludeAmbiguous flag strips quotes, brackets and slashes if a password has to survive a shell or config file.
  • Gate admin-set passwords with the Password Strength API.
  • New to APIVerve? The quickstart takes a few minutes.

Get a free API key and drop Step 1 into your invite flow today.

Give your software a way to act on the world.

Scaling in production?

The same key runs from your first prototype to millions of calls — on APIVerve's rails, backed by an uptime SLA.

See pricing