Java SDK API Reference

Complete API reference for the Hawcx Java SDK

Java SDK API Reference

All public types live under com.hawcx. Add the dependency:

<dependency>
    <groupId>com.hawcx</groupId>
    <artifactId>hawcx-java-sdk</artifactId>
    <version>0.3.0</version>
</dependency>

The SDK exposes two construction paths on HawcxOauthClient:

PathConstructorWhen to use
Discovery (recommended)HawcxOauthClient.fromIssuer(issuerUrl, configId, clientId)New code. Discovers /.well-known/openid-configuration once at startup and wires a stock-Nimbus IDTokenValidator.
Legacynew HawcxOauthClient(HawcxOauthConfig)v0.2.x callers. Hits fixed endpoints /oauth2/token and /keys directly; verification through the homegrown JwtVerifier.

Both modes are thread-safe. Cross-mode method calls raise IllegalStateException with a message pointing at the correct constructor.


HawcxOauthClient (discovery mode)

import com.hawcx.oauth.HawcxOauthClient;

HawcxOauthClient client = HawcxOauthClient.fromIssuer(
        System.getenv("HAWCX_BASE_URL"),    // issuer URL
        System.getenv("HAWCX_CONFIG_ID"),   // → x-config-id header on token exchange
        System.getenv("HAWCX_CLIENT_ID"));  // → expected aud on id_tokens

fromIssuer(String issuerUrl, String configId, String clientId)

Fetches <issuerUrl>/.well-known/openid-configuration, reads token_endpoint, jwks_uri, and id_token_signing_alg_values_supported, and builds an IDTokenValidator that enforces signature, iss, aud, exp, and nbf on every subsequent verifyIdToken call.

ParameterPurpose
issuerUrlHawcx issuer URL (e.g. https://api.hawcx.com). Also matched as the iss claim.
configIdHawcx tenant routing key. Sent as x-config-id on the token-exchange POST.
clientIdThe value the tenant has configured (Admin Console) as the aud claim on its id_tokens.

configId and clientId are distinct

configId routes the token exchange (header); clientId is matched against the aud claim. They are configured separately in the Admin Console and may be different strings. Passing the same value for both fails verification with Unexpected JWT audience unless the tenant has configured them identically.

Discovery is bounded at 5s connect + 5s read. An unreachable issuer fails fast with HawcxOauthException (error code discovery_failed) rather than hanging the constructor. A malformed discovery document (missing token_endpoint, jwks_uri, or id_token_signing_alg_values_supported) fails with invalid_discovery_document. The Nimbus exception is preserved as getCause().

exchangeCodeForIdToken(String authCode, String codeVerifier)

Exchange the authorization code for the raw id_token (a signed JWT string). The token has not yet been verified — pair with verifyIdToken.

String idToken = client.exchangeCodeForIdToken(authCode, codeVerifier);

exchangeCodeForIdToken(String authCode, String codeVerifier, String redirectUri)

Same as above plus the redirect_uri form field, required by RFC 6749 §4.1.3 when the authorization request registered one. Pass null to omit (only valid if the authorization request also omitted it).

String idToken = client.exchangeCodeForIdToken(authCode, codeVerifier,
                                                "https://app.example.com/cb");

verifyIdToken(String idToken)

Verify the id_token's signature and standard OIDC claims:

  • iss — matched against the issuer in the resolved discovery document
  • aud — matched against the clientId passed to fromIssuer (not configId)
  • exp, nbf — enforced

Returns the parsed claim set. Throws JwtVerificationException on any failure with the Nimbus exception preserved as getCause().

import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;

IDTokenClaimsSet claims = client.verifyIdToken(idToken);
String userId = claims.getSubject().getValue();
String email  = claims.getStringClaim("email");

verifyIdToken(String idToken, String expectedNonce)

Same as above plus a nonce claim check. Pass null to skip the nonce check (equivalent to the single-arg overload).

IDTokenClaimsSet claims = client.verifyIdToken(idToken, expectedNonce);

getDiscoveryMetadata()

The Nimbus OIDCProviderMetadata resolved at construction time. Useful for diagnostics — confirm which endpoints and signing algs were picked up.

client.getDiscoveryMetadata().getTokenEndpointURI();
client.getDiscoveryMetadata().getJWKSetURI();
client.getDiscoveryMetadata().getIDTokenJWSAlgs();

Returns null if the client was constructed via the legacy constructor.


HawcxOauthClient (legacy mode)

For v0.2.x callers; new code should prefer the discovery path above. Thread-safe; construct once and reuse.

import com.hawcx.oauth.HawcxOauthClient;
import com.hawcx.oauth.HawcxOauthConfig;

HawcxOauthConfig config = new HawcxOauthConfig(System.getenv("HAWCX_BASE_URL"));
HawcxOauthClient client = new HawcxOauthClient(config);

HawcxOauthConfig exposes two endpoint helpers:

  • config.tokenEndpoint()<baseUrl>/oauth2/token
  • config.keysEndpoint()<baseUrl>/keys (use this as the jwksUrl for JwtVerifier.VerifyOptions)

exchangeCode(TokenRequest)

Exchange an authorization code for an id_token without verifying it. Useful when you only need the raw token. Most apps should use exchangeCodeForClaims instead.

TokenRequest req = new TokenRequest(configId, authCode, codeVerifier);
TokenResponse resp = client.exchangeCode(req);
String idToken   = resp.getIdToken();
String tokenType = resp.getTokenType();
long   expiresIn = resp.getExpiresIn();

exchangeCodeForClaims(TokenRequest, VerifyOptions)

Exchange the code, verify the returned id_token, and return the claims. The recommended end-to-end entry point.

JsonObject claims = client.exchangeCodeForClaims(req, opts);
String userId = claims.get("sub").getAsString();

exchangeCodeForTokenAndClaims(TokenRequest, VerifyOptions)

Same as above but also returns the raw TokenResponse, useful when you need the id_token itself (e.g. to set as a cookie):

HawcxOauthClient.TokenAndClaims result = client.exchangeCodeForTokenAndClaims(req, opts);
String     idToken = result.tokenResponse().getIdToken();
JsonObject claims  = result.claims();

verifyJwt(String token, VerifyOptions)

Standalone JWT verification, useful when you already have a token (e.g. from a session cookie) and just want to validate it.

JsonObject claims = client.verifyJwt(idToken, opts);

getKeys(TokenRequest)

Fetch the raw JWKS document for inspection. Most callers don't need this; JwtVerifier fetches and caches JWKS internally via jwksUrl.

KeysResponse keys = client.getKeys(req);
JsonObject jwks   = keys.getJwks();

TokenRequest

Value object describing a single token exchange.

// Without redirect URI:
new TokenRequest(configId, authCode, codeVerifier);

// With OAuth 2.0 redirect URI (required by some providers):
new TokenRequest(configId, authCode, codeVerifier,
                 "https://app.example.com/auth/callback");

JwtVerifier.VerifyOptions

Builder-style options controlling JWT verification. Build one per audience/issuer pair and reuse.

import com.hawcx.oauth.JwtVerifier;

JwtVerifier.VerifyOptions opts = JwtVerifier.VerifyOptions.builder()
    .jwksUrl(config.keysEndpoint())     // OR .publicKeyPem("-----BEGIN PUBLIC KEY-----...")
    .audience(System.getenv("HAWCX_CONFIG_ID"))
    .issuer(System.getenv("HAWCX_BASE_URL"))
    .verifyExp(true)                    // default true
    .leewaySeconds(10)                  // default 0; tolerate small clock skew
    .algorithms(java.util.Set.of(JWSAlgorithm.RS256))   // default: RS256, ES256/384/512, EdDSA
    .build();
MethodDefaultNotes
jwksUrl(String)(none)Either this or publicKeyPem must be set. JWKS is fetched once per TTL window (default 1h) and cached.
publicKeyPem(String)(none)Static PEM key. Use for fixed-key setups or tests.
audience(String)not validatedExpected aud claim. Set this in production.
issuer(String)not validatedExpected iss claim. Set this in production.
verifyExp(boolean)trueDisable only for unit tests.
leewaySeconds(long)0Tolerated clock skew, in seconds.
algorithms(Set<JWSAlgorithm>)RS256, ES256, ES384, ES512, EdDSAOnly tokens signed with one of these algorithms are accepted.

JwksCache

In-memory TTL cache for JWKS documents. Defaulted automatically by HawcxOauthClient. Override only if you need a custom TTL or want to share a cache across clients:

import com.hawcx.oauth.JwksCache;
import com.hawcx.oauth.JwtVerifier;

JwksCache cache = new JwksCache(/* ttlSeconds */ 600);
JwtVerifier verifier = new JwtVerifier(cache);
HawcxOauthClient client = new HawcxOauthClient(config, verifier);

Delegation Client

Optional: advanced flows only

The delegation client is only required for backend-driven MFA setup/verify, suggested-MFA policy, device management, and step-up authentication. Apps that only need OAuth code exchange can skip this section.

Backend-driven MFA setup, user management, and device management. Every request is automatically encrypted (ECIES X25519 + AES-GCM) and signed (Ed25519 / RSA-PSS). Construct once and reuse; thread-safe.

DelegationClient.fromSecretKey(baseUrl, secretKey, configId)

import com.hawcx.delegation.DelegationClient;

DelegationClient client = DelegationClient.fromSecretKey(
    System.getenv("HAWCX_BASE_URL"),
    System.getenv("HAWCX_SECRET_KEY"),    // hwx_sk_v1_...
    System.getenv("HAWCX_CONFIG_ID")
);

Or with a raw KeyMaterial bundle:

DelegationClient client = DelegationClient.fromKeys(
    baseUrl,
    KeyMaterial.builder()
        .spSigningKey(spEd25519PrivateKeyPem)
        .spEncryptionKey(spX25519PrivateKeyPem)
        .idpVerifyKey(idpEd25519PublicKeyPem)
        .idpEncryptionKey(idpX25519PublicKeyPem)
        .build(),
    configId
);

MFA

import com.google.gson.JsonObject;
import com.hawcx.delegation.MfaMethod;

// Initiate (Email / SMS / TOTP)
JsonObject init = client.mfa.initiate(
    "[email protected]",
    MfaMethod.SMS,
    "+15551234567",   // null for EMAIL/TOTP
    null              // optional target method when changing
);
String sessionId = init.get("session_id").getAsString();

// Verify the OTP and complete the change
client.mfa.verify("[email protected]", sessionId, "123456");

Users

JsonObject creds = client.users.getCredentials("[email protected]");

// Suggested-MFA preference: "none", "always", or "risk_only"
client.users.setSuggestedMfa("[email protected]", "always");
JsonObject pref = client.users.getSuggestedMfa("[email protected]");

Devices

JsonObject devices = client.devices.list("[email protected]");

client.devices.revoke("[email protected]",   "device-h2index");
client.devices.unrevoke("[email protected]", "device-h2index");
client.devices.delete("[email protected]",   "device-h2index");

Generic request

For endpoints not covered by a typed namespace method:

JsonObject body = new JsonObject();
body.addProperty("userid", "[email protected]");
JsonObject response = client.request("/v1/management/users/phone", body);

Exception Hierarchy

Every SDK error extends com.hawcx.HawcxException (which extends RuntimeException). Catch the root if you only want one handler:

HawcxException
├── HawcxOauthException             : token exchange failures (network, 4xx/5xx, missing id_token)
├── JwtVerificationException        : signature / claims validation failures
├── InvalidPublicKeyException       : JWKS unreachable, no usable keys, malformed PEM
└── DelegationException             : base for delegation errors
    ├── DelegationCryptoException   : ECIES / signature operation failures
    ├── DelegationRequestException  : HTTP failure; carries statusCode + responseBody
    └── DelegationResponseException : response signature, timestamp, or decryption checks failed
try {
    JsonObject claims = client.exchangeCodeForClaims(req, opts);
} catch (HawcxException e) {
    log.error("Hawcx SDK call failed", e);
}