npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@xzawed/keycloak-sdk

v1.0.3

Published

Keycloak SDK for Node.js/TypeScript — OIDC/OAuth2 authentication + Admin REST API, part of a nine-language polyglot SDK

Readme

Keycloak SDK for Node.js

A TypeScript SDK for Keycloak covering both Authentication (OIDC / OAuth2) and the Admin REST API behind one consistent facade.

Part of a nine-language polyglot SDK (Java · Python · Node · Go · C# / .NET · PHP · Rust · Ruby · Kotlin) — idiomatic in each language, isomorphic across all of them. Monorepo: https://github.com/xzawed/KeyCloakSDK

1.0.3 is on npm and holds the latest dist-tag, so a bare install resolves it. A patch release: with expectedAudience set to something other than the client id (a resource server), exchangeCode(…, nonce) rejected a valid id_token issued by Keycloak as invalid id_token, because the access token's expected audience was demanded of the id_token too. The id_token's aud is now checked for the client id (OIDC Core §2, §3.1.3.7), so the exchange passes under the override, while an id_token whose aud lacks the client id is still refused even when it equals the override. validate still applies the override to access tokens, and the two checks share one JWKS cache and refetch limit. The declared public API (.d.ts) is unchanged.

1.0.2 was a security patch: exchangeCode(…, nonce) had not verified the id_token's signature in 1.0.0 and 1.0.1 — openid-client checks the nonce but not the signature of an id_token that comes straight from the token endpoint, so an id_token signed outside the realm JWKS (HS256, or a forged RS256) passed. Since then it goes through the SDK's hardened validator (signature · signatureAlgorithms · iss · aud · exp), as in the other eight languages, and a missing id_token is refused.

1.0.1 was a patch release of security and correctness fixes on top of 1.0.0, the first release carrying the stability guarantee (the public API is under SemVer, and a breaking change requires a major bump). The PKCE verifier and tokens no longer leak through console.log / JSON.stringify, and an SDK error no longer carries the IdP's raw token response in its cause chain (both present in 1.0.0); the configured clock skew now actually reaches validation; JWKS responses are size-capped, an empty 200 key set no longer replaces a good cached one, and failed fetches back off. ⚠️ Config is now validated when it is built — a serverUrl that is not an absolute http(s) URL, or a timeout that is not a finite number in (0, 2147483647] ms, throws KeycloakConfigError.

⚠️ Upgrading from 0.1.0? Two breaking changes since then, both at the type level only — the runtime behaviour is unchanged and the normal paths (kc.auth.validate(token), (await kc.admin()).users.search(...)) are untouched. (1) JwtValidator can no longer be built with new — use JwtValidator.forJwksUri(...). (2) The five admin resource classes no longer expose their constructors in the emitted declarations; AdminClient assembles them, and they were never a consumer construction path. Both existed because the constructors were putting jose and @keycloak/keycloak-admin-client types onto this package's public surface.

Requirements

  • Node.js 22 or newer (engines: { "node": ">=22" })
  • ESM-only ("type": "module") and async-only — every network method returns a Promise (createAuthorizationRequest is the one auth operation that is not; non-network calls such as KeycloakClient.create are synchronous too)
  • Ships .d.ts type declarations, so consumers get full type checking
  • A running Keycloak server (26.6 verified) to connect to

Install

npm install @xzawed/keycloak-sdk

A bare install resolves 1.0.3, and so does a ^1.0.0 range — at and above 1.0.0 a caret covers every 1.x, so it picks up later minor and patch releases but never a 2.0.0. Pin the exact version if you would rather not follow latest:

npm install @xzawed/[email protected]

Quickstart

The same three-step shape as every other language in the monorepo — get a token → verify it → call the Admin API.

import { KeycloakClient } from '@xzawed/keycloak-sdk'

const client = KeycloakClient.create({
  serverUrl: 'https://keycloak.example.com',
  realm: 'my-realm',
  clientId: 'my-client',
  clientSecret: process.env['KEYCLOAK_CLIENT_SECRET'], // load from env / a secret manager
})

try {
  // 1. Get a token via the client-credentials grant.
  //    TokenSet masks accessToken/refreshToken when logged or serialized.
  const token = await client.auth.clientCredentialsToken()

  // 2. Verify it with the hardened validator (algorithm pinning, exact iss, aud containment).
  const validated = await client.auth.validate(token.accessToken)
  console.log(`subject=${validated.subject} aud=${validated.audience.join(',')}`)

  // 3. Call the Admin API. `admin` is created lazily on first access (client-credentials grant).
  const admin = await client.admin()
  const users = await admin.users.search(undefined, 0, 10)
  console.log(`users=${users.map((u) => u.username).join(', ')}`)
} finally {
  await client.close() // close protocol; both halves are no-ops today (global `fetch` holds no connections)
}

validate() expects the token's aud to contain clientId by default, but a stock realm does not put the client id into a client-credentials token. Either pass expectedAudience: 'my-api' to create() to check the audience your tokens actually carry, or add an audience mapper to the client in Keycloak (Client scopes → dedicated scope → Add mapper → Audience).

KeycloakClient implements AsyncDisposable, so await using client = KeycloakClient.create(…) cleans up on scope exit.

For the browser/authorization-code flow, start with client.auth.createAuthorizationRequest(redirectUri) and exchange the callback with client.auth.exchangeCode(code, redirectUri, codeVerifier, nonce) — the nonce must be passed back for id_token validation to succeed.

Security defaults

Hardened JWT validation, not the unsafe library defaults:

  • Algorithm pinning — signature algorithms are pinned by configuration (RS256 by default); alg: none and header-supplied algorithms are rejected.
  • Strict claim checks — exact iss match, aud containment check, mandatory exp, and a bounded clock skew (30s by default).
  • DoS-safe JWKS refetch — the key set is refetched only on an unresolved key ID and never on a bad signature, and a cooldown rate-limits refetches to a minimum interval (jwksMinRefetchSeconds, 30s by default) — so once the key set has been fetched, no volume of forged tokens makes the SDK issue more than one JWKS request per interval. A failed fetch is bounded separately: consecutive failures back off exponentially (0.2s, doubling, capped at 5s, with jitter), and inside that window the SDK fails fast without contacting the IdP. So a cold cache during an IdP outage no longer turns every validation into a request (measured: 20 attempts → 1 request, down from 20). ⚠️ The SDK never sleeps — it returns the error immediately, so retry pacing stays the caller's decision.
  • Secret handling — clientSecret and tokens are masked as *** in toString, JSON.stringify, and util.inspect output; TLS verification is on by default.

Masking covers those three serialization paths, which is what most loggers reach for. It does not cover direct property access, so it is defence in depth rather than a guarantee about your logs.

Versioning and support

This SDK is 1.0 and follows SemVer: a breaking change to the public API requires a major bump. That promise is machine-backed — CI diffs this lane's public API against the previously published artifact on every build (api-extractor, whose report is diffed against the published tarball), and a removal or an incompatible change fails the build. ⚠️ This lane's gate has a known gap: it compares the emitted type surface as text, so adding a required field to an input interface passes it even though it breaks callers — that case is caught by review, not by machine. ⚠️ The gate compares the API surface. A change that leaves the surface identical but alters behaviour is not caught by it, so read the release notes before upgrading.

Only the newest released version of each language SDK receives security fixes; there are no long-term-support lines and older releases are not backported to.

Each of the nine languages versions independently. All nine reached 1.0.0 in the same release wave because they earned the same guarantee at the same time — they do not move in lockstep afterwards.

Documentation

License

Apache-2.0 — see LICENSE.