Enterprise Identity Made Simple: Integrating FusionAuth with Java Applications

In the world of modern Java applications, robust authentication and authorization are non-negotiable. While building your own auth system is possible, it's error-prone and diverts focus from core business logic. FusionAuth is an open-source identity and access management platform that provides a complete, secure solution for user management, authentication, and authorization. Let's explore how to integrate it seamlessly with Java applications.

Why FusionAuth? The Java Developer's Perspective

FusionAuth stands out for Java developers because:

  • Open Source - No vendor lock-in, complete transparency
  • API-First - Designed for integration with modern applications
  • Self-Hostable - Maintain full control over user data
  • Feature-Rich - Includes registration, login, MFA, social login, and more
  • Java-Friendly - Excellent REST API support and client libraries

Setting Up FusionAuth

Docker Compose Setup:

# docker-compose.yml
version: '3'
services:
fusionauth:
image: fusionauth/fusionauth-app:latest
environment:
DATABASE_URL: jdbc:postgresql://postgres:5432/fusionauth
DATABASE_ROOT_USERNAME: postgres
DATABASE_ROOT_PASSWORD: password
FUSIONAUTH_APP_MEMORY: 512M
ports:
- "9011:9011"
depends_on:
- postgres
postgres:
image: postgres:13
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: fusionauth

Basic FusionAuth Configuration

Before coding, configure FusionAuth:

  1. Create an Application in FusionAuth admin UI
  2. Generate API Keys for server-to-server communication
  3. Configure OAuth settings (redirect URIs, scopes)
  4. Set up JWT signing keys

Spring Boot Integration

1. Dependencies Setup

<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>io.fusionauth</groupId>
<artifactId>fusionauth-java-client</artifactId>
<version>1.49.0</version>
</dependency>
</dependencies>

2. Configuration Properties

# application.yml
fusionauth:
base-url: http://localhost:9011
api-key: ${FUSIONAUTH_API_KEY}
application-id: ${FUSIONAUTH_APPLICATION_ID}
issuer-uri: http://localhost:9011
client-id: ${FUSIONAUTH_CLIENT_ID}
client-secret: ${FUSIONAUTH_CLIENT_SECRET}
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${fusionauth.issuer-uri}

3. FusionAuth Client Configuration

@Configuration
@ConfigurationProperties(prefix = "fusionauth")
@Data
public class FusionAuthConfig {
private String baseUrl;
private String apiKey;
private String applicationId;
private String clientId;
private String clientSecret;
private String issuerUri;
@Bean
public FusionAuthSyncClient fusionAuthClient() {
return new FusionAuthSyncClient(apiKey, baseUrl);
}
}

User Registration Service

@Service
@Slf4j
public class FusionAuthUserService {
private final FusionAuthSyncClient fusionAuthClient;
private final String applicationId;
public FusionAuthUserService(FusionAuthSyncClient fusionAuthClient, 
FusionAuthConfig config) {
this.fusionAuthClient = fusionAuthClient;
this.applicationId = config.getApplicationId();
}
public UserRegistrationResponse registerUser(UserRegistrationRequest request) {
try {
// Build FusionAuth user registration
UserRegistration registration = new UserRegistration()
.withApplicationId(UUID.fromString(applicationId))
.withUsername(request.getUsername())
.withRoles(request.getRoles());
User user = new User()
.withEmail(request.getEmail())
.withPassword(request.getPassword())
.withFirstName(request.getFirstName())
.withLastName(request.getLastName())
.withMobilePhone(request.getPhoneNumber())
.withRegistrations(registration);
// Call FusionAuth API
ClientResponse<RegistrationResponse, Errors> response = 
fusionAuthClient.register(null, new RegistrationRequest(user));
if (response.wasSuccessful()) {
log.info("User registered successfully: {}", request.getEmail());
return UserRegistrationResponse.success(
response.successResponse.user.id.toString(),
response.successResponse.user.email
);
} else {
log.error("User registration failed: {}", response.errorResponse);
return UserRegistrationResponse.failure(
extractErrorMessage(response.errorResponse)
);
}
} catch (Exception e) {
log.error("Registration error for user: {}", request.getEmail(), e);
throw new UserRegistrationException("Failed to register user", e);
}
}
public UserResponse getUserById(String userId) {
ClientResponse<UserResponse, Errors> response = 
fusionAuthClient.retrieveUser(UUID.fromString(userId));
if (response.wasSuccessful()) {
return mapToUserResponse(response.successResponse.user);
} else {
throw new UserNotFoundException("User not found: " + userId);
}
}
public void updateUserRoles(String userId, List<String> roles) {
ClientResponse<UserResponse, Errors> userResponse = 
fusionAuthClient.retrieveUser(UUID.fromString(userId));
if (userResponse.wasSuccessful()) {
User user = userResponse.successResponse.user;
UserRegistration registration = user.registrations.get(UUID.fromString(applicationId));
if (registration != null) {
registration.roles = roles;
fusionAuthClient.updateRegistration(user.id, new RegistrationRequest(registration));
}
}
}
private String extractErrorMessage(Errors errors) {
return errors.fieldErrors.stream()
.map(error -> error.field + ": " + error.message)
.collect(Collectors.joining(", "));
}
}

Spring Security Configuration with FusionAuth JWT

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final FusionAuthConfig fusionAuthConfig;
public SecurityConfig(FusionAuthConfig fusionAuthConfig) {
this.fusionAuthConfig = fusionAuthConfig;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/register", "/api/auth/login", "/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasRole("USER")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(fusionAuthJwtConverter())
.decoder(jwtDecoder())
)
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.csrf(csrf -> csrf.disable())
.build();
}
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(fusionAuthConfig.getIssuerUri());
// Add custom validators for FusionAuth
OAuth2TokenValidator<Jwt> validator = new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(),
new JwtIssuerValidator(fusionAuthConfig.getIssuerUri()),
new FusionAuthApplicationValidator(fusionAuthConfig.getApplicationId())
);
jwtDecoder.setJwtValidator(validator);
return jwtDecoder;
}
@Bean
public JwtAuthenticationConverter fusionAuthJwtConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(new FusionAuthJwtGrantedAuthoritiesConverter());
return converter;
}
}
@Component
class FusionAuthJwtGrantedAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
Map<String, Object> application = jwt.getClaim("application");
if (application != null && application.containsKey("roles")) {
@SuppressWarnings("unchecked")
List<String> roles = (List<String>) application.get("roles");
return roles.stream()
.map(role -> "ROLE_" + role.toUpperCase())
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
return Collections.emptyList();
}
}
@Component
class FusionAuthApplicationValidator implements OAuth2TokenValidator<Jwt> {
private final String applicationId;
public FusionAuthApplicationValidator(FusionAuthConfig config) {
this.applicationId = config.getApplicationId();
}
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
Map<String, Object> application = jwt.getClaim("application");
if (application == null || !applicationId.equals(application.get("id"))) {
return OAuth2TokenValidatorResult.failure(
new OAuth2Error("invalid_token", 
"Token not issued for this application", null)
);
}
return OAuth2TokenValidatorResult.success();
}
}

Authentication Controller

@RestController
@RequestMapping("/api/auth")
@Slf4j
public class AuthController {
private final FusionAuthUserService userService;
private final FusionAuthSyncClient fusionAuthClient;
private final FusionAuthConfig config;
public AuthController(FusionAuthUserService userService,
FusionAuthSyncClient fusionAuthClient,
FusionAuthConfig config) {
this.userService = userService;
this.fusionAuthClient = fusionAuthClient;
this.config = config;
}
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody UserRegistrationRequest request) {
try {
UserRegistrationResponse response = userService.registerUser(request);
if (response.isSuccess()) {
return ResponseEntity.ok(response);
} else {
return ResponseEntity.badRequest().body(response);
}
} catch (UserRegistrationException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Registration failed"));
}
}
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
try {
LoginResponse loginResponse = new LoginResponse();
loginResponse.setUsername(request.getUsername());
// FusionAuth login API call
ClientResponse<LoginResponse, Errors> response = fusionAuthClient.login(
new LoginRequest(
UUID.fromString(config.getApplicationId()),
request.getUsername(),
request.getPassword()
)
);
if (response.wasSuccessful()) {
loginResponse.setToken(response.successResponse.token);
loginResponse.setRefreshToken(response.successResponse.refreshToken);
loginResponse.setUserId(response.successResponse.user.id.toString());
loginResponse.setRoles(getUserRoles(response.successResponse.user));
return ResponseEntity.ok(loginResponse);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(loginResponse);
}
} catch (Exception e) {
log.error("Login error for user: {}", request.getUsername(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
@PostMapping("/refresh")
public ResponseEntity<RefreshResponse> refreshToken(@RequestBody RefreshRequest request) {
ClientResponse<RefreshResponse, Errors> response = fusionAuthClient.exchangeRefreshTokenForJWT(
new RefreshRequest(request.getRefreshToken(), 
UUID.fromString(config.getApplicationId()))
);
if (response.wasSuccessful()) {
return ResponseEntity.ok(response.successResponse);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
@PostMapping("/logout")
public ResponseEntity<Void> logout(@RequestHeader("Authorization") String authHeader) {
String token = authHeader.replace("Bearer ", "");
// FusionAuth logout
fusionAuthClient.logout(true, token);
return ResponseEntity.ok().build();
}
private List<String> getUserRoles(io.fusionauth.domain.User user) {
UserRegistration registration = user.registrations.get(UUID.fromString(config.getApplicationId()));
return registration != null ? registration.roles : Collections.emptyList();
}
}

Advanced Features Integration

1. Multi-Factor Authentication (MFA)

@Service
public class FusionAuthMFAService {
private final FusionAuthSyncClient fusionAuthClient;
public void enableMFA(String userId, MFAMethod method) {
TwoFactorRequest request = new TwoFactorRequest()
.withMethod(method.toString())
.withUserId(UUID.fromString(userId));
ClientResponse<TwoFactorResponse, Errors> response = 
fusionAuthClient.enableTwoFactor(request);
if (!response.wasSuccessful()) {
throw new MFAException("Failed to enable MFA");
}
}
public LoginResponse loginWithMFA(String username, String password, String code) {
LoginRequest loginRequest = new LoginRequest(username, password)
.withTwoFactorTrustId("device_id") // Optional device trust
.withCode(code);
ClientResponse<LoginResponse, Errors> response = 
fusionAuthClient.login(loginRequest);
if (response.wasSuccessful()) {
return response.successResponse;
} else {
throw new AuthenticationException("MFA login failed");
}
}
}

2. Social Login Integration

@Service
public class SocialLoginService {
public String getAuthorizationUrl(String provider, String redirectUri) {
// FusionAuth provides built-in social providers
// Returns OAuth2 authorization URL
return String.format("%s/oauth2/authorize?client_id=%s&response_type=code" +
"&redirect_uri=%s&scope=openid",
fusionAuthConfig.getBaseUrl(),
fusionAuthConfig.getClientId(),
redirectUri);
}
public LoginResponse handleSocialCallback(String code, String redirectUri) {
ClientResponse<OAuthResponse, OAuthError> response = 
fusionAuthClient.exchangeOAuthCodeForAccessToken(
code, fusionAuthConfig.getClientId(), 
fusionAuthConfig.getClientSecret(), redirectUri);
if (response.wasSuccessful()) {
return mapToLoginResponse(response.successResponse);
} else {
throw new SocialLoginException("Social login failed");
}
}
}

Testing FusionAuth Integration

@SpringBootTest
@Testcontainers
class FusionAuthIntegrationTest {
@Container
static GenericContainer<?> fusionauth = 
new GenericContainer<>("fusionauth/fusionauth-app:latest")
.withExposedPorts(9011)
.withEnv("DATABASE_URL", "jdbc:postgresql://postgres:5432/fusionauth")
.withEnv("FUSIONAUTH_APP_MEMORY", "512M");
@Test
void shouldRegisterUserSuccessfully() {
UserRegistrationRequest request = UserRegistrationRequest.builder()
.email("[email protected]")
.password("SecurePassword123!")
.firstName("John")
.lastName("Doe")
.username("johndoe")
.roles(List.of("USER"))
.build();
UserRegistrationResponse response = userService.registerUser(request);
assertTrue(response.isSuccess());
assertNotNull(response.getUserId());
}
@Test
void shouldAuthenticateWithValidCredentials() {
LoginRequest request = new LoginRequest("johndoe", "SecurePassword123!");
ResponseEntity<LoginResponse> response = authController.login(request);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody().getToken());
}
}

Best Practices for FusionAuth Integration

  1. Secure API Keys - Use environment variables or secret management
  2. Validate JWT Properly - Always verify issuer, audience, and expiration
  3. Handle Errors Gracefully - FusionAuth provides detailed error responses
  4. Use Webhooks - Listen for user events (registration, login, updates)
  5. Implement Rate Limiting - Protect your FusionAuth instance
  6. Regular Key Rotation - Rotate JWT signing keys periodically
  7. Monitor Performance - Track authentication latency and errors

Webhook Integration for Real-time Updates

@RestController
@RequestMapping("/webhooks/fusionauth")
@Slf4j
public class FusionAuthWebhookController {
@PostMapping("/user-registration")
public ResponseEntity<Void> handleUserRegistration(@RequestBody WebhookPayload payload) {
log.info("User registered: {}", payload.getUser().getEmail());
// Trigger welcome email, initialize user profile, etc.
userOnboardingService.onboardUser(payload.getUser());
return ResponseEntity.ok().build();
}
@PostMapping("/user-login")
public ResponseEntity<Void> handleUserLogin(@RequestBody WebhookPayload payload) {
// Track login analytics, update last login time
analyticsService.trackLogin(payload.getUser().getId());
return ResponseEntity.ok().build();
}
}

Conclusion

FusionAuth provides a robust, enterprise-ready identity platform that integrates seamlessly with Java applications. By leveraging its comprehensive API and combining it with Spring Security, you can implement secure authentication and authorization without the overhead of building and maintaining your own auth system.

The key benefits for Java teams include:

  • Rapid development with pre-built auth flows
  • Enterprise security features out of the box
  • Scalability to handle millions of users
  • Flexibility to customize workflows and user experiences
  • Cost-effectiveness compared to commercial solutions

Whether you're building a new application or modernizing an existing one, FusionAuth offers a compelling identity solution that lets you focus on your core business logic while ensuring your users' security and privacy.

Java Logistics, Shipping Integration & Enterprise Inventory Automation (Tracking, ERP, RFID & Billing Systems)

https://macronepal.com/blog/aftership-tracking-in-java-enterprise-package-visibility/
Explains how to integrate AfterShip tracking services into Java applications to provide real-time shipment visibility, delivery status updates, and centralized tracking across multiple courier services.

https://macronepal.com/blog/shipping-integration-using-fedex-api-with-java-for-logistics-automation/
Explains how to integrate the FedEx API into Java systems to automate shipping tasks such as creating shipments, calculating delivery costs, generating shipping labels, and tracking packages.

https://macronepal.com/blog/shipping-and-logistics-integrating-ups-apis-with-java-applications/
Explains UPS API integration in Java to enable automated shipping operations including rate calculation, shipment scheduling, tracking, and delivery confirmation management.

https://macronepal.com/blog/generating-and-reading-qr-codes-for-products-in-java/
Explains how Java applications generate and read QR codes for product identification, tracking, and authentication, supporting faster inventory handling and product verification processes.

https://macronepal.com/blog/designing-a-robust-pick-and-pack-workflow-in-java/
Explains how to design an efficient pick-and-pack workflow in Java warehouse systems, covering order processing, item selection, packaging steps, and logistics preparation to improve fulfillment efficiency.

https://macronepal.com/blog/rfid-inventory-management-system-in-java-a-complete-guide/
Explains how RFID technology integrates with Java applications to automate inventory tracking, reduce manual errors, and enable real-time stock monitoring in warehouses and retail environments.

https://macronepal.com/blog/erp-integration-with-odoo-in-java/
Explains how Java applications connect with Odoo ERP systems to synchronize inventory, orders, customer records, and financial data across enterprise systems.

https://macronepal.com/blog/automated-invoice-generation-creating-professional-excel-invoices-with-apache-poi-in-java/
Explains how to automatically generate professional Excel invoices in Java using Apache POI, enabling structured billing documents and automated financial record creation.

https://macronepal.com/blog/enterprise-financial-integration-using-quickbooks-api-in-java-applications/
Explains QuickBooks API integration in Java to automate financial workflows such as invoice management, payment tracking, accounting synchronization, and financial reporting.


Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper