@callowayisweird/steam-auth
v0.1.0
Published
Zero-dependency, framework-agnostic Steam OpenID 2.0 authentication. Secure by default.
Downloads
113
Maintainers
Readme
@callowayisweird/steam-auth
Zero-dependency, framework-agnostic Steam OpenID 2.0 authentication. Secure by default.
Install
npm install @callowayisweird/steam-authQuick 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:
- Mode validation -- Ensures
openid.modeisid_res - Return URL verification -- Validates
openid.return_tomatches your configuredreturnUrlexactly. This is the check that passport-steam skipped, allowing attackers to forge authentication responses. - Endpoint validation -- Confirms
openid.op_endpointis the real Steam endpoint (https://steamcommunity.com/openid/login) - Claimed ID format check -- Validates
openid.claimed_idmatches the expected Steam URL pattern - Replay protection -- Tracks nonces to prevent replay attacks. Nonces are automatically cleaned up after 5 minutes.
- Server-side verification -- POSTs back to Steam's
check_authenticationendpoint 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
