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.3.0

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.

Downloads

591

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 | | AuthTransportError | Thrown by refresh()/getSessionInfo(); carries an HTTP httpStatus when available | | isRetryable / isNetworkError | The keep-vs-end-session classification — isRetryable(err) is true for a transient failure (network / 5xx / 429), false for a confirmed-dead token (4xx) | | AntzAuthConfig, AntzTokenSet, AntzLoginOptions, AntzSessionInfo, AntzUserClaims | TypeScript types |

Breaking export change (unreleased): AntzWso2RefreshError is retired. refresh() and getSessionInfo() now throw the shared AuthTransportError (.httpStatus instead of .status); classify failures with isRetryable(err). The connector's engine layers (src/core/, src/transport/) are now vendored copies of @antzsoft/auth-web's tested SessionManager, kept byte-identical by scripts/check-shared.sh — see docs/ENGINE-PORT-PLAN.md. Version bump and publish happen at Phase 6.

Engine mode (advanced)

Alongside AntzAuthClient — still used for WSO2 token capture, silent cross-app SSO and RP-initiated logout — the package also exports the tested lifecycle engine and its two transports. As of ENGINE-PORT-PLAN.md Phase 4 the ThingsBoard fork's AuthService drives TB's own JWT pair through a SessionManager (tbManager + ThingsboardJwtAdapter), retiring its hand-rolled proactive-refresh timer / visibility catch-up / cross-tab listener:

| Export | Purpose | |---|---| | SessionManager | The framework-free lifecycle engine — refresh timer, single-flight lock, retry-vs-logout, persistence. One instance per session lifecycle. | | createSsoTransport(config) | AuthTransport for the WSO2 session — refresh / validateSession / changePassword / logout against the backend's /api/auth/wso2/* routes. | | createNativeTransport(config) | AuthTransport for the app's own credential API — /api/auth/login, /api/auth/token, /api/auth/changePassword, /api/auth/logout, /api/auth/user. verifyCredentials is opt-in via verifyCredentialsPath (ThingsBoard CE has no verify-only endpoint, and reusing the login route would mint a session that then has to be torn down). | | createRestTransport(options) | For a transport this package does not ship — JSON-over-HTTP against another API, with per-request hooks. | | attachBrowserLifecycle(manager, config) | Wires visibilitychange / online / storage events to a SessionManager, plus refresh-token expiry warnings; returns a detach function. | | deriveModeCapabilities(flags, transport?, overrides?) | Collapses the checkUser response into { mode, capabilities } for the login screen. mode is 'sso' \| 'direct' \| null (null = not provisioned). | | decodeJwt / expiryFromJwt / jwtToUser | For a transport whose server issues JWTs. Use expiryFromJwt() rather than multiplying exp by hand — TokenSet expiries are epoch milliseconds and exp is in seconds. | | SplitStorageAdapter · localStorageAdapter · createMemoryStorageAdapter() | Storage adapters. ThingsBoard supplies its own (ThingsboardJwtAdapter) because it persists jwt_token under the app's existing keys. | | AuthTransportError · AuthNotSupportedError · isRetryable() · isNetworkError() | isRetryable() is the keep-vs-end-session decision: 4xx → the credential is dead, 5xx / network → transient. |

Capabilities are read from the transport, not declared. The three feature flags (credentialLogin, changePassword, verifyCredentials) are typeof transport.x === 'function'; only passwordRecovery and usesRedirectCallback come from the mode. A hand-written boolean can lie — it keeps saying true after someone deletes the method, and the screen then renders a control that throws when pressed. overrides covers the one honest exception: an app that implements an operation outside the transport, as the fork does with login.

There is no switchMode. ThingsBoard picks the mode per user on the server (bypassSso), so it is not a user-facing choice; capabilities is the only part of the dual-mode API surface adopted.

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. | | revalidateOnFocus | boolean | no | true | Gates startFocusRevalidation(). When false that call is a no-op — no listener attached, throttle never consulted — so focus revalidation can be disabled from config without removing the call site. | | revalidateOnFocusMinIntervalSeconds | number | no | 300 | Minimum gap between two startFocusRevalidation() firings, in seconds (300 = 5 minutes). The window starts when startFocusRevalidation() is called, so the page load's own check counts as the first one — a refocus shortly after load is throttled, not fired. 0 disables the throttle; startFocusRevalidation's own second argument overrides this per call. | | 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)

Silent cross-app SSO

When a WSO2 SSO session already exists in the browser (login elsewhere), the app can pick it up automatically — auto-login on the login page, auto-logout if the shared session vanished elsewhere, auto-switch if it now belongs to a different (already TB-provisioned) user — without ever showing a credential form. Full spec: docs/thingsboard-silent-sso-design.md (repo root) and ../docs/silent-cross-app-sso.md.

These three methods perform a real top-level browser navigation and do not resolve synchronously — same warning as login()/getLoginUrl() above, but easier to miss here since nothing in the method names says "navigate." Don't await checkSilentSession/ checkSilentSessionAuthenticated expecting a return value; call handleSilentCheckReturn() once at bootstrap instead.

// Login page: call unconditionally — the SDK's own cooldown/grace-period guards prevent a
// redirect loop, not the call site.
auth.checkSilentSession({ loginUrl: oauth2Client.url }); // loginUrl only needed if config.loginUrl isn't set

// Authenticated page: call once per page load, with the current user's own email.
auth.checkSilentSessionAuthenticated(currentUser.email, { loginUrl: oauth2Client.url });

// App bootstrap, alongside (before) captureTokenFromRedirect():
const { handled } = await auth.handleSilentCheckReturn(async ({ reason, previousUser, newUser }) => {
  // reason: 'no_session' | 'different_user' | 'app_access_denied' | 'app_rejected_by_own_policy'
  // Only called when something actually changed — never for a same-user no-op or a fresh
  // case-1 auto-login. Clear this app's own session state here; never touch WSO2's commonAuthId.
});
if (!handled) {
  auth.captureTokenFromRedirect(); // normal login-return path, untouched
}
  • buildSilentCheckUrl(options?) — builds the silent=true&prompt=none[&expected_user=...] probe URL. Pass options.loginUrl for apps that build per-provider URLs rather than a single config.loginUrl (same reasoning as withLoginHint's standalone url parameter).
  • checkSilentSession(options?) — login-page auto-login. No-ops within a short cooldown after the last silent-check return, or within a short grace period after any successful token capture (real login or silent-check success) — both guard against redirect loops/races, see the design doc for the exact regressions each one prevents.
  • checkSilentSessionAuthenticated(expectedUserEmail, options?) — authenticated-page auto-logout/auto-switch. Same guards, plus lets the backend distinguish same-user (no-op) from different-user (auto-switch) from no-session (auto-logout).
  • handleSilentCheckReturn(onSharedSessionChanged?) — interprets a silent-check return. Returns { handled: false } immediately for a normal (non-silent) URL. onSharedSessionChanged is awaited once, only on an actual change; previousUser/newUser are AntzUserClaims (decoded id_token claims — sub, email, etc.).

A background silent probe never auto-provisions a brand-new TB account — an authenticated-but-not- yet-TB-provisioned WSO2 identity comes back as reason: 'app_access_denied' instead of silently creating one. That check lives server-side (Oauth2AuthenticationSuccessHandler, ThingsBoard sample), not in this SDK.

startFocusRevalidation() / stopFocusRevalidation()

revalidateOnFocus for this SDK — the same feature @antzsoft/wso2-auth-web's UseAntzAuthOptions.revalidateOnFocus and Antzsoft.Wso2Auth.BlazorServer's AntzAuthConfig.RevalidateOnFocus ship, and gated by the matching AntzAuthConfig.revalidateOnFocus flag (default true). Re-runs the authenticated-page silent check on tab refocus, not just on the app's own login/logout transitions — otherwise a tab left open and refocused after a cross-app global logout or account switch elsewhere wouldn't notice until the next real navigation.

auth.startFocusRevalidation(() => {
  auth.checkSilentSessionAuthenticated(currentUser.email, { loginUrl: oauth2Client.url });
});
auth.stopFocusRevalidation(); // remove the listener

Unlike Web/Blazor's variant — a background fetch, invisible to the user — checkSilentSessionAuthenticated here is a real top-level browser navigation, since the browser never holds a token to check locally. startFocusRevalidation accounts for that: on top of checkSilentSessionAuthenticated's own cooldown/grace-period guards (which only cover the round trip itself), it throttles how often onFocus fires — default 300s (5 minutes) — so a user alt-tabbing back and forth a few times a minute doesn't reload the whole app on every single refocus.

Disabling it: focus revalidation here is opt-in by call — nothing happens unless the app calls startFocusRevalidation(). Set AntzAuthConfig.revalidateOnFocus = false (default true) to turn it off from configuration without removing the call site — the call becomes a no-op, no listener is attached, and the throttle below is never consulted. stopFocusRevalidation() remains the way to detach a listener already installed.

That throttle is configurable, resolved in this order:

  1. startFocusRevalidation(onFocus, minIntervalSeconds) — the optional second argument, per call site.
  2. AntzAuthConfig.revalidateOnFocusMinIntervalSeconds — set once where the client is constructed.
  3. DEFAULT_REVALIDATE_ON_FOCUS_MIN_INTERVAL_SECONDS (300 = 5 minutes).

0 is honoured as "no throttle"; negative and non-finite values fall through to the next source.

// every refocus, at most once every 15s
auth.startFocusRevalidation(() => auth.checkSilentSessionAuthenticated(email, opts), 15);

Customizing error messages

An app can reject a WSO2-authenticated, TB-provisioned user via its own rule (role, group, feature flag, a check against another backend) that neither WSO2 nor TB's OAuth2 mapper config can express — by registering an Oauth2AppAccessPolicy bean server-side (ThingsBoard sample, Oauth2AuthenticationSuccessHandler). On rejection, the just-issued WSO2 refresh token is revoked (this app's own client_id only) and:

  • Manual login — the browser lands back on /login?loginError=<message>, same convention as the existing account-mismatch error; read the message off the query string to show it directly, or match on it to show your own copy instead.
  • Silent probe — surfaces through handleSilentCheckReturn's onSharedSessionChanged as reason: 'app_rejected_by_own_policy' — the same single place you already handle no_session/ different_user/app_access_denied.

This SDK has no config surface for the policy itself — see Oauth2AppAccessPolicy/Oauth2AppAccessDeniedException in the ThingsBoard sample fork (docs/silent-cross-app-sso.md §3.2a).


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 | | wso2Silent | true on any return from a silent cross-app SSO probe — read by handleSilentCheckReturn(), never a normal login return | | wso2SilentSwitch | true alongside a successful wso2Silent return whose identity differs from what the app previously knew | | wso2SilentError | The OAuth2/backend error code on a failed wso2Silent return (e.g. login_required, access_denied, app_access_denied_by_policy) — mutually exclusive with wso2AccessToken being present |

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.