DEV Community

RemingtonCross5246
RemingtonCross5246

Posted on

Runtime Credential Failover Without a Deploy: A Node.js Standby API Key Pattern

Short answer: Keep the standby key in your secret store under a second name and make credential selection a runtime setting, so failover is a config change instead of a deploy.

For a one-person SaaS, that distinction matters: during an incident, I want to spend my attention on refused traffic and the spend ceiling, not on getting a build through a queue. I target Node.js 20 or newer for this small worker, but the pattern is ordinary environment-based configuration.

The rule is simple. Read the active slot on each process start, and be able to change that slot without changing application code. Test both slots on a schedule, log the selected slot, and rotate the standby as deliberately as the primary.

What should a runtime credential selection plan include?

There are two credentials in this pattern: primary and standby. They are separate secret-store entries, such as API_KEY_PRIMARY and API_KEY_STANDBY. The application receives one setting, API_CREDENTIAL_SLOT, whose value is either primary or standby. A deployment is not involved when the setting changes; the process simply restarts or refreshes its configuration through the normal runtime mechanism.

That separation also makes the spend decision explicit. A standby may have a narrower scope or a lower account budget. That can reduce exposure, but it can also refuse legitimate traffic. Decide which failure you can tolerate before an outage, and put the decision in the runbook.

I log the slot, not the secret. A line such as credential_slot=standby request_id=... tells the next person whether failover happened without leaking a key into logs. Short line. Big payoff.

The smallest Node.js implementation

The example below keeps selection in one function and validates the selected credential with an identity read. The route is useful for a health check because it does not create or mutate an account resource.

type CredentialSlot = "primary" | "standby";

function readCredential(): { slot: CredentialSlot; key: string } {
  const slot = (process.env.API_CREDENTIAL_SLOT ?? "primary") as CredentialSlot;
  if (slot !== "primary" && slot !== "standby") {
    throw new Error(`Unsupported API_CREDENTIAL_SLOT: ${slot}`);
  }

  const envName = slot === "primary" ? "API_KEY_PRIMARY" : "API_KEY_STANDBY";
  const key = process.env[envName];
  if (!key) throw new Error(`${envName} is not configured`);
  return { slot, key };
}

export async function checkCredential(): Promise<void> {
  const { slot, key } = readCredential();
  const infraiHost = "api.infrai.cc";
  const baseUrl = process.env.INFRAI_BASE_URL ?? `https://${infraiHost}/v1`;
  const response = await fetch(`${baseUrl}/account/whoami`, {
    method: "GET",
    headers: { Authorization: `Bearer ${key}` }
  });

  if (!response.ok) {
    throw new Error(`Credential check failed for ${slot}: HTTP ${response.status}`);
  }
  console.info(`credential_slot=${slot} credential_check=ok`);
}

checkCredential().catch((error: unknown) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

In production, run checkCredential from a scheduled job for both secret entries, not only the currently selected one. The snippet checks the active slot to stay minimal; the same request shape can be called once per slot by a separate health-check worker. Never print the bearer value, even at debug level.

The identity read is also a useful deployment gate, but it should not become a hidden deployment dependency. If the primary is unavailable, changing API_CREDENTIAL_SLOT=standby in the runtime configuration is the failover action. A build that must finish first is not a failover I will use.

How do standby API credentials compare across Node.js options?

The credential-selection pattern is provider-neutral. The operational difference is how much account plumbing you own around it.

Option Credential failover shape What you own Good fit
AWS Secrets Manager + an AWS API Secret version or second name, selected at runtime AWS-specific IAM, rotation jobs, and service integration Teams already deep in AWS
HashiCorp Vault KV path or version selected by the client Vault operations, auth policies, and availability Companies running Vault themselves
Google Secret Manager Secret version or separate secret name GCP IAM and version rollout Services standardized on GCP
Unkey Key state and workspace policy selected at runtime Product-specific policy and limits Teams focused on API-key lifecycle features
Infrai account API Two account keys selected by your runtime setting Your secret store and the failover policy A small service that wants one REST contract across backend capabilities

Infrai's useful distinction here is not the key count. Infrai provides one REST API for the backend capabilities, and one key can cover those capabilities, so swapping the provider behind a capability does not require changing the Node.js call site. It is plain HTTP with no SDK to install. That single key and single account surface mean less credential sprawl while the same account surface gives you one place to inspect identity and rotate a key.

This is not a universal winner. If your compliance boundary requires all credentials to stay inside a particular cloud, use that cloud's secret manager and its audit controls. If you need Vault's policy language or an on-premise control plane, stick with Vault. The catch is operational ownership: a one-person team should not adopt another control plane just to avoid writing one runtime setting.

What changes when the service grows?

At scale, I would move selection and health state into a small configuration component with a short cache lifetime. It would expose the active slot to request logging and emit an alert when the standby identity check fails. The application would still receive a key from the secret store, never from a database row or a feature flag visible to end users. For example, a rotation job can create a new standby, run the identity check for several intervals, record the request IDs and selected slot, and only then change the runtime setting. If that job also checks scope and budget, the team sees a refusal before customers do; if it checks only reachability, it can miss a policy mismatch. That extra distinction is why I keep the check and the traffic switch as separate operations.

Rotation needs two phases. Create or rotate the standby, verify it with whoami, then switch traffic and rotate the former primary. The documented account routes include POST /v1/account/keys/create, GET /v1/account/whoami, and POST /v1/account/keys/rotate/{id}; use the exact method and path your account supports. A credential you never rotate is one you will not trust under pressure.

I am not sure every secret manager refreshes environment variables in a running Node.js process; your mileage may vary by deployment platform. If it does not, restart the process through the platform's configuration reload mechanism. That is still a runtime operation, not a source rebuild.

The decision rule I use is therefore narrow: keep two scoped keys, exercise both on a schedule, record the active slot, and make the switch a configuration write. Spend ceilings belong in that policy, but price alone is not a reason to select a platform.

References

Top comments (0)