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

@starci/auth-oidc

v0.1.3

Published

The OIDC client of a StarCi full app (Keycloak) behind the same PrincipalVerifier interface as @starci/auth: a NestJS module (issuer discovery, JWKS with caching and rotation, algorithm-pinned token checks, role mapping, the authorization code flow with P

Readme

@starci/auth-oidc

The sign-in of a full StarCi app: the OIDC client of its identity provider (Keycloak), behind the same PrincipalVerifier interface as @starci/auth (the lite edition), so the guards, @Public and @Credential of an app do not change between editions. One package, four entries.

| entry | holds | |---|---| | @starci/auth-oidc/server | OidcModule.register / registerAsync (also createOidc without Nest): issuer discovery, the provider's JWKS with caching and rotation, the token verifier (algorithm pinned, issuer, audience, expiry, typ), OidcPrincipalVerifier, role mapping, and OidcClient, the authorization code flow (PKCE S256, state, nonce, code exchange, ID token verification, RP-initiated logout) | | @starci/auth-oidc/client | createOidcClient: the browser half of a public client: PKCE redirect, callback, one shared refresh, the access token in memory, sign-out only when the provider ends the session | | @starci/auth-oidc/contracts | algorithms, wire codes, claim names, the principal and the outcome shapes both sides read | | @starci/auth-oidc/testing | FakeOidcIssuer: discovery, JWKS, token minting, the token endpoint and failure injection, in memory or on a socket, so an app tests without Keycloak |

An app reaches the package only through integrations/keycloak (back end) and modules/auth/client.ts (front end); the lint refuses jose and every other JWT library in an app.

Back end

OidcModule.registerAsync({
    isGlobal: true,
    inject: [HTTP_CLIENT, CLOCK],
    useFactory: (http: HttpClient, clock: Clock) => ({
        ...parsedOptions, // issuer, audience, client registration, role mapping, from the app's config loader
        http: { request: (request) => http.request({ correlationId: "identity-keycloak", ...request }) },
        clock,
    }),
})

The package reads no environment. The app parses its settings (BE-CONFIG-2) and passes typed options; the client secret is passed as a { reveal(): string } holder or text and revealed only for the token request. Every provider request goes through the OidcHttp port, so the app's bounded HTTP client (platform/http) is the one door; without the port the platform fetch is used with the request's deadline.

| a bearer token | is checked by | |---|---| | the algorithm | pinned to algorithms (RS256) before any key is looked up; none, HMAC and unlisted algorithms are refused | | the key | the JWKS of the discovered jwks_uri, selected by kid; cached 10 minutes; an unknown kid refreshes it at most once per 30 seconds (rotation); an unreachable provider keeps the cached keys for 24 hours | | issuer, audience, expiry, not-before | the registration and the clock, with a clock skew of 5 seconds | | the token type | an ID token or a refresh token presented as a bearer is refused (typ must be Bearer) | | the roles | realm_access.roles, resource_access.<client>.roles of the mapped clients and the mapped flat claims; a claim of the wrong shape or past 128 roles refuses the token |

verify answers { kind: "ok", value: OidcPrincipal } or { kind: "refused", code: "PASSPORT_ACCESS_TOKEN_INVALID", detail: { reason } } (the code @starci/auth refuses with; the reason is for the log). A provider that cannot be asked and has nothing cached throws an OidcError of kind unavailable, which the app maps to a 503, never a 401: an identity-provider outage does not sign anyone out.

Endpoints a discovery document advertises are trusted only on the issuer's origin, over https, without credentials; http: is admitted only with allowInsecureHttp (a developer machine or a test double).

The glue in an app

integrations/keycloak (slot be.integrations.keycloak) is declaration only, five files; no unit spec sits beside them (the proof is the integration spec of src/tests/integration/keycloak/, which drives an outage with FakeOidcIssuer, and the guard's own spec):

| file | holds | |---|---| | keycloak.options.ts | the typed registered client (issuer, audience, clientId, clientSecret: Secret, callbackUri, postLogoutUri, timeoutMs) | | keycloak.config.ts | parseKeycloakConfig(env: EnvSource): env.url, env.string, env.secret, env.int; no default address or credential | | keycloak.decorators.ts | the token of the package's verifier (OIDC_PRINCIPAL_VERIFIER), which domain/identity provides under its own ACCESS_TOKEN_VERIFIER | | keycloak.registration.ts | keycloakModule(options, { isGlobal }) = OidcModule.registerAsync over HTTP_CLIENT and CLOCK, the http option adapting HttpClient.request with its correlation id | | index.ts | what the rest of the app reads: the config parser, the module function, the token and the package's PrincipalVerifier and OidcPrincipal types |

AuthGuard of domain/identity reads the bearer token and asks the injected PrincipalVerifier, exactly as the lite guard asks @starci/auth's: a verified principal is left on the request, a refusal is IDENTITY_UNAUTHENTICATED, and an unavailable error is not caught (the errors filter maps its kind to a 503).

Front end

// modules/auth/client.ts: the one instance
export const auth = createOidcClient({ issuer: () => issuer(), clientId: "web", redirectUri: () => `${origin()}/auth/callback`, postLogoutRedirectUri: () => origin() })

signIn() redirects to the provider; the callback page calls completeSignIn(window.location.href); restore() signs in again without a page after a reload (a prompt=none redirect, tried once); accessToken(), renew(), headers(), subscribe, getSnapshot, watch and signOut() have the shape of the @starci/auth/client session surface. The refresh token lives in memory with the access token and never in storage; a refresh ends the session only on a 401, a 403 or a 400 invalid_grant, and a 429, a 5xx or a lost connection is retried with backoff.

Tests

npm test typechecks (including the assignability of the verifier and the client to the @starci/auth interfaces), lints, and runs the node:test specs under 90% line coverage. The suite needs @starci/auth built next to it (file:../auth).

const issuer = await FakeOidcIssuer.create({ audience: "api" })
const moduleRef = await Test.createTestingModule({ imports: [OidcModule.register(issuer.options({ callbackUri }))] }).compile()
const token = await issuer.mintAccessToken({ sub: "user-1", roles: ["member"] })
issuer.setDown(true) // the provider is unreachable: verification throws an unavailable error