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

@hyper.authio/client

v0.10.1

Published

Authio client for Node.js and edge runtimes: discovery, interactive sign-in with ID tokens and logout, every grant, JWE, introspection, client-credentials cache, Admin API credentials, roles and permissions, webhooks.

Readme

@hyper.authio/client

The Authio client for Node.js 20.19+ and edge runtimes: discovery, interactive sign-in (PKCE, ID tokens, end session, back-channel logout tokens, step-up), every grant, client authentication with a secret or private_key_jwt, introspection, revocation, UserInfo, JWE, a cached client-credentials fetch, account credentials for the Admin API, the roles and permissions of a token, and webhook verification. Its only dependency is jose.

npm install @hyper.authio/client

Configure

import { createAuthioClient } from "@hyper.authio/client";

const authio = createAuthioClient({
  issuer: "https://auth.example.com/realms/acme", // or authority + realm
  clientId: "orders-worker",
  clientSecret: process.env.AUTHIO_CLIENT_SECRET,
});

| Option | Default | | | --- | --- | --- | | issuer, or authority + realm | | The realm's issuer. Must be HTTPS, except on loopback or with requireHttps: false. | | clientId | | Required. | | tokenEndpointAuthMethod | client_secret_basic with a secret, private_key_jwt with only a key, none with neither | The client's authentication method in Authio, which matches it exactly: client_secret_basic, client_secret_post, private_key_jwt or none (a public client). | | clientSecret | | For client_secret_basic / client_secret_post. A function returning the secret is called for every request, so a rotated secret is used without a restart. | | clientPrivateKey, clientPrivateKeyPath, clientKeyId, clientSigningAlgorithm | RS256 for RSA, the curve's ES algorithm for EC | For private_key_jwt: PEM (PKCS#8, PKCS#1, SEC1) or JWK; the file is re-read when it changes; RSA keys need 2048 bits. | | clientAssertionLifetime | 120 s | At most 3600. | | checks | none | What the sign-in checks: pkce, state, nonce (or none). Only the listed checks are made. | | codeChallengeMethod | S256 | S256 or plain, with the pkce check: one of the client's allowed PKCE methods. | | responseMode | not sent | query or web_message. | | idTokenSignedResponseAlg | the asymmetric algorithms the discovery document announces | The client's signing algorithm: RS/PS/ES 256/384/512, or HS256/384/512 verified with clientSecret (at least 32, 48 or 64 bytes, RFC 7518 §3.2). Also used for logout tokens. | | accessTokenSignedResponseAlg | the asymmetric algorithms the discovery document announces | The algorithm of the client's access tokens (Authio's second signing setting), for readAccessTokenClaims; HS* with clientSecret. | | userinfoSignedResponseAlg | idTokenSignedResponseAlg | For UserInfo answered as a signed JWT. | | clientEncryptionKey, clientEncryptionKeyPath | | The private half of the client's encryption key (JWK or PEM, or a function returning it; the path is Node only), to read ID tokens, UserInfo and logout tokens Authio encrypts. Without it nothing is decrypted. | | encryptionAlgorithms | RSA-OAEP, RSA-OAEP-256 | The JWE alg accepted. RSA1_5 cannot be listed: Node and WebCrypto cannot decrypt it. | | encryptionMethods | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | The JWE enc accepted; A128GCM, A192GCM and A256GCM may be listed. | | allowLegacyEncryptionAlgorithms | false | Accepted for parity; RSA1_5 stays refused in Node, which cannot decrypt it (legacy_algorithm). | | redirectUri, postLogoutRedirectUri | | Defaults for sign-in and sign-out requests. | | clockSkew | 30 s | Tolerated on ID tokens, logout tokens and signed UserInfo. | | discoveryCacheDuration | 3600 s | | | httpTimeout | 30 s | | | tokenRefreshSkew | 60 s | How early cached tokens are renewed (at most half their lifetime). | | fetch, clock | global fetch, system clock | Replace them in tests. |

Every endpoint is read from the discovery document, which is cached, read once for concurrent callers, and refused when it names another issuer.

Option names follow the shared conformance/options.md; there are no aliases for older names.

Mirror the client

The options describe the client as it is registered in Authio's console, and an option left out is off: the library hard-codes none of it. Authio refuses a request that does not match the client and accepts what the client does not require.

| In the console | Options | | --- | --- | | PKCE required, method S256 | checks: ["pkce", "state"] (add "nonce" to bind the ID token), codeChallengeMethod: "S256" | | PKCE required, method plain only | checks: ["pkce", "state"], codeChallengeMethod: "plain" | | PKCE off | checks: ["state"], or leave checks out to check nothing (sending PKCE anyway is accepted) | | Authentication on, client_secret_post | clientSecret, tokenEndpointAuthMethod: "client_secret_post" | | Authentication on, private_key_jwt | clientPrivateKey (or clientPrivateKeyPath), clientKeyId | | Authentication off (public client) | no secret nor key: tokenEndpointAuthMethod is none | | ID token signing algorithm HS256 | clientSecret, idTokenSignedResponseAlg: "HS256" | | Access token signing algorithm HS256 | clientSecret, accessTokenSignedResponseAlg: "HS256" (an API: the verifier's accessTokenSignedResponseAlg) | | Tokens encrypted with the client's key, RSA-OAEP-256 | clientEncryptionKey, encryptionAlgorithms: ["RSA-OAEP-256"] |

Sign people in

const authio = createAuthioClient({
  issuer, clientId: "storefront", // a public client: no secret nor key, so tokenEndpointAuthMethod is none
  checks: ["pkce", "state", "nonce"], codeChallengeMethod: "S256", // the client requires PKCE with S256
  redirectUri: "https://shop.example.com/callback",
});

// 1. Send the person to Authio, keeping the transaction (session, encrypted cookie…).
const { url, transaction } = await authio.createAuthorizationUrl({
  scopes: ["openid", "profile", "offline_access"],
  resource: "https://orders.example.com",   // RFC 8707
  organization: "acme-europe",              // optional: sign in for one organization
  prompt: "login",                          // none | login | select_account | consent
  maxAge: 900, acrValues: "2", loginHint, uiLocales: "pt-BR", idpHint: "google",
});

// 2. On the callback: state (with the state check) and iss (RFC 9207) are checked, the code is redeemed (with the
//    PKCE verifier, with the pkce check), and the ID token validated (nonce with the nonce check, at_hash, auth_time
//    against maxAge).
const { tokens, idToken } = await authio.handleAuthorizationCallback(request.url, transaction);

Each check generates its parameter and is verified on the callback; the transaction lists the checks it was made with, and a state it did not ask for is ignored. A request may pass its own checks, codeChallengeMethod and responseMode. popup: true sets display=popup and response_mode=web_message; pass the parameters the popup posted to the opener to handleAuthorizationCallback. A callback that is not for the transaction throws AuthioCallbackError (reason: state_mismatch, issuer_mismatch, missing_issuer, missing_code); an OAuth error Authio sent back (login_required after prompt: "none", for one) throws AuthioProtocolError.

acrValues asks for a sign-in level; it does not guarantee one. Authio may sign the person in with less (no second factor enrolled and none required by the client), as OpenID Connect allows (acr_values is a voluntary claim), so read idToken.acr after the callback and refuse a sign-in below the level the app needs, as examples/web-login does.

ID tokens and sign-out

const claims = await authio.validateIdToken(idToken, { nonce, accessToken, maxAge }); // AuthioTokenValidationError otherwise
const logoutUrl = await authio.createEndSessionUrl({ idTokenHint, postLogoutRedirectUri, state });
const logout = await authio.validateLogoutToken(form.logout_token); // { subject, sessionId, tokenId, claims }

ID tokens are checked against the realm's JWKS (cached, re-read on an unknown kid), or against clientSecret for the HS* algorithm idTokenSignedResponseAlg names: iss, aud = the client id, exp with clockSkew, payload typ = ID, azp when there are several audiences, nonce, at_hash (hash of the signing algorithm) and auth_time against maxAge. An encrypted ID token is decrypted with clientEncryptionKey first. A back-channel logout token must be typed logout+jwt, carry the back-channel event, no nonce, and a sub or sid. Every refusal is an AuthioTokenValidationError whose reason uses the outcome names of conformance/tokens.json (invalid_nonce, invalid_at_hash, invalid_logout_token, invalid_algorithm, expired, …). In Express, authioBackchannelLogout from @hyper.authio/express is the endpoint.

readAccessTokenClaims(accessToken) returns the payload of an access token the client received once it is decrypted and its signature, iss and exp verify (undefined for an opaque token). For front-channel logout, Authio loads the client's front-channel logout URI in an iframe; validateFrontChannelLogout(request.url) checks iss and returns the sessionId (sid) to end (authioFrontchannelLogout in @hyper.authio/express).

Step-up

When an API answers 401 with WWW-Authenticate: Bearer error="insufficient_user_authentication", acr_values="2", max_age=300 (RFC 9470 §3; servers before @hyper.authio/server 0.8.1 and @hyper.authio/mcp 0.4.1 answer 403, and the challenge reads the same):

const challenge = parseAuthioBearerChallenge(response.headers.get("www-authenticate"));
const { url, transaction } = await authio.createStepUpAuthorizationUrl(challenge!); // acr_values, max_age, prompt=login for max_age=0

Grants

await authio.tokens.clientCredentials({ scopes: ["orders:read"], resource: "https://orders.example.com" });
await authio.tokens.authorizationCode({ code, redirectUri, codeVerifier });
await authio.tokens.refresh({ refreshToken, resource });   // store the rotated refresh_token it returns; resource: RFC 8707
await authio.tokens.exchange({ subjectToken, resource });  // on-behalf-of (RFC 8693)
await authio.tokens.password({ username, password });
await authio.tokens.personalAccessToken({ token: "pat_…", clientId: "billing-api" }); // clientId only for a consumer's PAT

const device = await authio.tokens.deviceAuthorization({ scopes: "openid" });
console.log(device.verification_uri_complete);
const token = await authio.tokens.pollDevice(device);      // waits, slows down, throws access_denied / expired_token

const connection = await authio.tokens.exchangeForConnection({ subjectToken, provider: "github" });
switch (connection.status) {
  case "granted": /* accessToken, issuedTokenType, tokenType, expiresIn, scope, connectionId, provider, providerType, externalAccount */ break;
  case "required": /* send the person to connection.connectUri */ break;
  case "selection_required": /* ask again with one of connection.connections[].id (label, externalLogin) */ break;
  case "refused": /* connection.error, connection.errorCode */ break;
}

Device polling waits interval, keeps polling on authorization_pending, adds 5 seconds on slow_down, and stops with the error on access_denied and expired_token (toDevicePollOutcome tells which).

OAuth errors throw AuthioProtocolError (error, errorDescription, errorCode, status); error is http_{status} when Authio sent none, invalid_response when a success body could not be read. Tokens print as [redacted] in console.log and util.inspect, and serialize normally.

Machine-to-machine calls

const ordersFetch = authio.clientCredentialsFetch({ scopes: "orders:read", resource: "https://orders.example.com" });
await ordersFetch("https://orders.example.com/orders");

The token is cached per scopes and resource, renewed before expiry with one request for concurrent callers, and on a 401 dropped and retried once. authio.clientCredentials.getToken() gives the token itself.

Introspection, revocation, UserInfo

const result = await authio.introspect(token); // { active, sub, scope, …, and Authio's extra members }
await authio.revoke(refreshToken, { tokenTypeHint: "refresh_token" });
const info = await authio.userInfo(accessToken); // JSON, or a signed / encrypted JWT verified for you

Authio answers introspection according to who asks: a token requested for a resource is active only to the client that resource server names as its introspection client.

Admin API credentials

import { createAuthioAccountCredential, readAuthioAdminResponse } from "@hyper.authio/client";

const credential = createAuthioAccountCredential({
  authority: "https://auth.example.com",
  credential: process.env.AUTHIO_CREDENTIAL, // ak_…, cs_… or pat_…
  credentialId: process.env.AUTHIO_CREDENTIAL_ID, // needed for a cs_ secret
});
const response = await credential.fetch("https://auth.example.com/api/v1/admin/realms");
const { data } = await readAuthioAdminResponse(response); // { items, totalCount, pageNumber, pageSize } for a listing; AuthioAdminError otherwise

ak_ and pat_ go directly as Authorization: Bearer (or x-api-key with presentAs); a cs_ secret is exchanged at {authority}/realms/authio with client_credentials and the client_id cred_{id}, and a pat_ may be exchanged with the personal_access_token grant (exchangePersonalAccessToken: true). The fetch retries an idempotent request once after Retry-After on 429/503. AuthioAdminError carries status (the HTTP one), errorCode, notifications, requestId (body or X-Request-Id) and retryAfterSeconds; parseAuthioAdminEnvelope reads an answer you fetched yourself. The full Admin client comes in a later release.

Roles and permissions

import { parseResourceAccess } from "@hyper.authio/client";

const access = parseResourceAccess(jwtPayload);   // or introspection answer, or [type, value] pairs
access.roles;                                     // ["billing-admin", "viewer"]
access.permissions;                               // { invoice: ["read", "write"], report: ["read"] }
access.grants("invoice", "write");                // true, case-insensitive
access.grants("report", "read", ["user"]);        // only roles from user_resource_access
access.fromSource("client");                      // the client's roles

The seven *_resource_access claims (user, realm, client, plan, consumer, secret, system) are merged. A claim that is not exactly { role: { resource: [method] } } contributes nothing, as in Authio's own check. To protect an API, use @hyper.authio/server, which also reads groups, mapper claims and the rest.

Webhooks

import { verifyWebhookSignature, parseWebhookEvent, asUserEvent } from "@hyper.authio/client";

const result = await verifyWebhookSignature(request.headers["x-authio-signature"], rawBody, secret);
if (!result.valid) return reply(401);

const event = parseWebhookEvent(rawBody);
const user = asUserEvent(event); // { id, username, emailConfirmed, … } for user_created / user_updated / user_deleted, else undefined

Every event type has its accessor (asLoginEvent, asUserLockedOutEvent, asTokenRevokedEvent, asKeysRotatedEvent, asTokenReuseDetectedEvent, asAdminResourceEvent, asUserSuspensionEvent, asAttackDetectedEvent, asCredentialCompromisedEvent, asAuthenticatorAppEvent, asPingEvent), which exposes every member of data in camelCase with its value unchanged; event.data stays as delivered (snake_case). An unknown type still parses (isKnownWebhookEventType is false, event.data is the raw JSON).

The signature is HMAC-SHA256 of {t}.{body}, compared in constant time; the timestamp must be within 5 minutes. @hyper.authio/server adds deduplication and dispatch.

License

MIT