Changelog
What changed in each published Hawcx SDK release, and what you need to do about it.
Entries are keyed to versions that are actually published, and every API name below was checked against the published package rather than against source. If a release needs action on your side, it says so at the top of the entry.
Current versions
| SDK | Package | Current version | Published |
|---|---|---|---|
| Web core | @hawcx/core (npm) | 2.7.0 | 2026-10-10 |
| Web React | @hawcx/react (npm) | 2.7.0 | 2026-10-10 |
| Node.js backend | @hawcx/oauth-client (npm) | 6.0.0 | 2026-10-10 |
| Python backend | hawcx-oauth-client (PyPI) | 2.0.0 | 2026-10-10 |
| Java backend | com.hawcx:hawcx-java-sdk (Maven Central) | 0.5.0 | 2026-10-10 |
| iOS | hawcx_ios_sdk (Swift Package Manager) | 6.0.3 | 2026-04-13 |
| Android | api.hawcx:hawcx (Hawcx Maven repository) | 6.0.2 | 2026-04-07 |
| React Native | @hawcx/react-native-sdk (npm) | 1.1.5 | 2026-04-25 |
| Flutter | hawcx_flutter_sdk (pub.dev) | 1.0.5 | 2026-01-21 |
Web SDK
@hawcx/core 2.7.0 and @hawcx/react 2.7.0
Action needed: upgrade @hawcx/core and @hawcx/react to 2.7.0 together. If you
implement or mock the HawcxAuth interface yourself, add a forgetDevice member. And
check that apiBase is an https: URL unless it points at your own machine.
Security fixes from a third-party security assessment of the Hawcx SDKs:
client.forgetDevice(identifier?)deletes the device record and key pair this browser stored foridentifier, so the next sign-in with that identifier has to verify again instead of completing device trust silently. It defaults to the identifier that signed in on this client. It is a local storage wipe, not a sign-out, and it does not revoke the device server-side. SeeforgetDevice.client.signOut({ forgetDevice: true, identifier? })signs out and forgets the device in one call, and returns a promise that settles once the credentials are deleted.signOut()with no arguments is unchanged: synchronous, returns nothing. The newSignOutOptionstype is exported from both packages.- In React,
useAuthActions()anduseSession().actionsgainforgetDevice, and theirsignOutaccepts the same options. apiBaseis now checked by parsing the URL. It must usehttps:, except that plainhttp:is allowed when the host is exactlylocalhost,127.0.0.1or[::1]. A URL with credentials in it is rejected.createHawcxAuththrows if the check fails.- Device-credential storage keys are normalised in one place, so credentials for
identifiers such as mixed-case plus-addressed emails (
[email protected]) are found and cleared reliably. Keys match what enrollment already stored, so nothing is migrated.
Moving from 2.6.0
- Bump both packages in the same change.
@hawcx/react2.7.0 depends on@hawcx/core^2.7.0, because itsforgetDeviceandsignOut(options)call core APIs that 2.6.0 doesn't have. HawcxAuthgains a requiredforgetDevice(identifier?: string): Promise<void>member. Code that only calls the client is unaffected, but a class that implementsHawcxAuth, or a typed test mock of it, stops compiling until it adds one.- If
apiBasepoints at a development host over plainhttp:, move it tohttps:or to one of the three loopback hosts. 2.6.0 matched a string prefix, sohttp://localhost.example.testand*.localhosthosts over plainhttp:used to pass and now throw.@hawcx/angulardepends on@hawcx/core^2.4.0, so an Angular app picks up this check too once its lockfile resolves core 2.7.0.
@hawcx/core 2.6.0 and @hawcx/react 2.6.0
Action needed: upgrade @hawcx/core and @hawcx/react to 2.6.0 together. Apart from
that, the release is additive: everything the 2.4.x entry points exported is still
exported, so existing 2.4.x code keeps working.
Adds the device-revocation surface:
getAuthNotice(state)returns the server-driven notice riding along with the current step, ornull. It is exported from@hawcx/core.useAuthFlags()gainsnoticeanddeviceRevoked.deviceRevokedis true when the notice code isunauthorized_device, meaning this device's access was revoked and completing the flow re-enrolls it.client.logoutEverywhere(bearerToken)ends every active session for the user and resolves to{ sessionsEnded }. No device is de-enrolled, so the next sign-in on a trusted device is still silent.useAuthActions()exposes it aslogoutAllDevices.AuthResultand the completed state gain an optionalsessionToken, the hx_auth session JWT you pass tologoutEverywhere. It is present only when the server minted one.client.resumeSession(session, identifier?)andclient.getPkceVerifier()support flows that survive a page navigation.AuthConfiggains an optionalscope, the OIDC-standard space-delimited scope string your relying party wants to request (for example"openid profile email"). Leave it unset and the SDK omitsscopefrom the wire request entirely, so the request shape is unchanged from a client that never set it.
Moving from 2.4.x
Three things to get right when you upgrade:
- Bump both packages in the same change.
@hawcx/react2.6.0 declares a dependency on@hawcx/coreof^2.4.0, which a lockfile pinned to core 2.4.1 already satisfies; npm will therefore leave core alone if you bump only@hawcx/react. That combination fails at runtime, becauseuseAuthFlagsimportsgetAuthNoticefrom@hawcx/coreanduseAuthActionscallsclient.logoutEverywhere(...), and core 2.4.1 has neither. - If you render a sign-in form, handle the revoked-device notice. Reading it through
getAuthNotice(state)narrows the state for you, becausenoticeis declared only on the loading and step variants ofAuthState; readingstate.noticeon an un-narrowedAuthStateis a TypeScript error. In React,useAuthFlags().noticegives you the same value with no narrowing needed. Note that@hawcx/reactdoes not re-exportgetAuthNotice, so import it from@hawcx/coreif you want the function itself. - If your flow redirects (OAuth, social sign-in), persist
getPkceVerifier()before navigating, and hand the stashed value to your token exchange yourself. There is no API that puts it back:resumeSession()restores the session and identifier only, andgetCompletion()reads the verifier off the client instance, so it comes backundefinedon the resumed one. A resumed client cannot redeem the authorization code on its own.
If you see @hawcx/core 3.0.0 on npm
A 3.0.0 was published to npm and then deprecated. Its deprecation message reads
use @hawcx/core@^2.6.0 instead. 2.6.0 is the latest tag and the version to install.
Pin a ^2.6.0 range so an automated dependency bump does not pull 3.0.0 in.
Backend SDKs
The Node.js 6.0.0, Python 2.0.0 and Java 0.5.0 releases carry the same security fixes
from a third-party security assessment of the Hawcx SDKs. The headline change is the
same in all three: management API responses are now authenticated by default. Every
successful (2xx) response to a private_key_jwt management call carries an
X-Response-Signature JWS, and the SDK now verifies it before handing you the body,
where earlier releases left that to you. Verification detects a response that was
altered in transit, for example by a proxy that terminates TLS. The signing keys come
from {baseUrl}/.well-known/jwks.json by default, the same origin as the API, so it is
not a defence against someone who can impersonate that origin itself.
@hawcx/oauth-client 6.0.0
Action needed: this is a major release because it refuses responses and configurations that 5.2.0 accepted. Before upgrading:
- If a gateway, CDN or proxy sits between your backend and Hawcx, make sure it passes
the
X-Response-Signatureresponse header through. If it strips it, every management call fails withResponseSignatureInvalidError. - If your mocks, stubs or contract tests return unsigned 2xx management responses,
sign them, or set
verifyResponseSignature: falsein the test harness only. - If
baseUrlis not the Hawcx API host (an internal service URL, or local development with separate ports), setissuerso the SDK can find the JWKS. - Check every
baseUrlandissueryou pass against the URL rules below.
Response signatures, verified by default:
StepUpClient.withPrivateKeyJwt(...)andJwtBearerTransportverifyX-Response-Signatureon every successful response. This reverses the 5.2.0 note that callers "may" verify the header themselves. The JWS must use ES256, ES384 or ES512 and verify against{issuer}/.well-known/jwks.json; itsiatmust be withinclockSkewSeconds; itsbody_sha256must match the exact response bytes; and it must not carryaud,subornonce, so an ID token signed by the same key cannot pass as one. Responses with status 400 and above are not verified.- New options on both:
issuer(defaults tobaseUrl),verifyResponseSignature(defaults totrue) andclockSkewSeconds(defaults to 300). SettingverifyResponseSignature: falserestores the 5.2.0 behaviour and emits a one-time process warning. Treat it as an emergency escape hatch, not a setting. - Two new error classes, both subclasses of
DelegationResponseError, each with a stablecode.ResponseSignatureInvalidError(response_signature_invalid) means the signature is missing or does not verify.ResponseSignatureUnavailableError(response_signature_unavailable) means the signing keys could not be fetched. The server has already processed the request by then, so the operation may have been applied: reconcile state rather than retrying blindly.
URL validation:
- A
baseUrlorissuermust be anhttps:URL. Plainhttp:is accepted only forlocalhost,127.0.0.1and[::1], so values such as a docker-compose service name (http://hawcx-api:8080),http://host.docker.internal:8080or an in-clusterhttp:service URL now throw at construction. So does a base with userinfo, a query or a fragment (https://user:[email protected],https://api.hawcx.com?tenant=x). - This applies to
StepUpClient.withPrivateKeyJwtandJwtBearerTransport, the ECIESStepUpClientandDelegationClientfactories (all throwDelegationRequestError), the legacynew HawcxOAuth({ baseUrl })(HawcxOAuthError) andHawcxOAuth.fromIssuer(DiscoveryError). A discoveredtoken_endpointorjwks_urithat carries userinfo is rejected withDiscoveryError. - Request URLs are now joined structurally and must stay on the base's origin and
under its path prefix. An endpoint path that contains a percent-encoded
/,.or\(%2f,%2e,%5c, any case) is rejected. - Host case and default ports are normalised, which changes the signed
audfor a non-canonical base:baseUrl: 'https://API.Hawcx.com:443'now signsaud = https://api.hawcx.com/v1/.... A lower-case base without a default port produces the sameaudas before.
Fixed:
- In discovery mode (
HawcxOAuth.fromIssuer),exchangeCode()now always sends the configuredclientIdasclient_id, for public (PKCE) clients as well asprivate_key_jwtclients. Previously a public-client exchange left it out, so the server used the project's default client and set the ID tokenaudto that client's id. On a project with more than one public OIDC client, an app using a non-default client failed with "Unexpected JWT audience".X-Config-Idis still sent. Single-client projects and the legacynew HawcxOAuth({ configId, baseUrl })constructor are unaffected. ClientAssertionSigner.sign()now reads the clock once foriatandexp. Two separate reads could straddle a second boundary and make the assertion one second longer than its lifetime, so atwithLifetimeSeconds(120)the token exchange could be rejected for exceeding the server's 120-second limit.
Unchanged: clearCache() is still present (and still deprecated) in 6.0.0, despite
the 5.x notice that 6.0.0 would remove it. Its deprecation warning now says it will
be removed in a future major version. Use refreshJwks(). The ECIES fromSecretKey
and fromKeys factories also remain, deprecated.
hawcx-oauth-client 2.0.0 (Python)
Action needed: this is a major release because it refuses responses and
configurations that 1.3.0 accepted. Check the same things as for Node.js 6.0.0
above: header-stripping proxies, unsigned test doubles, and every base_url you
pass. If you still use the ECIES transport, read the last item below too.
StepUpClient.with_private_key_jwt(...)and theJwtBearerTransport/JwtBearerTransportAsyncclasses verifyX-Response-Signatureon every 2xx response, with the same checks as Node.js. A missing or invalid signature raises the newResponseSignatureError, a subclass ofDelegationResponseError, exported from the package root.with_private_key_jwtand the JWT transports gainverify_response_signature(defaultTrue) andjwks_url(default{base_url}/.well-known/jwks.json). The transports also takeclock_tolerance_seconds(default 10), extra leeway on top of the fixed 300-secondiatwindow.verify_response_signature=Falseis an emergency escape hatch and logs a warning once per process.- A
base_urlmust behttps:, orhttp:tolocalhost,127.0.0.1or[::1], and may not carry userinfo, a query, a fragment, a backslash, whitespace or non-ASCII characters, a percent-encoded host, port 0, or a./..path segment. Violations raiseValueErrorat construction, fromwith_private_key_jwt,from_secret_key,from_keys, the transports, andHawcx/HawcxAsync. - Endpoints and
api_prefixmust be/-rooted paths with no./..segments (literal or%2e) and no encoded/or\(%2f,%5c). An unsafe endpoint raisesDelegationRequestErrorbefore anything is sent; an unsafeStepUpClient(api_prefix=...)raisesValueError. - Host case and the default port are normalised when origins are compared, but the request URL itself is sent as configured, so a valid configuration sends the same URLs as before.
- The legacy ECIES transport (
StepUpClient.from_secret_key,StepUpClient.from_keys,Hawcx,HawcxAsync) now rejects a 2xx response that is missing any of its signature headers (X-Signature,X-Signature-Alg,X-Enc-Alg,X-Timestamp) withDelegationResponseError("Missing signature headers in IdP response"). 1.3.0 returned such a body unverified. Unsigned error responses still raiseDelegationRequestErrorwith their status, as before. - Fixed:
HawcxOAuth/HawcxOAuthAsyncnow sendclient_idon every authorization-code exchange, not only when aprivate_key_jwtsigner is attached. Previously a public (PKCE) exchange left it out, so the server used the project's default client and set the ID tokenaudto that client's id, andexchange_coderejected the token with "Audience doesn't match" for an app using a non-default client on a project with more than one public OIDC client.X-Config-Idis unchanged, and single-client projects are unaffected. - The
httpxdependency is nowhttpx>=0.27.0,<1.0. httpx 1.0, in prerelease at the time of this release, removeshttpx.InvalidURLand parses URLs differently, so an install that resolved it failed every request.
com.hawcx:hawcx-java-sdk 0.5.0
Action needed: response-signature verification is now on by default, so calls that
0.4.0 accepted can fail. As a 0.x release this is a minor version bump, but treat it as
breaking. Check for header-stripping proxies and unsigned test doubles, and make sure
the issuer JWKS is reachable at {baseUrl}/.well-known/jwks.json or configure
issuer / jwksUrl. Also, HawcxOauthIssuerConfig.builder().build() now throws
IllegalArgumentException("<name> is required") when issuer, configId or
clientId is null or blank. 0.4.0 threw a NullPointerException for a null value and
accepted a blank one (a blank clientId only failed later, as an ID token audience
mismatch), so make sure all three are set.
JwtBearerTransport, and soStepUpClient.withPrivateKeyJwt(...), verifiesX-Response-Signatureon every 2xx response, with the same checks as Node.js. A missing or invalid signature throws the newResponseSignatureException, a subclass ofDelegationResponseException. The response body is not returned. Verification runs after the server has processed the request, so the exception does not mean the operation did not happen.- New
JwtBearerTransport.Builderoptions:verifyResponseSignature(boolean)(defaulttrue;falselogs a warning),issuer(String)(defaults tobaseUrl),jwksUrl(String)(overridesissuer),clockSkewSeconds(long)(default 300;0means strict, a negative value throwsIllegalArgumentException, andbuild()rejects values above 3600) andjwksCache(JwksCache). Pass the built transport toStepUpClient.withPrivateKeyJwt(JwtBearerTransport)to use them. - With verification on,
build()throwsIllegalArgumentExceptionif the JWKS URL is nothttps:(plainhttp:is allowed for loopback only). JwksCache.refresh(String)forces a re-fetch of a cached JWKS.- Fixed: discovery-mode
exchangeCodeForIdTokenandexchangeCode(ExchangeOptions)now always sendclient_idin the token request. Previously it was sent only when aClientAssertionSignerwas set, so a public-client (PKCE) exchange made the server fall back to the project's default client, and ID token verification failed with an audience mismatch for a non-default public client on a project with more than one. Thex-config-idheader,private_key_jwtrequests and the legacyHawcxOauthClient(HawcxOauthConfig)mode are unchanged; single-client projects are unaffected.
@hawcx/oauth-client 5.2.0
Action needed: two things, neither urgent. Rename clearCache() to refreshJwks() if
you call it, before 6.0.0 removes the old name. And if you build a StepUpClient from an
ECIES secret key, start planning a move to private_key_jwt.
StepUpClient.withPrivateKeyJwt(...)authenticates management calls with a per-request EdDSA bearer JWT, reusing the Ed25519 key already registered for OIDC login. The JWT is bound to the exact request body through abody_sha256claim.StepUpClient.fromSecretKey(...)andStepUpClient.fromKeys(...), the ECIEShwx_sk_v1_factories, are now deprecated. They emit a one-time deprecation warning, remain fully functional, and will be removed no earlier than the next major version.- From 5.1.0:
refreshJwks()is the canonical JWKS-cache reset, aligning with the JavarefreshJwks()and the Pythonrefresh_jwks(). clearCache()is deprecated as of 5.1.0 and will be removed in 6.0.0. Its behaviour is identical torefreshJwks(); only the name changed.
hawcx-oauth-client 1.3.0 (Python)
Action needed: nothing breaks, but if you build a StepUpClient from an ECIES secret
key, plan a move to private_key_jwt.
StepUpClient.with_private_key_jwt(...)is the recommended constructor, matching the Node.js 5.2.0 change above.StepUpClient.from_secret_key(...)andStepUpClient.from_keys(...)still work but now raise aDeprecationWarningand will be removed in a future major version.
The step-up surface itself is unchanged: start_token, consume_receipt, and
management_request.
hawcx-oauth-client 1.2.0 (Python)
Action needed: this one breaks imports. 1.2.0 replaced most of the 1.0.0 public
surface rather than extending it, so plan the upgrade instead of running a blind
pip install --upgrade. There was no 1.1.x, so 1.0.0 is the version you are coming from.
Gone from 1.0.0:
HawcxDelegationClientandMfaMethod, both imported fromhawcx_oauth_client.delegation, plus the lower-levelIdpDelegationClient. The/hc_auth/v5/*endpoints they called were removed from hx_auth. UseStepUpClientagainst the current/v1/management/*endpoints instead:start_token,consume_receipt, andmanagement_requestcover the same ground.- The bootstrap client.
HawcxBootstrapClient,BootstrapToken,DPoPSession, and theBootstrapError,BootstrapChallengeError,BootstrapExchangeErrorandBootstrapSigningErrorexceptions all went with thehawcx_oauth_client.bootstrappackage. - The loose OAuth helpers
exchange_code_for_claims,exchange_code_for_token_and_claims,verify_jwt,require_oauth_claims,fetch_jwksandget_key_from_jwks. TheHawcxOAuthclient below replaces the whole group. build_assertion_and_dpopandAssertionResult. The 1.2.0 wheel still contains anassertion.py, but it cannot be imported: it depends on ahawcx_oauth_client._cryptomodule that the wheel no longer ships, so importing it raisesModuleNotFoundError. Treat both symbols as gone.ClientAssertionSigner, exported from the package root, is the supported replacement from 1.2.0 onward.
What replaces the OAuth half:
HawcxOAuth.from_issuer(issuer, config_id, client_id)is the entry point for the OAuth relying-party client. It discovers the OAuth endpoints from<issuer>/.well-known/openid-configurationand wires a verifier that enforces the signature plusiss,aud,exp, andnbfon every verify.- Use
from_issuerrather than callingHawcxOAuth(...)yourself. The constructor is keyword-only and expects aDiscoveryMetadataobject you would have to build first, sofrom_issueris the only supported way in.
com.hawcx:hawcx-java-sdk 0.4.0
Action needed: nothing breaks, but if you build a StepUpClient from an ECIES secret
key, plan a move to private_key_jwt. This is the same deprecation as Node.js 5.2.0 and
Python 1.3.0.
StepUpClient.withPrivateKeyJwt(...)authenticates management-API calls with a per-request EdDSA bearer JWT, the Java side of the same change as Node.js 5.2.0 and Python 1.3.0.- All four
fromSecretKeyandfromKeysoverloads are now annotated@Deprecated. They still work; only the recommendation changed. JwtBearerTransportis the new transport behind it. It sends the config ID under a single header, because HTTP header names are case-insensitive and setting bothX-Config-Idandx-config-idappended a second value to the same header, which made gateways doing key-auth on that header reject the request with "Duplicate API key found".
Mobile SDKs
iOS 6.0.3, Android 6.0.2, React Native 1.1.5
The V6 generation adds an adaptive, server-driven flow: the server chooses the primary and MFA methods, and your app renders each step it is given.
Moving from V4/V5: V6 is additive rather than a forced cutover. The V6 releases ship a
new adaptive surface (HawcxV1SDK on iOS, the com.hawcx.protocol.v1 classes reached
through buildProtocolV1Sdk on Android) alongside the existing V4/V5 HawcxSDK APIs, so
an app already in production can migrate screen by screen. The 5.x releases do not contain
that adaptive surface at all, which is why V6 is a version bump rather than a flag. Both
generations take a tenant base URL at initialization, so moving to V6 is not what unlocks
pointing at a non-production tenant. See the
iOS and
Android guides.
React Native gained the V6 adaptive flow in 1.1.0; 1.1.5 is the current release.
Flutter 1.0.5
Flutter bridges to the v5-generation native SDK: the published package depends on
api.hawcx:hawcx:5.1.4 on Android. The V6 adaptive flow is not yet available for Flutter.
Something here not matching what you see? Get support.
Last updated on