Documentation
/
SDK Reference
/
Backend
/
Java
/
Integrate in Existing Java Project

Integrate in Existing Java Project

Add Hawcx authentication to an existing Java application

Integrating Hawcx Backend SDK into Existing Java Projects

This guide walks through adding Hawcx OAuth authentication to an existing Java backend (Spring Boot is shown, but the same pattern works for any framework).

Step 1: Add the SDK Dependency

The SDK is on Maven Central. Java 21+ is required.

Maven:

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

Gradle (Kotlin DSL):

implementation("com.hawcx:hawcx-java-sdk:0.2.0")

Step 2: Configure Environment Variables

Add the following to application.properties (Spring Boot), application.yml, or your environment:

# Config ID, from Admin Console → Project Settings
hawcx.config-id=<Config ID from Admin Console>
# Base URL, from Admin Console → Project Settings (environment-specific)
hawcx.base-url=<Base URL from Admin Console>

# Optional (for delegation / MFA management):
hawcx.secret-key=hwx_sk_v1_...

Where to find these values: Open the Hawcx Admin Console, go to Project Settings, and copy both the Config ID and Base URL. The base URL is unique to your environment; there is no universal default.

Step 3: Wire the OAuth Client as a Bean

Create a configuration class so the OAuth client and its verifier options are constructed once and reused (HawcxOauthClient and JwtVerifier are thread-safe):

import com.hawcx.oauth.HawcxOauthClient;
import com.hawcx.oauth.HawcxOauthConfig;
import com.hawcx.oauth.JwtVerifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HawcxConfiguration {

    @Bean
    public HawcxOauthConfig hawcxOauthConfig(@Value("${hawcx.base-url}") String baseUrl) {
        return new HawcxOauthConfig(baseUrl);
    }

    @Bean
    public HawcxOauthClient hawcxOauthClient(HawcxOauthConfig config) {
        return new HawcxOauthClient(config);
    }

    @Bean
    public JwtVerifier.VerifyOptions hawcxVerifyOptions(
            HawcxOauthConfig config,
            @Value("${hawcx.config-id}") String configId,
            @Value("${hawcx.base-url}") String baseUrl) {
        return JwtVerifier.VerifyOptions.builder()
                .jwksUrl(config.keysEndpoint())
                .audience(configId)
                .issuer(baseUrl)
                .leewaySeconds(10)
                .build();
    }
}

Step 4: Create an Exchange Endpoint

import com.google.gson.JsonObject;
import com.hawcx.HawcxException;
import com.hawcx.oauth.HawcxOauthClient;
import com.hawcx.oauth.JwtVerifier;
import com.hawcx.oauth.TokenRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
public class AuthController {

    private final HawcxOauthClient client;
    private final JwtVerifier.VerifyOptions verifyOpts;
    private final String configId;
    private final UserService userService;
    private final SessionService sessionService;

    public AuthController(HawcxOauthClient client,
                          JwtVerifier.VerifyOptions verifyOpts,
                          @Value("${hawcx.config-id}") String configId,
                          UserService userService,
                          SessionService sessionService) {
        this.client = client;
        this.verifyOpts = verifyOpts;
        this.configId = configId;
        this.userService = userService;
        this.sessionService = sessionService;
    }

    public record ExchangeRequest(String authCode, String codeVerifier) {}

    @PostMapping("/exchange")
    public ResponseEntity<?> exchange(@RequestBody ExchangeRequest body) {
        if (body.authCode() == null || body.codeVerifier() == null) {
            return ResponseEntity.badRequest()
                    .body(java.util.Map.of("error", "Missing authCode or codeVerifier"));
        }

        try {
            TokenRequest req = new TokenRequest(configId, body.authCode(), body.codeVerifier());
            JsonObject claims = client.exchangeCodeForClaims(req, verifyOpts);

            // Find or create user in your database
            User user = userService.findOrCreate(
                    claims.get("sub").getAsString(),
                    claims.has("email") ? claims.get("email").getAsString() : null
            );

            // Create your application's session/JWT
            String sessionToken = sessionService.generateToken(user);

            return ResponseEntity.ok(java.util.Map.of(
                    "success", true,
                    "sessionToken", sessionToken,
                    "user", java.util.Map.of(
                            "id", user.getId(),
                            "email", user.getEmail()
                    )
            ));
        } catch (HawcxException e) {
            return ResponseEntity.status(401)
                    .body(java.util.Map.of("error", "Authentication failed"));
        }
    }
}

Step 5: Integrate with Your User Management

Update your user service to handle Hawcx identities. The claims.get("sub") value is the stable Hawcx user identifier; store it on your user records.

import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository repo;

    public UserService(UserRepository repo) {
        this.repo = repo;
    }

    public User findOrCreate(String hawcxId, String email) {
        return repo.findByHawcxId(hawcxId)
                .orElseGet(() -> repo.save(new User(hawcxId, email)));
    }
}

Optional: Backend-Driven MFA Management

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. If your backend only needs the OAuth code exchange shown above, you can stop at Step 5.

If you need to manage MFA from your backend, add a DelegationClient bean and inject it where needed:

import com.hawcx.delegation.DelegationClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HawcxDelegationConfiguration {

    @Bean
    public DelegationClient hawcxDelegationClient(
            @Value("${hawcx.base-url}") String baseUrl,
            @Value("${hawcx.secret-key}") String secretKey,
            @Value("${hawcx.config-id}") String configId) {
        return DelegationClient.fromSecretKey(baseUrl, secretKey, configId);
    }
}
import com.google.gson.JsonObject;
import com.hawcx.delegation.DelegationClient;
import com.hawcx.delegation.MfaMethod;
import org.springframework.stereotype.Service;

@Service
public class MfaService {

    private final DelegationClient client;

    public MfaService(DelegationClient client) {
        this.client = client;
    }

    public String startSmsMfa(String userId, String phoneNumber) {
        JsonObject result = client.mfa.initiate(userId, MfaMethod.SMS, phoneNumber, null);
        return result.get("session_id").getAsString();
    }

    public void verifyMfa(String userId, String sessionId, String otp) {
        client.mfa.verify(userId, sessionId, otp);
    }

    public JsonObject listDevices(String userId) {
        return client.devices.list(userId);
    }
}

Error Handling

Every SDK error extends com.hawcx.HawcxException. Catch the root if you only want a single handler:

import com.hawcx.HawcxException;
import com.hawcx.oauth.HawcxOauthException;
import com.hawcx.oauth.JwtVerificationException;
import com.hawcx.oauth.InvalidPublicKeyException;

try {
    JsonObject claims = client.exchangeCodeForClaims(req, verifyOpts);
} catch (JwtVerificationException e) {
    // Signature, audience, issuer, expiration, or nbf validation failed
} catch (HawcxOauthException e) {
    // Token endpoint returned non-2xx, or response missing id_token
} catch (InvalidPublicKeyException e) {
    // JWKS unreachable or contained no usable keys
} catch (HawcxException e) {
    // Catch-all for any SDK-originated failure
}

For delegation calls, catch com.hawcx.delegation.DelegationException (or its subtypes DelegationCryptoException, DelegationRequestException, DelegationResponseException).