# OAuth2 Client Java Mastercard's OAuth2 Client Java is a zero-dependency library that simplifies accessing Mastercard APIs with OAuth 2.0 and FAPI 2.0 authentication. It automatically handles access token acquisition, caching, and DPoP (Demonstrating Proof-of-Possession) proof generation for secure API calls. The library supports FAPI 2.0 Security Profile with private_key_jwt client authentication and DPoP-bound access tokens. The library is designed to be modular and lightweight, using "provided" dependencies so you can choose your preferred HTTP client and JSON provider at runtime. It supports multiple HTTP client implementations including OkHttp, Java HTTP Client, Apache HttpClient, OpenFeign, Spring WebClient, and Spring RestClient. At least one JSON library (Jackson, Gson, or org.json) must be available on the classpath. ## OAuth2Config Builder The `OAuth2Config` class is the central configuration object that contains client credentials, DPoP key configuration, token endpoint, and other settings for OAuth 2.0 authentication. All configurations are built using a fluent builder pattern with validation on build. ```java 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.keys.KeyLoader; import com.mastercard.developer.oauth2.keys.KeyGenerator; import java.net.URL; import java.nio.file.Paths; import java.security.KeyPair; import java.security.PrivateKey; import java.time.Duration; import java.util.Set; // Load keys from files PrivateKey clientKey = KeyLoader.loadPrivateKey(Paths.get("path/to/client-private-key.pem")); KeyPair dpopKeyPair = KeyLoader.loadKeyPair(Paths.get("path/to/dpop-private-key.json")); // Or generate a new EC key pair for DPoP // KeyPair dpopKeyPair = KeyGenerator.generateEcKeyPair("secp256r1"); // Build the configuration OAuth2Config config = OAuth2Config.builder() .securityProfile(SecurityProfile.FAPI2SP_PRIVATE_KEY_DPOP) .clientId("ZvT0sklPsqzTNgKJIiex5_wppXz0Tj2wl33LUZtXmCQH8dry") .tokenEndpoint(new URL("https://sandbox.api.mastercard.com/oauth/token")) .issuer(new URL("https://sandbox.api.mastercard.com")) .clientKey(clientKey) .kid("302449525fad5309874b16298f3cbaaf0000000000000000") .accessTokenStore(new InMemoryAccessTokenStore()) .scopeResolver(new StaticScopeResolver(Set.of("service:scope1", "service:scope2"))) .dpopKeyProvider(new StaticDPoPKeyProvider(dpopKeyPair)) .clockSkewTolerance(Duration.ofSeconds(10)) .build(); ``` ## KeyLoader - Loading Private Keys The `KeyLoader` utility class provides methods to load private keys from various formats including PKCS#1 PEM, PKCS#8 PEM, PKCS#8 DER, PKCS#12 keystores, and JWK files for key pairs. ```java import com.mastercard.developer.oauth2.keys.KeyLoader; import java.nio.file.Paths; import java.security.KeyPair; import java.security.PrivateKey; // Load from PKCS#1 PEM file (-----BEGIN RSA PRIVATE KEY-----) PrivateKey pkcs1Key = KeyLoader.loadPrivateKey(Paths.get("keys/client-key-pkcs1.pem")); // Load from PKCS#8 PEM file (-----BEGIN PRIVATE KEY-----) PrivateKey pkcs8Key = KeyLoader.loadPrivateKey(Paths.get("keys/client-key-pkcs8.pem")); // Load from PKCS#8 DER binary file PrivateKey derKey = KeyLoader.loadPrivateKey(Paths.get("keys/client-key.der")); // Load from PKCS#12 keystore PrivateKey p12Key = KeyLoader.loadPrivateKey( "keys/keystore.p12", // keystore path "key-alias", // key alias "keystore-password" // password ); // Load JWK key pair for DPoP KeyPair dpopKeyPair = KeyLoader.loadKeyPair(Paths.get("keys/dpop-key.json")); ``` ## KeyGenerator - Generating Key Pairs The `KeyGenerator` utility class creates RSA or EC key pairs programmatically, useful for generating DPoP keys at runtime. ```java import com.mastercard.developer.oauth2.keys.KeyGenerator; import java.security.KeyPair; // Generate RSA key pair (minimum 2048 bits for FAPI 2.0 compliance) KeyPair rsaKeyPair = KeyGenerator.generateRsaKeyPair(2048); // Generate EC key pair with P-256 curve (secp256r1) KeyPair ecKeyPair = KeyGenerator.generateEcKeyPair("secp256r1"); // Generate EC key pair with P-384 curve (secp384r1) KeyPair ecKeyPair384 = KeyGenerator.generateEcKeyPair("secp384r1"); ``` ## OkHttp Integration with OAuth2Interceptor The `OAuth2Interceptor` is an OkHttp interceptor that automatically adds access tokens and DPoP proofs to outgoing requests. Register it with `OkHttpClient.Builder.addInterceptor()`. ```java import com.mastercard.developer.oauth2.http.okhttp3.OAuth2Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; // Create OkHttpClient with OAuth2 interceptor OkHttpClient httpClient = new OkHttpClient.Builder() .addInterceptor(new OAuth2Interceptor(config)) .build(); // Make authenticated request Request request = new Request.Builder() .url("https://sandbox.api.mastercard.com/resources") .get() .build(); try (Response response = httpClient.newCall(request).execute()) { System.out.println("Status: " + response.code()); System.out.println("Body: " + response.body().string()); } // With OpenAPI Generator (okhttp-gson library) OkHttpClient baseClient = new OkHttpClient(); OkHttpClient oauthClient = new OkHttpClient.Builder() .addInterceptor(new OAuth2Interceptor(config, baseClient.newBuilder())) .build(); ApiClient client = new ApiClient(oauthClient); client.setBasePath("https://sandbox.api.mastercard.com"); ResourcesApi api = new ResourcesApi(client); Resource resource = api.createResource(new Resource()); ``` ## Java HTTP Client Integration with OAuth2HttpClient The `OAuth2HttpClient` extends `java.net.http.HttpClient` and provides a builder that extends `HttpClient.Builder`. Use it as a drop-in replacement for the standard Java HTTP client. ```java import com.mastercard.developer.oauth2.http.java.OAuth2HttpClient; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; // Create OAuth2-enabled HttpClient HttpClient httpClient = OAuth2HttpClient.newBuilder(config).build(); // Or with custom base builder HttpClient customClient = OAuth2HttpClient.newBuilder(config, HttpClient.newBuilder() .connectTimeout(java.time.Duration.ofSeconds(30))) .build(); // Make authenticated request HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://sandbox.api.mastercard.com/resources")) .GET() .build(); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println("Status: " + response.statusCode()); System.out.println("Body: " + response.body()); // With OpenAPI Generator (native library) ApiClient client = new ApiClient(); client.setHttpClientBuilder(OAuth2HttpClient.newBuilder(config, HttpClient.newBuilder())); client.updateBaseUri("https://sandbox.api.mastercard.com"); ResourcesApi api = new ResourcesApi(client); Resource resource = api.createResource(new Resource()); ``` ## Apache HttpClient Integration with OAuth2HttpClient The Apache `OAuth2HttpClient` extends `CloseableHttpClient` and wraps request execution to add access tokens and DPoP proofs before sending requests. ```java import com.mastercard.developer.oauth2.http.apache.OAuth2HttpClient; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; // Create OAuth2-enabled Apache HttpClient CloseableHttpClient httpClient = new OAuth2HttpClient(config); // Make authenticated request HttpGet request = new HttpGet("https://sandbox.api.mastercard.com/resources"); try (CloseableHttpResponse response = httpClient.execute(request)) { System.out.println("Status: " + response.getCode()); } // Or with response handler String result = httpClient.execute(request, response -> { int statusCode = response.getCode(); String body = new String(response.getEntity().getContent().readAllBytes()); return "Status: " + statusCode + ", Body: " + body; }); // With OpenAPI Generator (apache-httpclient library) CloseableHttpClient oauthClient = new OAuth2HttpClient(config); ApiClient client = new ApiClient(oauthClient); client.setBasePath("https://sandbox.api.mastercard.com"); ResourcesApi api = new ResourcesApi(client); Resource resource = api.createResource(new Resource()); ``` ## OpenFeign Integration with OAuth2Client The `OAuth2Client` implements Feign's `Client` interface and intercepts requests to add access tokens and DPoP proofs. Pass it to `Feign.Builder.client()`. ```java import com.mastercard.developer.oauth2.http.feign.OAuth2Client; import feign.Client; import feign.Feign; import feign.jackson.JacksonDecoder; import feign.jackson.JacksonEncoder; // Create OAuth2-enabled Feign Client Client feignClient = new OAuth2Client(config); // Build Feign API interface ResourcesApi api = Feign.builder() .client(feignClient) .encoder(new JacksonEncoder()) .decoder(new JacksonDecoder()) .target(ResourcesApi.class, "https://sandbox.api.mastercard.com"); Resource resource = api.getResource("resource-id"); // With OpenAPI Generator (feign library) Client oauthClient = new OAuth2Client(config); ApiClient client = new ApiClient(); client.getFeignBuilder().client(oauthClient); client.setBasePath("https://sandbox.api.mastercard.com"); ResourcesApi generatedApi = client.buildClient(ResourcesApi.class); Resource result = generatedApi.createResource(new Resource()); ``` ## Spring WebClient Integration with OAuth2Filter The `OAuth2Filter` implements Spring's `ExchangeFilterFunction` for reactive WebClient. It intercepts requests to add access tokens and DPoP proofs. Register it with `WebClient.Builder.filter()`. ```java import com.mastercard.developer.oauth2.http.spring.webclient.OAuth2Filter; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; // Create OAuth2 filter OAuth2Filter filter = new OAuth2Filter(config, WebClient.builder()); // Build WebClient with filter WebClient webClient = WebClient.builder() .filter(filter) .baseUrl("https://sandbox.api.mastercard.com") .build(); // Make authenticated request (reactive) Mono resourceMono = webClient.get() .uri("/resources/{id}", "resource-id") .retrieve() .bodyToMono(Resource.class); Resource resource = resourceMono.block(); // POST request Resource newResource = webClient.post() .uri("/resources") .bodyValue(new Resource()) .retrieve() .bodyToMono(Resource.class) .block(); // With OpenAPI Generator (webclient library) WebClient oauthWebClient = WebClient.builder().filter(filter).build(); ApiClient client = new ApiClient(oauthWebClient); client.setBasePath("https://sandbox.api.mastercard.com"); ResourcesApi api = new ResourcesApi(client); Resource result = api.createResource(new Resource()); ``` ## Spring RestClient Integration with OAuth2ClientHttpRequestInterceptor The `OAuth2ClientHttpRequestInterceptor` implements Spring's `ClientHttpRequestInterceptor` for synchronous RestClient. Register it with `RestClient.Builder.requestInterceptor()`. ```java import com.mastercard.developer.oauth2.http.spring.restclient.OAuth2ClientHttpRequestInterceptor; import org.springframework.web.client.RestClient; // Create OAuth2 interceptor OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(config); // Build RestClient with interceptor RestClient restClient = RestClient.builder() .baseUrl("https://sandbox.api.mastercard.com") .requestInterceptor(interceptor) .build(); // Make authenticated GET request Resource resource = restClient.get() .uri("/resources/{id}", "resource-id") .retrieve() .body(Resource.class); // Make authenticated POST request Resource created = restClient.post() .uri("/resources") .body(new Resource()) .retrieve() .body(Resource.class); // With OpenAPI Generator (restclient library) RestClient oauthRestClient = RestClient.builder() .requestInterceptor(interceptor) .build(); ApiClient client = new ApiClient(oauthRestClient); client.setBasePath("https://sandbox.api.mastercard.com"); ResourcesApi api = new ResourcesApi(client); Resource result = api.createResource(new Resource()); ``` ## ScopeResolver - Custom Scope Resolution The `ScopeResolver` interface determines which OAuth2 scopes to request for each API call. Implement it for dynamic scope resolution based on the request method and URL. ```java import com.mastercard.developer.oauth2.core.scope.ScopeResolver; import com.mastercard.developer.oauth2.core.scope.StaticScopeResolver; import java.net.URL; import java.util.Set; // Static scope resolver (same scopes for all requests) ScopeResolver staticResolver = new StaticScopeResolver(Set.of( "service:read", "service:write" )); // Custom scope resolver based on HTTP method and URL ScopeResolver customResolver = new ScopeResolver() { @Override public Set resolve(String httpMethod, URL url) { String path = url.getPath(); if (path.startsWith("/payments")) { return Set.of("payments:read", "payments:write"); } else if (path.startsWith("/accounts")) { if ("GET".equals(httpMethod)) { return Set.of("accounts:read"); } return Set.of("accounts:read", "accounts:write"); } return Set.of("default:scope"); } @Override public Set allScopes() { return Set.of("payments:read", "payments:write", "accounts:read", "accounts:write", "default:scope"); } }; // Use in config OAuth2Config config = OAuth2Config.builder() .scopeResolver(customResolver) // ... other config .build(); ``` ## DPoPKeyProvider - Custom DPoP Key Management The `DPoPKeyProvider` interface supplies keys for DPoP proof generation. Implement it for key rotation or dynamic key management scenarios. ```java import com.mastercard.developer.oauth2.core.dpop.DPoPKey; import com.mastercard.developer.oauth2.core.dpop.DPoPKeyProvider; import com.mastercard.developer.oauth2.core.dpop.StaticDPoPKeyProvider; import com.mastercard.developer.oauth2.keys.KeyGenerator; import java.security.KeyPair; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.UUID; // Static provider (single key for entire application lifecycle) KeyPair keyPair = KeyGenerator.generateEcKeyPair("secp256r1"); DPoPKeyProvider staticProvider = new StaticDPoPKeyProvider(keyPair); // Custom rotating key provider DPoPKeyProvider rotatingProvider = new DPoPKeyProvider() { private final Map keys = new ConcurrentHashMap<>(); private volatile DPoPKey currentKey = generateNewKey(); private DPoPKey generateNewKey() { try { KeyPair kp = KeyGenerator.generateEcKeyPair("secp256r1"); String kid = UUID.randomUUID().toString(); DPoPKey key = new DPoPKey() { @Override public KeyPair getKeyPair() { return kp; } @Override public String getKeyId() { return kid; } }; keys.put(kid, key); return key; } catch (Exception e) { throw new RuntimeException("Failed to generate key", e); } } @Override public DPoPKey getCurrentKey() { return currentKey; } @Override public DPoPKey getKey(String kid) { return keys.get(kid); } public void rotateKey() { currentKey = generateNewKey(); } }; ``` ## AccessTokenStore - Custom Token Caching The `AccessTokenStore` interface controls how access tokens are cached and retrieved. Implement it for custom caching strategies like distributed caches. ```java import com.mastercard.developer.oauth2.core.access_token.AccessToken; import com.mastercard.developer.oauth2.core.access_token.AccessTokenFilter; import com.mastercard.developer.oauth2.core.access_token.AccessTokenStore; import com.mastercard.developer.oauth2.core.access_token.InMemoryAccessTokenStore; import java.time.Instant; import java.util.Optional; // In-memory store (thread-safe, automatic expiration) AccessTokenStore inMemoryStore = new InMemoryAccessTokenStore(); // Custom Redis-backed store (example structure) AccessTokenStore redisStore = new AccessTokenStore() { // private RedisClient redisClient; @Override public void put(AccessToken accessToken) { String key = createKey(accessToken.jkt(), accessToken.scopes()); // redisClient.setex(key, accessToken.expiresAt().getEpochSecond() - Instant.now().getEpochSecond(), serialize(accessToken)); } @Override public Optional get(AccessTokenFilter filter) { String key = createKey(filter.jkt().orElse(null), filter.scopes()); // String value = redisClient.get(key); // if (value == null) return Optional.empty(); // AccessToken token = deserialize(value); // if (token.expiresAt().isBefore(Instant.now().plusSeconds(60))) { // return Optional.empty(); // } // return Optional.of(token); return Optional.empty(); } private String createKey(String jkt, java.util.Set scopes) { return "oauth2:token:" + (jkt != null ? jkt : "none") + ":" + String.join(",", scopes); } }; ``` ## Maven Dependency Configuration Add the oauth2-client-java dependency to your project along with your preferred HTTP client and JSON provider. ```xml com.mastercard.developer oauth2-client-java 1.0.0 com.squareup.okhttp3 okhttp 4.12.0 org.apache.httpcomponents.client5 httpclient5 5.5.2 io.github.openfeign feign-core 13.2.1 org.springframework spring-webflux 6.2.13 org.springframework spring-web 6.2.13 com.fasterxml.jackson.core jackson-databind 2.19.2 com.google.code.gson gson 2.10.1 org.json json 20231013 ``` ## Summary The OAuth2 Client Java library provides a streamlined way to integrate OAuth 2.0 and FAPI 2.0 authentication into Java applications accessing Mastercard APIs. The primary use cases include making secure API calls with automatic token management, implementing DPoP-bound access tokens for enhanced security, and integrating with OpenAPI Generator-generated clients. The library handles all the complexity of token acquisition, caching, expiration, and DPoP proof generation behind simple interceptor or filter patterns. Integration follows a consistent pattern across all supported HTTP clients: create an `OAuth2Config` with your credentials and keys, instantiate the appropriate interceptor/filter/client wrapper for your HTTP library, and make requests as normal. The library automatically intercepts outgoing requests, acquires or refreshes access tokens as needed, generates DPoP proofs, and attaches the required headers. For advanced use cases, implement custom `ScopeResolver`, `DPoPKeyProvider`, or `AccessTokenStore` interfaces to control scope resolution, key rotation, and token caching strategies respectively.