# Authenticate and Build the API Client
source: https://developer.mastercard.com/mastercard-threat-intelligence/tutorial/api-sandbox-guide/step-6/index.md

In this step, you write two classes. `AppConfig` reads your settings. `MtiClientFactory` authenticates and builds a ready-to-use API client.

*** ** * ** ***

### How Authentication Works {#how-authentication-works}

The Mastercard Threat Intelligence API uses OAuth 2.0. The Mastercard OAuth 2.0 client library does the work for you:

1. It signs a request with your private key to prove your identity.
2. It exchanges that request for a short-lived access token.
3. It attaches the token to every API call.

The library adds this behavior through an OkHttp interceptor, so your API calls stay clean.

*** ** * ** ***

### Read the Configuration {#read-the-configuration}

Create `AppConfig.java` under `src/main/java/com/example/mti/`. This class loads `application.properties` and exposes each setting through a method:

```java
package com.example.mti;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Loads demo settings from application.properties on the classpath.
 */
public class AppConfig {

    private final Properties props = new Properties();

    public AppConfig() {
        this("application.properties");
    }

    public AppConfig(String resourceName) {
        try (InputStream in = getClass().getClassLoader().getResourceAsStream(resourceName)) {
            if (in == null) {
                throw new IllegalStateException("Configuration file not found: " + resourceName);
            }
            props.load(in);
        } catch (IOException e) {
            throw new UncheckedIOException("Failed to read configuration: " + resourceName, e);
        }
    }

    public String get(String key) {
        String value = props.getProperty(key);
        if (value == null || value.isBlank()) {
            throw new IllegalStateException("Missing required setting: " + key);
        }
        return value.trim();
    }

    public String getOrDefault(String key, String fallback) {
        String value = props.getProperty(key);
        return (value == null || value.isBlank()) ? fallback : value.trim();
    }

    public String baseUrl() {
        return get("mti.api.base-url");
    }

    public String tokenUrl() {
        return get("mti.oauth.token-url");
    }

    public String issuer() {
        return get("mti.oauth.issuer");
    }

    public String clientId() {
        return get("mti.oauth.client-id");
    }

    public String keyId() {
        return get("mti.oauth.key-id");
    }

    public String keystore() {
        return get("mti.oauth.keystore");
    }

    public String keystorePassword() {
        return get("mti.oauth.keystore-password");
    }

    public String keyAlias() {
        return get("mti.oauth.key-alias");
    }

    public Set<String> scopes() {
        Set<String> scopes = new LinkedHashSet<>();
        for (String scope : get("mti.oauth.scopes").split("\\s+")) {
            if (!scope.isBlank()) {
                scopes.add(scope.trim());
            }
        }
        return scopes;
    }

    public Map<String, String> additionalHeaders() {
        Map<String, String> headers = new LinkedHashMap<>();
        for (String entry : getOrDefault("mti.api.additional-headers", "").split(",")) {
            int separator = entry.indexOf(':');
            if (separator > 0) {
                headers.put(entry.substring(0, separator).trim(), entry.substring(separator + 1).trim());
            }
        }
        return headers;
    }
}
```

*** ** * ** ***

### Build the API Client {#build-the-api-client}

Create `MtiClientFactory.java` under `src/main/java/com/example/mti/`. This class loads your signing key, configures OAuth 2.0, and returns a configured `ApiClient`:

```java
package com.example.mti;

import com.example.mti.client.invoker.ApiClient;
import com.mastercard.developer.oauth2.config.OAuth2Config;
import com.mastercard.developer.oauth2.config.SecurityProfile;
import com.mastercard.developer.oauth2.core.access_token.InMemoryAccessTokenStore;
import com.mastercard.developer.oauth2.core.dpop.StaticDPoPKeyProvider;
import com.mastercard.developer.oauth2.core.scope.StaticScopeResolver;
import com.mastercard.developer.oauth2.http.okhttp3.OAuth2Interceptor;
import com.mastercard.developer.oauth2.keys.KeyGenerator;
import okhttp3.OkHttpClient;

import java.io.InputStream;
import java.net.URL;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.time.Duration;
import java.util.Map;

/**
 * Builds an API client that authenticates with Mastercard using OAuth 2.0.
 */
public class MtiClientFactory {

    private final AppConfig config;

    public MtiClientFactory(AppConfig config) {
        this.config = config;
    }

    public ApiClient create() throws Exception {
        PrivateKey clientKey = loadClientKey();
        KeyPair dpopKeyPair = KeyGenerator.generateEcKeyPair("secp256r1");

        OAuth2Config oauthConfig = OAuth2Config.builder()
                .securityProfile(SecurityProfile.FAPI2SP_PRIVATE_KEY_DPOP)
                .clientId(config.clientId())
                .tokenEndpoint(new URL(config.tokenUrl()))
                .issuer(new URL(config.issuer()))
                .clientKey(clientKey)
                .kid(config.keyId())
                .accessTokenStore(new InMemoryAccessTokenStore())
                .scopeResolver(new StaticScopeResolver(config.scopes()))
                .dpopKeyProvider(new StaticDPoPKeyProvider(dpopKeyPair))
                .clockSkewTolerance(Duration.ofSeconds(10))
                .build();

        OkHttpClient baseClient = new OkHttpClient.Builder().build();
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(new OAuth2Interceptor(oauthConfig, baseClient.newBuilder()))
                .build();

        ApiClient apiClient = new ApiClient(httpClient);
        apiClient.setBasePath(config.baseUrl());
        for (Map.Entry<String, String> header : config.additionalHeaders().entrySet()) {
            apiClient.addDefaultHeader(header.getKey(), header.getValue());
        }
        return apiClient;
    }

    private PrivateKey loadClientKey() throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (InputStream in = getClass().getClassLoader().getResourceAsStream(config.keystore())) {
            if (in == null) {
                throw new IllegalStateException("Signing key not found: " + config.keystore());
            }
            keyStore.load(in, config.keystorePassword().toCharArray());
        }
        return (PrivateKey) keyStore.getKey(config.keyAlias(), config.keystorePassword().toCharArray());
    }
}
```

*** ** * ** ***

### What the Factory Does {#what-the-factory-does}

The `create` method performs four tasks:

* It loads your private key from the PKCS#12 keystore.
* It builds an `OAuth2Config` from your settings.
* It registers the `OAuth2Interceptor`, which adds a token to every request.
* It returns an `ApiClient` with the correct base URL.

With the client ready, you can call each feature. The next three steps do exactly that.

*** ** * ** ***

