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

@antzsoft/wso2-auth-thingsboard-js

v1.0.1

Published

Minimal WSO2 IS login-redirect + token-storage connector for ThingsBoard CE's Angular frontend — a standard (non-SPA) confidential-client web app whose backend does its own OAuth2 code exchange.

Readme

@antzsoft/wso2-auth-thingsboard-js

Minimal login-redirect + token-capture connector for WSO2 Identity Server 7.x, built for ThingsBoard CE's Angular frontend — a standard confidential-client web app (separate Java backend + separate frontend), not a PKCE/SPA client.

Unlike @antzsoft/wso2-auth-web (which owns the whole OAuth2 code exchange in the browser), this SDK never talks to WSO2 directly and never holds a client secret. The backend does its own authorization-code exchange server-side (via Spring Security's stock OAuth2 login) and attaches the WSO2 access/refresh token to the post-login redirect as query params — this package's whole job is to build the login-redirect URL, capture those query params, store them, and (optionally) keep them fresh.


Contents


What's in the package

| Export | Description | |--------|-------------| | AntzAuthClient | Core client class — login/logout navigation, token capture/storage, proactive refresh | | withLoginHint | Stateless helper for apps that build their own per-provider login URLs | | AntzWso2RefreshError | Thrown by refresh()/getSessionInfo(); carries an HTTP status when available, so callers can distinguish a confirmed-dead token from a transient failure | | AntzAuthConfig, AntzTokenSet, AntzLoginOptions, AntzSessionInfo | TypeScript types |

There is no framework adapter (no React/Vue/Angular hook) — the class is plain TypeScript with only browser globals (localStorage, window, fetch, URL). ThingsBoard's AuthService (Angular) wraps it directly; any other backend-mediated-redirect app can do the same.


Installation

npm install @antzsoft/wso2-auth-thingsboard-js

For local SDK development, build/publish it to a local Verdaccio registry the same way as the web/reactnative SDKs — see ../PUBLISH.md (the outer sdks/ publishing tooling covers all four packages: web, rn, thingsboard-js, and spring).


Configuration

import { AntzAuthClient } from "@antzsoft/wso2-auth-thingsboard-js";

const auth = new AntzAuthClient({
  loginUrl: "/oauth2/authorization/wso2",       // optional — see below
  logoutUrl: "/logout",                          // optional — see below
  refreshUrl: "/api/auth/wso2/refresh",          // optional — enables refresh()/startAutoRefresh()
  headersProvider: () => ({ Authorization: `Bearer ${myAppToken}` }), // optional
});

Config options

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | loginUrl | string | only for login()/getLoginUrl() | — | The app's own OAuth2 login-initiation URL (e.g. Spring Security's stock /oauth2/authorization/{registrationId}). Omit if the app builds its own per-provider URLs and only uses this SDK for token capture — withLoginHint() still works standalone. | | logoutUrl | string | only for logout() | — | The app's own logout URL. | | accessTokenParam | string | no | wso2AccessToken | Query param the backend attaches the WSO2 access token as. | | refreshTokenParam | string | no | wso2RefreshToken | Query param the backend attaches the WSO2 refresh token as. | | idTokenParam | string | no | wso2IdToken | Query param for the captured OIDC id_token (requires openid scope on the WSO2 app). | | logoutUrlParam | string | no | wso2LogoutUrl | Query param for WSO2's end-session URL — used by ssoLogout(). | | oauth2ClientIdParam | string | no | wso2OAuth2ClientId | Query param identifying which registered WSO2 app this session came from. | | expiresInParam | string | no | wso2ExpiresIn | Query param for the access token's remaining lifetime in seconds, as of the redirect. | | storageKeyPrefix | string | no | antz_wso2_ | localStorage key prefix. | | refreshUrl | string | only for refresh()/startAutoRefresh() | — | The app's own backend endpoint that refreshes the WSO2 token pair server-side (this SDK never holds the confidential client's secret). | | refreshBufferSeconds | number | no | 60 | How many seconds before actual expiry to proactively refresh. | | headersProvider | () => Record<string, string> | no | — | Called once per refresh()/getSessionInfo() request to supply extra headers (e.g. the host app's own bearer token, if the backend endpoint requires authentication). | | onSessionExpired | () => void | no | — | Fires when the proactive refresh loop confirms the WSO2 refresh token itself is dead (a 4xx response from refreshUrl, e.g. invalid_grant) — never for a transient network/5xx failure, which is retried silently instead. | | sessionInfoUrl | string | only for getSessionInfo()/startSessionPoll() | — | The app's own backend endpoint that returns the current WSO2 token's expiry, read server-side from WSO2's token DB (the wso2-session-info bundle). | | sessionPollIntervalSeconds | number | no | 0 (off) | How often startSessionPoll() calls getSessionInfo() to detect server-side session revocation ahead of the access token's own natural expiry. |


Core API

login() / getLoginUrl()

Navigates the browser to the app's own OAuth2 login-initiation URL (loginUrl), with an optional login_hint to prefill WSO2's hosted login form.

auth.login({ loginHint: "[email protected]" }); // navigates immediately
const href = auth.getLoginUrl();                // for <a href> bindings, doesn't navigate

login_hint is a pure UX convenience — it is never a user-existence check. WSO2 still owns all credential/existence validation.

Multi-account / session-mismatch enforcementexpectedUser/autoSwitchAccount guard against WSO2's single-session-per-browser behavior (see docs/multi-account-login-behavior.md):

auth.login({ loginHint: "[email protected]", expectedUser: "[email protected]" });
// default (autoSwitchAccount unset/false): if the browser's live WSO2 SSO session belongs to a
// DIFFERENT user, the backend redirects to /login?loginError=... instead of completing the
// login as the wrong user.

auth.login({ loginHint: "[email protected]", expectedUser: "[email protected]", autoSwitchAccount: true });
// opt-in: same user → silent reuse (no login page shown); different user → backend forces a
// fresh WSO2 credential prompt (prompt=login) for [email protected] instead of erroring.

Unlike login_hint, this SDK never checks the result itself — it never sees the OAuth2 callback, the backend does (see ../docs/INTEGRATION_GUIDE.md's Oauth2AuthenticationSuccessHandler section). A mismatch surfaces as the app's existing loginError query param, the same mechanism any other OAuth2 login failure already uses — there is no thrown JS exception to catch here.


withLoginHint()

Stateless helper for apps that build their own per-provider login URLs (e.g. ThingsBoard, which can have several configured OAuth2 clients) rather than using a single fixed loginUrl. Carries the same multi-account enforcement as login()/getLoginUrl() above: since there's only ever the one username the user typed into the form, it sets both login_hint and expected_user to loginHint (there's no separate expectedUser to pass).

import { withLoginHint } from "@antzsoft/wso2-auth-thingsboard-js";

const url = withLoginHint(oauth2Client.url, username); // appends login_hint AND expected_user

const urlAutoSwitch = withLoginHint(oauth2Client.url, username, /* autoSwitchAccount */ true);
// also appends auto_switch_account=true — see the `login()` example above for the semantics

captureTokenFromRedirect()

Reads the WSO2 access/refresh/id token, end-session URL, OAuth2 client id, and expiry (whichever are present) off the current URL's query params — attached by the backend's OAuth2 success handler after login — stores them, and strips them from the URL. Call this once on app bootstrap.

const tokens = auth.captureTokenFromRedirect();
// { accessToken, refreshToken?, idToken?, logoutUrl?, expiresAt? } | null

Returns null if no access token query param was present (e.g. a non-SSO page load).


getStoredAccessToken() / getStoredTokens()

const token = auth.getStoredAccessToken(); // string | null — for later API calls (e.g. change-password)
const tokens = auth.getStoredTokens();     // AntzTokenSet | null

getStoredIdToken() / getStoredOAuth2ClientId()

const idToken = auth.getStoredIdToken();               // string | null — present only if `openid` scope was granted
const oauth2ClientId = auth.getStoredOAuth2ClientId();  // string | null — which registered WSO2 app this session came from

logout()

Navigates the browser to the app's own logoutUrl and clears stored WSO2 tokens. This is a local, app-only logout — it does not touch WSO2's shared SSO session. Use ssoLogout() instead when you need to actually kill the commonAuthId cookie.

auth.logout();

ssoLogout()

Real WSO2 SSO logout: a top-level browser navigation (not fetch, not an iframe) to WSO2's end-session endpoint with the id_token captured at login, killing the shared commonAuthId cookie so a later SSO redirect — in this app or any other — can't silently re-authenticate the user.

const navigated = auth.ssoLogout("https://your-app.example.com/login"); // post_logout_redirect_uri
if (!navigated) {
  auth.clearStoredTokens(); // local-only fallback
}

A real navigation is required here, not an iframe/fetch:

  • WSO2's /oidc/logout does not send Access-Control-Allow-Credentials, so a cross-origin fetch would never carry the commonAuthId cookie even with credentials: 'include'.
  • WSO2 commonly sends X-Frame-Options/a restrictive frame-ancestors CSP on its login/logout pages (clickjacking protection), which silently blocks an iframe from loading them at all.

Returns false (and does nothing) if no id_token/logout URL was captured at login — e.g. the WSO2 app wasn't registered with openid scope, or this session didn't go through a real SSO redirect at all (a local-login-only session). Callers should fall back to logout() in that case.


refresh() / startAutoRefresh() / stopAutoRefresh()

Renews the stored WSO2 access/refresh token pair via the app's own refreshUrl — the backend calls WSO2's refresh_token grant server-side, since this SDK never holds the confidential client's secret.

const tokens = await auth.refresh(); // throws if refreshUrl unset, no refresh token stored, or the request fails

auth.startAutoRefresh(); // self-re-arming proactive refresh, fires `refreshBufferSeconds` before expiry
auth.stopAutoRefresh();  // cancel a pending scheduled refresh

startAutoRefresh() is a no-op if no WSO2 token is currently stored (e.g. a local-login-only session that never went through WSO2 SSO) or expiresAt isn't known yet. A failed refresh either:

  • retries after a short delay, for a transient network/5xx failure — never treated as "session ended," or
  • stops and calls onSessionExpired, for a confirmed-dead refresh token (a 4xx response — WSO2 itself rejected it, e.g. invalid_grant).

startAutoRefresh() also installs a visibilitychange listener: if the tab was backgrounded (or suspended) long enough that the scheduled timer didn't fire on time, regaining focus triggers an immediate catch-up refresh instead of waiting for the next scheduled attempt or the next API call. stopAutoRefresh() removes both the timer and this listener.

Concurrent refresh() calls (e.g. the scheduled timer and a visibilitychange catch-up landing around the same time) share a single in-flight request rather than each independently POSTing — WSO2 rotates the refresh token on use, so two parallel requests would otherwise race and one would get rejected with an already-stale refresh token. The underlying fetch() is also guarded by a 15s timeout, so a stuck request against an unreachable backend can't stall the refresh loop forever — a timeout is treated the same as any other network failure (retried, never treated as "session ended").


getSessionInfo() / startSessionPoll() / stopSessionPoll()

Fetches the current WSO2 access/refresh token's expiry via the app's own sessionInfoUrl — server-side truth read from WSO2's token DB by the wso2-session-info bundle, rather than trusting the locally-stored expiresAt's clock or assuming the token is still valid. This is how a session that was revoked server-side (e.g. an admin-forced logout, or a password changed on another device) gets detected — something the proactive refresh timer alone can't catch, since it only reacts to the locally-known expiresAt.

const info = await auth.getSessionInfo(); // throws if sessionInfoUrl unset, no access token stored, or the request fails

auth.startSessionPoll(() => {
  // Called once, and the poll stops itself, when the session is confirmed dead server-side (4xx).
  console.log("Session expired — redirect to login");
});
auth.stopSessionPoll(); // cancel a running poll

startSessionPoll() is a no-op unless both sessionInfoUrl and sessionPollIntervalSeconds are configured (the latter defaults to 0/off), or if a poll is already running. Paused while the tab is hidden (document.visibilityState === 'hidden') — a network/5xx failure on a tick is swallowed and retried on the next tick, never treated as "session ended." clearStoredTokens() and ssoLogout() both stop a running poll automatically, same as they do for auto-refresh.


clearStoredTokens()

Clears any stored WSO2 tokens without navigating anywhere. Also stops auto-refresh and the session poll, if running.

auth.clearStoredTokens(); // e.g. after a successful password change (WSO2 revokes the old token anyway)

Backend Contract

This SDK is one half of a pair — the other half is the backend's own OAuth2 success handler, which must attach these query params to the post-login redirect (all optional except the access token; names configurable via the *Param config options above):

| Query param | Purpose | |---|---| | wso2AccessToken | The WSO2 access token (required — its absence means captureTokenFromRedirect() returns null) | | wso2RefreshToken | The WSO2 refresh token | | wso2IdToken | The OIDC id_token, if the WSO2 app was logged in via an openid-scoped flow | | wso2LogoutUrl | WSO2's end-session (/oidc/logout) URL for this tenant, if an id_token was captured | | wso2OAuth2ClientId | Identifier for which registered WSO2 app this session came from | | wso2ExpiresIn | Seconds until the access token expires, as of the redirect |

And, for refresh()/startAutoRefresh() to work, an endpoint at the configured refreshUrl accepting POST { refreshToken, oauth2ClientId } and returning { accessToken, refreshToken?, idToken?, expiresIn? }.

And, for getSessionInfo()/startSessionPoll() to work, an endpoint at the configured sessionInfoUrl accepting GET with an X-Wso2-Access-Token header (the caller's own stored WSO2 access token) and returning { accessTokenExpiresAt, accessTokenExpiresInSeconds, refreshTokenExpiresAt, refreshTokenExpiresInSeconds } (the first/third as Unix epoch seconds — this SDK converts to epoch milliseconds itself).

See com.antzsoft.wso2auth:wso2-auth-spring (the companion Spring Boot connector) for a ready-made server-side implementation of this contract.


Integration Guide (ThingsBoard)

See ../docs/INTEGRATION_GUIDE.md in this repo for the full end-to-end wiring into ThingsBoard's Angular AuthService/LoginComponent/AuthController (Spring), including the global logout flow, JWKS validation, change-password, and M2M SCIM2 user administration on the backend side.