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

SDKPackageCurrent versionPublished
Web core@hawcx/core (npm)2.7.02026-10-10
Web React@hawcx/react (npm)2.7.02026-10-10
Node.js backend@hawcx/oauth-client (npm)6.0.02026-10-10
Python backendhawcx-oauth-client (PyPI)2.0.02026-10-10
Java backendcom.hawcx:hawcx-java-sdk (Maven Central)0.5.02026-10-10
iOShawcx_ios_sdk (Swift Package Manager)6.0.32026-04-13
Androidapi.hawcx:hawcx (Hawcx Maven repository)6.0.22026-04-07
React Native@hawcx/react-native-sdk (npm)1.1.52026-04-25
Flutterhawcx_flutter_sdk (pub.dev)1.0.52026-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 for identifier, 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. See forgetDevice.
  • 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 new SignOutOptions type is exported from both packages.
  • In React, useAuthActions() and useSession().actions gain forgetDevice, and their signOut accepts the same options.
  • apiBase is now checked by parsing the URL. It must use https:, except that plain http: is allowed when the host is exactly localhost, 127.0.0.1 or [::1]. A URL with credentials in it is rejected. createHawcxAuth throws 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

  1. Bump both packages in the same change. @hawcx/react 2.7.0 depends on @hawcx/core ^2.7.0, because its forgetDevice and signOut(options) call core APIs that 2.6.0 doesn't have.
  2. HawcxAuth gains a required forgetDevice(identifier?: string): Promise<void> member. Code that only calls the client is unaffected, but a class that implements HawcxAuth, or a typed test mock of it, stops compiling until it adds one.
  3. If apiBase points at a development host over plain http:, move it to https: or to one of the three loopback hosts. 2.6.0 matched a string prefix, so http://localhost.example.test and *.localhost hosts over plain http: used to pass and now throw. @hawcx/angular depends 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, or null. It is exported from @hawcx/core.
  • useAuthFlags() gains notice and deviceRevoked. deviceRevoked is true when the notice code is unauthorized_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 as logoutAllDevices.
  • AuthResult and the completed state gain an optional sessionToken, the hx_auth session JWT you pass to logoutEverywhere. It is present only when the server minted one.
  • client.resumeSession(session, identifier?) and client.getPkceVerifier() support flows that survive a page navigation.
  • AuthConfig gains an optional scope, the OIDC-standard space-delimited scope string your relying party wants to request (for example "openid profile email"). Leave it unset and the SDK omits scope from 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:

  1. Bump both packages in the same change. @hawcx/react 2.6.0 declares a dependency on @hawcx/core of ^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, because useAuthFlags imports getAuthNotice from @hawcx/core and useAuthActions calls client.logoutEverywhere(...), and core 2.4.1 has neither.
  2. If you render a sign-in form, handle the revoked-device notice. Reading it through getAuthNotice(state) narrows the state for you, because notice is declared only on the loading and step variants of AuthState; reading state.notice on an un-narrowed AuthState is a TypeScript error. In React, useAuthFlags().notice gives you the same value with no narrowing needed. Note that @hawcx/react does not re-export getAuthNotice, so import it from @hawcx/core if you want the function itself.
  3. 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, and getCompletion() reads the verifier off the client instance, so it comes back undefined on 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-Signature response header through. If it strips it, every management call fails with ResponseSignatureInvalidError.
  • If your mocks, stubs or contract tests return unsigned 2xx management responses, sign them, or set verifyResponseSignature: false in the test harness only.
  • If baseUrl is not the Hawcx API host (an internal service URL, or local development with separate ports), set issuer so the SDK can find the JWKS.
  • Check every baseUrl and issuer you pass against the URL rules below.

Response signatures, verified by default:

  • StepUpClient.withPrivateKeyJwt(...) and JwtBearerTransport verify X-Response-Signature on 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; its iat must be within clockSkewSeconds; its body_sha256 must match the exact response bytes; and it must not carry aud, sub or nonce, 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 to baseUrl), verifyResponseSignature (defaults to true) and clockSkewSeconds (defaults to 300). Setting verifyResponseSignature: false restores 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 stable code. 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 baseUrl or issuer must be an https: URL. Plain http: is accepted only for localhost, 127.0.0.1 and [::1], so values such as a docker-compose service name (http://hawcx-api:8080), http://host.docker.internal:8080 or an in-cluster http: 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.withPrivateKeyJwt and JwtBearerTransport, the ECIES StepUpClient and DelegationClient factories (all throw DelegationRequestError), the legacy new HawcxOAuth({ baseUrl }) (HawcxOAuthError) and HawcxOAuth.fromIssuer (DiscoveryError). A discovered token_endpoint or jwks_uri that carries userinfo is rejected with DiscoveryError.
  • 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 aud for a non-canonical base: baseUrl: 'https://API.Hawcx.com:443' now signs aud = https://api.hawcx.com/v1/.... A lower-case base without a default port produces the same aud as before.

Fixed:

  • In discovery mode (HawcxOAuth.fromIssuer), exchangeCode() now always sends the configured clientId as client_id, for public (PKCE) clients as well as private_key_jwt clients. Previously a public-client exchange left it out, so the server used the project's default client and set the ID token aud to 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-Id is still sent. Single-client projects and the legacy new HawcxOAuth({ configId, baseUrl }) constructor are unaffected.
  • ClientAssertionSigner.sign() now reads the clock once for iat and exp. Two separate reads could straddle a second boundary and make the assertion one second longer than its lifetime, so at withLifetimeSeconds(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 the JwtBearerTransport / JwtBearerTransportAsync classes verify X-Response-Signature on every 2xx response, with the same checks as Node.js. A missing or invalid signature raises the new ResponseSignatureError, a subclass of DelegationResponseError, exported from the package root.
  • with_private_key_jwt and the JWT transports gain verify_response_signature (default True) and jwks_url (default {base_url}/.well-known/jwks.json). The transports also take clock_tolerance_seconds (default 10), extra leeway on top of the fixed 300-second iat window. verify_response_signature=False is an emergency escape hatch and logs a warning once per process.
  • A base_url must be https:, or http: to localhost, 127.0.0.1 or [::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 raise ValueError at construction, from with_private_key_jwt, from_secret_key, from_keys, the transports, and Hawcx / HawcxAsync.
  • Endpoints and api_prefix must be /-rooted paths with no . / .. segments (literal or %2e) and no encoded / or \ (%2f, %5c). An unsafe endpoint raises DelegationRequestError before anything is sent; an unsafe StepUpClient(api_prefix=...) raises ValueError.
  • 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) with DelegationResponseError("Missing signature headers in IdP response"). 1.3.0 returned such a body unverified. Unsigned error responses still raise DelegationRequestError with their status, as before.
  • Fixed: HawcxOAuth / HawcxOAuthAsync now send client_id on every authorization-code exchange, not only when a private_key_jwt signer is attached. Previously a public (PKCE) exchange left it out, so the server used the project's default client and set the ID token aud to that client's id, and exchange_code rejected 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-Id is unchanged, and single-client projects are unaffected.
  • The httpx dependency is now httpx>=0.27.0,<1.0. httpx 1.0, in prerelease at the time of this release, removes httpx.InvalidURL and 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 so StepUpClient.withPrivateKeyJwt(...), verifies X-Response-Signature on every 2xx response, with the same checks as Node.js. A missing or invalid signature throws the new ResponseSignatureException, a subclass of DelegationResponseException. 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.Builder options: verifyResponseSignature(boolean) (default true; false logs a warning), issuer(String) (defaults to baseUrl), jwksUrl(String) (overrides issuer), clockSkewSeconds(long) (default 300; 0 means strict, a negative value throws IllegalArgumentException, and build() rejects values above 3600) and jwksCache(JwksCache). Pass the built transport to StepUpClient.withPrivateKeyJwt(JwtBearerTransport) to use them.
  • With verification on, build() throws IllegalArgumentException if the JWKS URL is not https: (plain http: is allowed for loopback only).
  • JwksCache.refresh(String) forces a re-fetch of a cached JWKS.
  • Fixed: discovery-mode exchangeCodeForIdToken and exchangeCode(ExchangeOptions) now always send client_id in the token request. Previously it was sent only when a ClientAssertionSigner was 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. The x-config-id header, private_key_jwt requests and the legacy HawcxOauthClient(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 a body_sha256 claim.
  • StepUpClient.fromSecretKey(...) and StepUpClient.fromKeys(...), the ECIES hwx_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 Java refreshJwks() and the Python refresh_jwks().
  • clearCache() is deprecated as of 5.1.0 and will be removed in 6.0.0. Its behaviour is identical to refreshJwks(); 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(...) and StepUpClient.from_keys(...) still work but now raise a DeprecationWarning and 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:

  • HawcxDelegationClient and MfaMethod, both imported from hawcx_oauth_client.delegation, plus the lower-level IdpDelegationClient. The /hc_auth/v5/* endpoints they called were removed from hx_auth. Use StepUpClient against the current /v1/management/* endpoints instead: start_token, consume_receipt, and management_request cover the same ground.
  • The bootstrap client. HawcxBootstrapClient, BootstrapToken, DPoPSession, and the BootstrapError, BootstrapChallengeError, BootstrapExchangeError and BootstrapSigningError exceptions all went with the hawcx_oauth_client.bootstrap package.
  • The loose OAuth helpers exchange_code_for_claims, exchange_code_for_token_and_claims, verify_jwt, require_oauth_claims, fetch_jwks and get_key_from_jwks. The HawcxOAuth client below replaces the whole group.
  • build_assertion_and_dpop and AssertionResult. The 1.2.0 wheel still contains an assertion.py, but it cannot be imported: it depends on a hawcx_oauth_client._crypto module that the wheel no longer ships, so importing it raises ModuleNotFoundError. 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-configuration and wires a verifier that enforces the signature plus iss, aud, exp, and nbf on every verify.
  • Use from_issuer rather than calling HawcxOAuth(...) yourself. The constructor is keyword-only and expects a DiscoveryMetadata object you would have to build first, so from_issuer is 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 fromSecretKey and fromKeys overloads are now annotated @Deprecated. They still work; only the recommendation changed.
  • JwtBearerTransport is the new transport behind it. It sends the config ID under a single header, because HTTP header names are case-insensitive and setting both X-Config-Id and x-config-id appended 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