Integrating Interswitch VAS Authentication with Spring Boot
Build a Spring Boot service that authenticates with Interswitch VAS using OAuth 2.0 Client Credentials
Modern applications increasingly depend on external APIs for services such as airtime, data subscriptions, bill payments and other value-added services (VAS). Instead of implementing these services from scratch, developers can integrate with an established VAS platform and expose those capabilities through their own applications.
In this practical tutorial, we will build the authentication
layer for an Interswitch VAS integration using Spring Boot.
The implementation uses Spring's RestTemplate to communicate
with the Interswitch authentication endpoint, sends OAuth 2.0 client
credentials, receives an access token and exposes our own REST endpoint that
can be used to initiate authentication.
The authentication service will eventually become the
foundation for calling other Interswitch VAS APIs.
Note: The credentials shown in this article are
placeholders. Never publish real Interswitch client credentials, merchant
credentials or secrets in source code.
What We Are Building
The application will have the following flow:
The important point is that our application does not
directly expose the Interswitch credentials to the client. Instead, Spring Boot
keeps the credentials on the server and uses them to obtain an access token.
Step 1: Create the Spring Boot Project
The first step is to scaffold a Spring Boot application from
Spring Initializr.
Visit:
www.spring.io
For this project, select:
- Project:
Maven
- Language:
Java
- Spring
Boot: Current stable version
- Packaging:
Jar
- Java:
17 or later
Add the dependencies required by your application. At
minimum, the project needs Spring Web.
If the application already uses Lombok and OpenAPI/Swagger,
those dependencies can also be included.
A typical Maven configuration might contain:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
After generating the project, open it in your preferred IDE.
Rather than putting the entire integration into one large
class, it is better to separate the responsibilities.
For our VAS integration, we create a dedicated vas package:
vas
── config
│ ──
RestTemplateConfig.java
└── auth
│ ── InterswitchAuthService.java
└── controller
│ ── InterswitchAuthController.java
└── dto
──
InterswitchTokenResponse.java
The responsibilities are:
config
Contains configuration classes required by the integration.
In this example, it contains the RestTemplate bean.
auth
Contains the authentication business logic.
InterswitchAuthService is responsible for communicating with
the Interswitch authentication server.
controller
Contains REST endpoints exposed by our application.
InterswitchAuthController exposes an endpoint for testing
and initiating authentication.
dto
Contains Java objects used to represent external API
responses.
InterswitchTokenResponse maps the JSON returned by
Interswitch.
Each operation can have its own service and DTOs without
turning the project into a single massive class.
Step 3: Configure Interswitch Properties
Next, add the Interswitch configuration to
application.properties.
# ==========================================
# INTERSWITCH VAS INTEGRATION
# ==========================================
interswitch.test.baseUrl=https://qa.interswitchng.com/quicktellerservice/api/v5
interswitch.live.baseUrl=https://orion.interswitchng.com/quicktellerservice/api/v5
interswitch.merchantCode=YOUR_MERCHANT_CODE
interswitch.payItemID=YOUR_PAY_ITEM_ID
interswitch.dataRef=YOUR_DATA_REFERENCE
interswitch.client.id=YOUR_CLIENT_ID
interswitch.client.secret=YOUR_CLIENT_SECRET
interswitch.terminalID=YOUR_TERMINAL_ID
interswitch.auth.url=https://passport-sandbox.interswitchng.com/passport/oauth/token
The configuration separates environment-specific values from
Java code.
For example:
interswitch.test.baseUrl=...
interswitch.live.baseUrl=...
allows the application to switch between test and production
environments without changing the service implementation.
Do not commit secrets
Although application.properties is convenient during
development, production applications should not store credentials directly in
source control.
A better approach is:
interswitch.client.id=${INTERSWITCH_CLIENT_ID}
interswitch.client.secret=${INTERSWITCH_CLIENT_SECRET}
The actual values can then be supplied through environment
variables.
For example:
export INTERSWITCH_CLIENT_ID=your-client-id
export INTERSWITCH_CLIENT_SECRET=your-client-secret
This is especially important if the project will eventually
be pushed to GitHub or deployed to a cloud server.
Step 4: Create the RestTemplate Configuration
Spring Boot needs an HTTP client to communicate with the
Interswitch API.
For this implementation, we use Spring's RestTemplate.
Create:
vas/config/RestTemplateConfig.java
package com.installment.core.vas.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
public class RestTemplateConfig {
public
RestTemplate restTemplate() {
return new
RestTemplate();
}
}
The @Configuration annotation tells Spring that this class
contains application configuration.
The @Bean annotation registers the RestTemplate with
Spring's application context.
We can then inject it into our service using constructor
injection.
Step 5: Create the Interswitch Token Response DTO
The authentication server returns JSON containing the access
token and other information.
Instead of working with raw JSON, we create a Java DTO.
Create:
vas/dto/InterswitchTokenResponse.java
package com.installment.core.vas.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@JsonProperty("access_token")
private String
accessToken;
private String
tokenType;
@JsonProperty("expires_in")
private Long
expiresIn;
private String
scope;
@JsonProperty("merchant_code")
private String
merchantCode;
@JsonProperty("merchant-wallet-actions")
private
List<String> merchantWalletActions;
@JsonProperty("terminalId")
private String
terminalId;
private String
env;
@JsonProperty("client_name")
private String
clientName;
}
The @JsonProperty annotation is important because the naming
convention used by the external API may not match Java's normal camel-case
convention.
For example, the external response may contain:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600
}
while our Java property is:
private String accessToken;
Jackson uses @JsonProperty("access_token") to
connect the two.
Step 6: Create the Interswitch Authentication Service
Now we implement the most important part of the integration.
Create:
vas/auth/InterswitchAuthService.java
package com.installment.core.vas.auth;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class InterswitchAuthService {
private String
authUrl;
private String
clientId;
@Value("${interswitch.client.secret}")
private String
clientSecret;
public
InterswitchAuthService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public
InterswitchTokenResponse authenticate() {
String
credentials = clientId + ":" + clientSecret;
String
encodedCredentials =
Base64.getEncoder()
.encodeToString(
credentials.getBytes(StandardCharsets.UTF_8)
);
HttpHeaders
headers = new HttpHeaders();
headers.set(
HttpHeaders.AUTHORIZATION,
"Basic " + encodedCredentials
);
headers.setContentType(
MediaType.APPLICATION_FORM_URLENCODED
);
MultiValueMap<String, String> body =
new
LinkedMultiValueMap<>();
body.add("scope", "profile");
body.add("grant_type", "client_credentials");
HttpEntity<MultiValueMap<String, String>> request =
new
HttpEntity<>(body, headers);
ResponseEntity<InterswitchTokenResponse> response =
restTemplate.exchange(
authUrl,
HttpMethod.POST,
request,
InterswitchTokenResponse.class
);
if
(response.getBody() == null ||
response.getBody().getAccessToken() == null) {
throw new
RuntimeException(
"Unable to obtain Interswitch access token"
);
}
return
response.getBody();
}
}
Let's break down what is happening.
Step 6.1: Inject the Configuration
The service obtains the authentication URL and credentials
from application.properties:
@Value("${interswitch.auth.url}")
private String authUrl;
@Value("${interswitch.client.id}")
private String clientId;
@Value("${interswitch.client.secret}")
private String clientSecret;
This prevents the credentials from being hard-coded into the
service.
Step 6.2: Build the Basic Authentication Credentials
OAuth 2.0 client credentials are combined in this format:
clientId:clientSecret
The code creates that string:
String credentials = clientId + ":" +
clientSecret;
It is then Base64 encoded:
String encodedCredentials =
Base64.getEncoder()
.encodeToString(
credentials.getBytes(StandardCharsets.UTF_8)
);
The resulting value is placed in the HTTP Authorization
header:
Authorization: Basic <base64-credentials>
This is a common pattern for OAuth 2.0 client
authentication.
Step 6.3: Set the Content Type
The authentication request uses form-encoded data rather
than JSON.
Therefore, we specify:
headers.setContentType(
MediaType.APPLICATION_FORM_URLENCODED
);
The request body will look conceptually like:
scope=profile
grant_type=client_credentials
Step 6.4: Create the Request Body
Spring's MultiValueMap makes it easy to construct form data:
MultiValueMap<String, String> body =
new
LinkedMultiValueMap<>();
body.add("scope", "profile");
body.add("grant_type",
"client_credentials");
The important OAuth parameter is:
grant_type=client_credentials
This tells the authentication server that the application is
authenticating using its registered client credentials.
Step 6.5: Create the HTTP Request
We combine the headers and body:
HttpEntity<MultiValueMap<String, String>>
request =
new
HttpEntity<>(body, headers);
The request now contains everything required by the
authentication endpoint.
Step 6.6: Send the Authentication Request
We use RestTemplate.exchange():
ResponseEntity<InterswitchTokenResponse> response =
restTemplate.exchange(
authUrl,
HttpMethod.POST,
request,
InterswitchTokenResponse.class
);
The parameters tell Spring:
- Where
to send the request.
- Which
HTTP method to use.
- What
headers and body to send.
- Which
Java class should represent the response.
The response JSON is automatically deserialized into:
InterswitchTokenResponse
Step 6.7: Validate the Response
It is important not to assume that every request will return
a valid token.
We therefore check:
if (response.getBody() == null ||
response.getBody().getAccessToken() == null) {
throw new
RuntimeException(
"Unable to obtain Interswitch access token"
);
}
If an access token exists, the DTO is returned:
return response.getBody();
At this point, our Spring Boot application can successfully
communicate with the Interswitch authentication server.
Step 7: Create a Standard API Response Wrapper
In a real application, it is often useful to return API
responses using a consistent structure.
For this project, we use:
package com.installment.core.utils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AppApiResponse<T> {
}
The generic <T> allows the wrapper to work with
different response types.
For example:
AppApiResponse<InterswitchTokenResponse>
means the data field contains an InterswitchTokenResponse.
The API response can therefore have a consistent structure:
{
"success": true,
"message": "Interswitch authentication successful",
"data":
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600
}
}
Step 8: Create the Authentication Controller
Now we expose our authentication service through a REST
endpoint.
Create:
vas/controller/InterswitchAuthController.java
A simplified version is:
package com.installment.core.vas.controller;
import com.installment.core.vas.auth.InterswitchAuthService;
import
com.installment.core.vas.dto.InterswitchTokenResponse;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/vas/interswitch")
public class InterswitchAuthController {
private final
InterswitchAuthService authService;
public
InterswitchAuthController(
InterswitchAuthService authService) {
this.authService = authService;
}
@PostMapping("/auth")
public
ResponseEntity<
AppApiResponse<InterswitchTokenResponse>
>
authenticate() {
authService.authenticate();
new
AppApiResponse<>(
true,
"Interswitch authentication successful",
response
);
return
ResponseEntity.ok(apiResponse);
}
}
The endpoint is now:
POST /api/v1/vas/interswitch/auth
The controller has a very simple responsibility:
The controller does not know how authentication works internally. That logic remains inside InterswitchAuthService.
Step 9: Add OpenAPI Documentation
If your Spring Boot application uses Springdoc OpenAPI, you
can document the endpoint directly in the controller.
For example:
@Operation(
summary =
"Generate Interswitch access token",
description =
"Authenticates with the Interswitch sandbox using " +
"OAuth
2.0 client credentials and returns an access token."
)
@ApiResponses(value = {
responseCode =
"200",
description =
"Authentication successful"
),
@ApiResponse(
responseCode =
"401",
description =
"Invalid client credentials"
),
@ApiResponse(
responseCode =
"400",
description =
"Invalid authentication request"
),
@ApiResponse(
responseCode =
"500",
description =
"Internal server error"
)
})
This makes the endpoint easier to test from Swagger UI and
also provides documentation for other developers working with the integration.
One small correction worth making in the original
implementation is the controller tag.
Instead of:
@Tag(
name = "User
Authentication",
description =
"APIs for merchant login"
)
use something specific to the integration:
@Tag(
name =
"Interswitch VAS",
description =
"Interswitch VAS integration APIs"
)
The endpoint is authenticating the application with
Interswitch
Step 10: Build the Application
Before testing the integration, compile the project.
With Maven:
mvn clean package
If the build succeeds, Maven should produce a JAR under:
target/
For example:
target/installment-core-0.0.1-SNAPSHOT.jar
If you want to skip tests temporarily:
mvn clean package -DskipTests
However, during normal development it is better to keep the
tests enabled.
Step 11: Run the Application
The application can be started using Maven:
mvn spring-boot:run
Or by running the generated JAR:
java -jar target/installment-core-0.0.1-SNAPSHOT.jar
When Spring Boot starts successfully, you should see a
message similar to:
Started Application in ... seconds
The application is now ready to receive requests.
Step 12: Test the Authentication Endpoint
We can test the endpoint using Postman, Insomnia, curl or
Swagger UI.
Using curl:
curl --location --request POST \
http://localhost:8080/api/v1/vas/interswitch/auth
No Interswitch client credentials need to be supplied by the
caller because they are already configured on the server.
If authentication succeeds, the application returns the
token response.
A response could look conceptually like:
{
"success":
true,
"message": "Interswitch authentication successful",
"data":
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "profile",
"merchant_code": "YOUR_MERCHANT_CODE"
}
}
The exact response fields depend on the response returned by
the Interswitch environment being used.
The access token can subsequently be supplied in the
Authorization header when making authenticated VAS requests.
Conceptually:
Authorization: Bearer <access-token>
This gives the application a reusable authentication layer
rather than implementing OAuth authentication separately for every VAS
operation.
Improving the Production Implementation
The implementation above is intentionally straightforward so
that the authentication flow is easy to understand.
However, there are several improvements that should be
considered before deploying it to production.
1. Do not authenticate for every request
The current implementation obtains a new access token
whenever:
POST /api/v1/vas/interswitch/auth
is called.
A production integration should normally consider token
caching.
If the token is valid for a particular period, the
application can store it temporarily and reuse it until it is close to
expiration.
For example:
This reduces unnecessary authentication requests.
2. Move secrets to environment variables
Instead of:
interswitch.client.secret=actual-secret
use:
interswitch.client.secret=${INTERSWITCH_CLIENT_SECRET}
This prevents credentials from being accidentally committed
to Git.
3. Use proper exception handling
The example uses:
throw new RuntimeException(
"Unable to
obtain Interswitch access token"
);
A production application should ideally introduce a custom
exception and centralized exception handling.
For example:
InterswitchAuthenticationException
can be handled by a global exception handler and converted
into a consistent API response.
4. Add timeout configuration
External API calls should not be allowed to wait
indefinitely.
The HTTP client should have appropriate:
- Connection
timeout
- Read
timeout
- Connection
pool configuration
This becomes particularly important for payment and VAS
systems.
5. Add logging without exposing credentials
Logging is useful when troubleshooting integration problems.
However, never log:
client_secret
Authorization header
access_token
A log message such as:
Interswitch authentication request initiated
is preferable to logging the complete request.
Conclusion
Integrating a VAS provider into a Spring Boot application
starts with establishing a reliable authentication layer.
In this tutorial, we built that layer by:
- Creating
a Spring Boot application.
- Structuring
the VAS integration into separate packages.
- Configuring
Interswitch properties.
- Creating
a reusable RestTemplate.
- Creating
a DTO for the token response.
- Implementing
OAuth 2.0 client-credentials authentication.
- Encoding
the client credentials using Base64.
- Sending
a form-encoded authentication request.
- Mapping
the authentication response to a Java object.
- Creating
a REST controller.
- Wrapping
the response in a standard API response.
- Building
and running the application.
- Testing
the authentication endpoint.
- Preparing
the architecture for subsequent VAS operations.
The authentication endpoint is only the first building
block. Once it is working, the same architecture can be extended to product
discovery, airtime and data purchases, transaction verification, wallet
operations and other VAS capabilities.