IDP Server-Side Implementation Overview¶
Introduction¶
The Identity Provider (IDP) server-side implementation provides secure JWT-based authentication and authorization for headless commerce applications. It consists of two main modules:
idp-server- Validates and processes JWT tokens from external IDP servicesidp-client- Handles OAuth2 client credentials flow and token caching
Architecture Overview¶
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ External IDP │ │ IDP Server │ │ IDP Client │
│ (Auth Server) │◄───┤ (Validator) │◄───┤ (Token Mgmt) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
├─ JWT Tokens ├─ Security Config ├─ OAuth2 Flow
├─ JWK Keys ├─ JWT Validation ├─ Token Caching
└─ Device Registration ├─ Offline Support └─ API Client
└─ WebSocket Security
IDP Server Module (headless/core/idp-server)¶
Core Components¶
1. IdpAutoConfiguration¶
@AutoConfiguration
@EnableConfigurationProperties(IdpProperties.class)
@Import({SecurityConfig.class, IdpWebsocketInterceptor.class})
public class IdpAutoConfiguration
2. IdpProperties¶
Configuration properties for IDP server:
@ConfigurationProperties(prefix = "secured.idp")
public class IdpProperties {
private boolean enabled = false;
private String baseUrl;
private String authPoolId;
private String jwksCacheFile;
private int httpTimeoutInMs = 2000;
private int offlineExpirationLeniencyInMinutes = 4320; // 3 days
}
Security Framework¶
1. SecurityConfig¶
Main security configuration that: - Configures OAuth2 Resource Server with JWT validation - Sets up public endpoints (websockets, static assets) - Implements localhost bypass for development - Configures custom JWT decoder with ES256 algorithm support
Key features:
@Bean
public JwtDecoder jwtDecoder() throws Exception {
// Custom JWT processor with ES256 support
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWTClaimsSetVerifier(new LenientJWTClaimsVerifier());
// File-cached JWK source for offline support
JWKSource<SecurityContext> jwkSource = new FileCachingJwkSource(
new URL(jwkSetUri), cacheFile, httpTimeoutInMs);
// Lenient expiration validation for offline scenarios
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
new IgnoringTimestampFailuresValidator(JwtValidators.createDefault()),
new LenientExpirationValidator(leewayDuration, idpAvailabilityChecker)
));
}
2. FileCachingJwkSource¶
Provides resilient JWK (JSON Web Key) management: - Primary: Fetches JWK sets from remote IDP - Fallback: Uses local file cache when remote is unavailable - Auto-caching: Saves successful fetches to local file system
@Override
public List<JWK> get(JWKSelector jwkSelector, SecurityContext context) {
try {
// Try remote first
List<JWK> jwkSet = remoteJwkSet.get(jwkSelector, context);
saveToCache(cachedSet); // Cache successful fetch
return jwkSet;
} catch (Exception e) {
// Fallback to local cache
return loadFromCache().select(jwkSelector);
}
}
Offline Support & Resilience¶
1. IdpAvailabilityChecker¶
Determines if external IDP is reachable:
public boolean isIdpOnline() {
try {
HttpURLConnection connection = (HttpURLConnection) new URL(jwkSetUri).openConnection();
connection.setRequestMethod("HEAD");
connection.setConnectTimeout(timeout);
return connection.getResponseCode() == 200;
} catch (Exception e) {
return false;
}
}
2. LenientExpirationValidator¶
Implements smart token expiration handling: - Online Mode: Standard JWT expiration validation - Offline Mode: Allows tokens to be valid beyond expiration within configured leeway period - Hybrid: Automatically switches based on IDP availability
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
Instant now = clock.instant();
Instant exp = jwt.getExpiresAt();
if (now.isBefore(exp)) {
return OAuth2TokenValidatorResult.success(); // Still valid
}
boolean idpOnline = idpChecker.isIdpOnline();
if (!idpOnline && now.isBefore(exp.plus(leeway))) {
return OAuth2TokenValidatorResult.success(); // Allow leeway if offline
}
return failure("JWT expired");
}
3. LenientJWTClaimsVerifier & IgnoringTimestampFailuresValidator¶
Additional validation components that provide flexible JWT processing for edge cases and offline scenarios.
IDP Client Module (headless/core/idp-client)¶
Core Components¶
1. IdpClientAutoConfiguration¶
@AutoConfiguration
@EnableConfigurationProperties(IdpClientProperties.class)
@Import({SecureClientConfig.class, NoSecurityConfig.class})
2. IdpClientProperties¶
Configuration for client-side token management:
@ConfigurationProperties(prefix = "secured.idp")
public class IdpClientProperties {
private int offlineExpirationLeniencyInMinutes = 4320;
}
OAuth2 Client Implementation¶
1. SecureClientConfig¶
Configures OAuth2 client credentials flow:
@Bean
public OAuth2AuthorizedClientManager oauth2AuthorizedClientManager(...) {
OAuth2AuthorizedClientProvider provider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials() // Client credentials grant type
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager manager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(repo, service);
manager.setAuthorizedClientProvider(provider);
return manager;
}
2. JwtTokenRetriever¶
Simple token retrieval service:
public String retrieve() {
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest
.withClientRegistrationId("server")
.principal("system")
.build();
OAuth2AuthorizedClient client = clientManager.authorize(request);
return client.getAccessToken().getTokenValue();
}
Token Caching Strategy¶
1. CachingOAuth2Interceptor¶
HTTP interceptor that provides intelligent token management: - Primary: Attempts to fetch fresh tokens from OAuth2 server - Fallback: Uses cached tokens when server is unavailable - Validation: Checks token expiration with configured leeway
private String fetchOrLoadToken() {
try {
// Try to get fresh token
OAuth2AuthorizedClient client = delegate.authorize(authorizeRequest);
if (client != null) {
String tokenValue = client.getAccessToken().getTokenValue();
tokenCacheStrategy.saveToken(tokenValue); // Cache it
return tokenValue;
}
} catch (Exception e) {
// Fall back to cached token
}
String cachedToken = tokenCacheStrategy.loadToken();
if (cachedToken != null && isTokenStillValid(cachedToken)) {
return cachedToken;
}
throw new IllegalStateException("No valid access token available");
}
2. Token Cache Strategies¶
FileTokenCacheStrategy:
- Secure storage for production environments
- Generates a new AES key and encrypts the cached jwt for long term storage
- To enable set secured.idp-token-cache.type=file which is the default behavior
- To secure set an environment variable for the password SECURED_IDP_TOKEN_CACHE_PASSWORD=your-secure-password
NoopTokenCacheStrategy: - No-op implementation for testing/development or non-IDP enabled environments - Tokens are not persisted between application restarts
Device Management API¶
1. IdpApiClient¶
RESTful client for device registration and management:
public class IdpApiClient {
// Device registration
public CreateDeviceResponse createDevice(String orgId, String envId, CreateDeviceRequest request)
// Client app creation
public CreateClientAppIdResponse createClientAppId(String orgId, String envId, String deviceId, CreateClientAppIdRequest request)
// OTP generation
public CreateOtpResponse createOneTimePassword(String orgId, String envId, String deviceId, String deviceClientAppId)
// Device activation
public ActivateResponse activate(ActivateRequest request)
}
2. API Models¶
Data transfer objects for device registration flow:
- CreateDeviceRequest/Response
- CreateClientAppIdRequest/Response
- CreateOtpResponse
- ActivateRequest/Response
Configuration Example¶
secured:
api:
enabled: false
idp:
enabled: true
baseUrl: https://idp.env.jumpmind.cloud
authPoolId: client-pool-id
organizationId: org-id
apiKey: api-key
env: dev
jwksCacheFile: ./work/jwks.json
httpTimeoutInMs: 2000
offlineExpirationLeniencyInMinutes: 4320
idp-token-cache:
type: file
password: ${LOAD_SECURE_PASSWORD}
spring:
security:
oauth2:
client:
registration:
server:
client-id: client-id
client-secret: client-secret
authorization-grant-type: client_credentials
scope: device
provider: jmc-idp
provider:
jmc-idp:
token-uri: ${secured.idp.baseUrl}/${secured.idp.authPoolId}/oauth2/token
resourceserver:
jwt:
jwk-set-uri: ${secured.idp.baseUrl}/${secured.idp.authPoolId}/.well-known/jwks.json