JSPM

@callowayisweird/steam-auth

0.1.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 1
  • Score
    100M100P100Q36458F
  • License MIT

Zero-dependency, framework-agnostic Steam OpenID 2.0 authentication. Secure by default.

Package Exports

  • @callowayisweird/steam-auth

Readme

@callowayisweird/steam-auth

Zero-dependency, framework-agnostic Steam OpenID 2.0 authentication. Secure by default.

Install

npm install @callowayisweird/steam-auth

Quick Start

Hono

import { Hono } from "hono";
import { SteamAuth } from "@callowayisweird/steam-auth";

const steam = new SteamAuth({
  realm: "https://yoursite.com",
  returnUrl: "https://yoursite.com/auth/callback",
});

const app = new Hono();

app.get("/auth/login", (c) => {
  return c.redirect(steam.getRedirectUrl());
});

app.get("/auth/callback", async (c) => {
  const steamId = await steam.verify(new URL(c.req.url).searchParams);
  const profile = await steam.getProfile(steamId);
  return c.json(profile);
});

Express

import express from "express";
import { SteamAuth } from "@callowayisweird/steam-auth";

const steam = new SteamAuth({
  realm: "https://yoursite.com",
  returnUrl: "https://yoursite.com/auth/callback",
});

const app = express();

app.get("/auth/login", (req, res) => {
  res.redirect(steam.getRedirectUrl());
});

app.get("/auth/callback", async (req, res) => {
  const steamId = await steam.verify(req.query as Record<string, string>);
  const profile = await steam.getProfile(steamId);
  res.json(profile);
});

Fastify

import Fastify from "fastify";
import { SteamAuth } from "@callowayisweird/steam-auth";

const steam = new SteamAuth({
  realm: "https://yoursite.com",
  returnUrl: "https://yoursite.com/auth/callback",
});

const app = Fastify();

app.get("/auth/login", async (req, reply) => {
  return reply.redirect(steam.getRedirectUrl());
});

app.get("/auth/callback", async (req, reply) => {
  const steamId = await steam.verify(req.query as Record<string, string>);
  const profile = await steam.getProfile(steamId);
  return profile;
});

Security

This library fixes the passport-steam authentication bypass vulnerability and performs 6 security checks on every callback:

  1. Mode validation -- Ensures openid.mode is id_res
  2. Return URL verification -- Validates openid.return_to matches your configured returnUrl exactly. This is the check that passport-steam skipped, allowing attackers to forge authentication responses.
  3. Endpoint validation -- Confirms openid.op_endpoint is the real Steam endpoint (https://steamcommunity.com/openid/login)
  4. Claimed ID format check -- Validates openid.claimed_id matches the expected Steam URL pattern
  5. Replay protection -- Tracks nonces to prevent replay attacks. Nonces are automatically cleaned up after 5 minutes.
  6. Server-side verification -- POSTs back to Steam's check_authentication endpoint to confirm the assertion is genuine

API Reference

new SteamAuth(options)

Option Type Description
realm string Your site URL, e.g. "https://yoursite.com"
returnUrl string Callback URL, e.g. "https://yoursite.com/auth/callback"
fetch function Optional custom fetch implementation for testing

steam.getRedirectUrl(): string

Returns the Steam OpenID login URL. Redirect the user here.

steam.verify(query): Promise<string>

Verifies the OpenID callback and returns the user's SteamID64. Accepts either a Record<string, string> or URLSearchParams.

Throws one of the typed errors below on failure.

steam.getProfile(steamId): Promise<SteamProfile>

Fetches the user's public Steam profile (no API key required). Returns:

interface SteamProfile {
  steamId: string;
  name: string;
  avatarUrl: string;      // 64x64
  avatarMedium: string;   // 184x184
  avatarFull: string;     // Full size
  profileUrl: string;
  personaState: number;   // 0=offline, 1=online, etc.
}

steam.destroy(): void

Stops the nonce cleanup interval and clears stored nonces. Call this when shutting down.

Error Handling

All errors extend SteamAuthError and have a code property for programmatic handling:

import {
  SteamAuth,
  VerificationError,
  ReturnUrlMismatchError,
  SteamUnavailableError,
} from "@callowayisweird/steam-auth";

try {
  const steamId = await steam.verify(query);
} catch (err) {
  if (err instanceof ReturnUrlMismatchError) {
    // Possible attack -- return_to was tampered with
  } else if (err instanceof SteamUnavailableError) {
    // Steam is down, retry later
  } else if (err instanceof VerificationError) {
    // Verification failed
  }
}
Error Class Code Description
SteamAuthError (varies) Base class for all errors
VerificationError VERIFICATION_FAILED Steam's check_authentication returned invalid
ReturnUrlMismatchError RETURN_URL_MISMATCH return_to doesn't match configured returnUrl
InvalidClaimedIdError INVALID_CLAIMED_ID claimed_id doesn't match Steam format
InvalidEndpointError INVALID_ENDPOINT op_endpoint isn't the real Steam endpoint
ReplayAttackError REPLAY_ATTACK Nonce was already used
SteamUnavailableError STEAM_UNAVAILABLE Steam didn't respond or returned non-200
ProfileFetchError PROFILE_FETCH_FAILED Failed to fetch Steam profile

License

MIT