Milesoft App-Stack Library (app-stack)

1. Overview & Purpose

The app-stack library provides the foundational architecture and auto-configured capabilities for building secure, observable, and tenant-isolated Spring Boot applications within the Milesoft ecosystem.

Rather than writing boilerplate controller CRUD endpoints, configuring Spring Security rules from scratch, or manually implementing token-refresh interceptors, backend applications inherit these capabilities instantly by consuming app-stack.

1.1 Core Capabilities & Architectural Layering

  • Abstract Web Controllers: Pre-built, audited (@Audited) Spring MVC REST controllers offering out-of-the-box CRUD and search integrations with core platform APIs (Accounts, Users, Profiles, Notifications, OAuth).
  • Tenant-Bound Security & Verification: Robust Spring Security base configs establishing multi-tenant and single-tenant caller contexts, leveraging highly specific AuthValidator suites.
  • Transparent Self-Healing Tokens: Automatic in-flight interception and rotation of expired platform JWTs without user-facing interruptions or frontend redirect cycles.
  • Localized OAuth Encryption & Caching: Null-safe, secure stashing and retrieval of external third-party credential sets (Stripe, Square, QuickBooks) using standard symmetric encryption and TTL-aware refreshes.
  • Domain Cache Synchronization: Declares standardized caching regions and automatically coordinates eviction and updates (via Spring Cache) across user, profile, and account data boundaries.

2. Installation & Status

The app-stack package is hosted in our secure Google Cloud Artifact Registry repository.

To consume app-stack in your Spring Boot application, apply the Google Cloud Artifact Registry plugin and declare our secure repository in your build.gradle:

plugins {
    id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.5"
}

repositories {
    mavenCentral()
    maven { url "artifactregistry://us-west2-maven.pkg.dev/milesoft-repo/repo-maven" }
}

dependencies {
    // Declaring the standard app-stack library dependency
    implementation "io.milesoft:app-stack:2.2.0"
}

2.1 Status

The app-stack library is Production-Ready and forms the unified foundation for all backend service applications across the Milesoft platform.


3. Core Security & Authentication Configurations

3.1 CORS & Base Web Security (io.milesoft.app.config.AbstractSecurityConfig)

Extending BaseSecurityConfig, the AbstractSecurityConfig provides robust standard defaults for Spring Security, cross-origin resource sharing (CORS), and background Pub/Sub integration.

  • Automated CORS Filtering: Automatically handles Origin matching and safely exposes standard headers, including the platform's Authorization header. CORS patterns can be customized dynamically by overriding allowed origins list and API versions.
  • Pub/Sub Token Resolution: Seamlessly integrates a PubSubTokenResolver wrapper around standard token verification to process secure, multi-tenant message payloads routed via Google Cloud Pub/Sub topics.
import io.milesoft.app.config.AbstractSecurityConfig;
import io.milesoft.stack.domain.CurrentUser;
import io.milesoft.stack.strategy.TokenResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.SecurityFilterChain;
import java.util.Set;

@Configuration
public class SecurityConfig extends AbstractSecurityConfig {

    public SecurityConfig(CurrentUser currentUser, TokenResolver tokenResolver) {
        super(Options.builder()
            .currentUser(currentUser)
            .tokenResolver(tokenResolver)
            .allowedOrigins(List.of("https://*.milesoft.app", "http://localhost:3000"))
            .apiVersions(Set.of(1, 2))
            .pubSubProject("milesoft-prod")
            .build());
    }
}

3.2 Tenant Isolation Configs (AbstractMultiTenantAuthConfig vs. AbstractSingleTenantAuthConfig)

Depending on whether your microservice is shared across multiple subscriber domains or dedicated to a single specific subscriber tenant, you subclass one of two auth configuration blueprints:

A. Multi-Tenant Authentication (AbstractMultiTenantAuthConfig)

Allows access from any subscriber tenant (account) registered on the platform. It enforces that incoming requests originate from a validated platform application identifier.

  • Active Validators: IsCorrectAppAuthValidator, IsNotProxyAuthValidator.

B. Single-Tenant Authentication (AbstractSingleTenantAuthConfig)

Restricts access to a single, explicitly designated subscriber tenant. Attempts to execute requests from other tenant spaces are blocked at the gateway level.

  • Active Validators: IsCorrectAppAuthValidator, IsCorrectAccountAuthValidator, IsNotProxyAuthValidator.
// Example: Dedicated Single-Tenant Configuration
import io.milesoft.app.config.AbstractSingleTenantAuthConfig;
import org.springframework.context.annotation.Configuration;
import java.time.Clock;
import org.springframework.cache.CacheManager;

@Configuration
public class AuthConfig extends AbstractSingleTenantAuthConfig {
    public AuthConfig(
        Clock clock, PlatformClient platformClient, AuthClient authClient,
        CacheManager cacheManager, RefreshTokenManager refreshTokenManager) {
        
        super(clock, platformClient, authClient, cacheManager, refreshTokenManager,
            "https://accounts.milesoft.api", "my-app-id", "exclusive-account-id");
    }
}

4. Self-Healing Transparent Token Interception

4.1 In-Flight Expired Token Rotation (io.milesoft.app.strategy.RefreshingPlatformTokenResolver)

To prevent microservices from failing on expired authorization states or forcing client-facing React webapps/mobile apps to perform messy auth redirection, app-stack provides the RefreshingPlatformTokenResolver.

When an ExpiredJwtException is caught during filter chain execution, the resolver handles self-healing:

  1. Token Query: Extracts the unique JWT Identifier (jti) and active Session ID (sid) from the expired token's claims.
  2. Refresh Key Lookup: Queries the localized RefreshTokenManager using the token's jti to resolve the corresponding secure refresh token.
  3. Core Refreshes: Dispatches an asynchronous token rotation call to the core PlatformClient to generate a fresh pair of access and refresh tokens.
  4. Context & Header Injection: Seamlessly updates the Spring CurrentUser context and injects the new token into the HTTP response header (Authorization: Bearer <new_access_token>).

4.2 Panic Mode & Breach Detection

If a transparent refresh is attempted using an already-used or invalid JWT identifier, RefreshingPlatformTokenResolver automatically flags the request as a potential security compromise:

  • Evicts Cache: Immediately purges all token mappings and cached sessions associated with the active Session ID (sid) from the local redis/memory layer.
  • Identity Provider (IdP) Propagation: Calls AuthClient.revoke(sid) to invalidate the session globally across all platform APIs, instantly forcing logout and blocking subsequent requests.

5. Abstract Spring Web Controllers

Microservices implementing standard domain concepts quickly extend these audited controller templates to inherit standard paths, parameters, validations, and mapping transformations:

Abstract Controller Class Context Endpoints Core Operations Cache Synchronization Hooks
AbstractUserController /list, /admins, /removed, /find, /{userId}/ CRUD users, manage admins, re-invite inactive users Synchronizes user profiles and directory cache on mutation.
AbstractAccountController / (GET, PUT) Fetch and modify subscriber account metadata Evicts and updates account-wide caching.
AbstractProfileController /, /contact, /settings, /eula User-level profile edits, settings adjustments, EULA consent Dynamically updates user and profile caches.
AbstractNotificationController /list, /markAsRead, /bulkDelete, /preferences List user notifications, toggle read states, adjust delivery rules Integrates with notifications-sdk.
AbstractOauthController /integration/list, /tokens/{integrationId}/ Manage dynamic OAuth tokens, update configurations, remove linkings Localized OAuth cache synchronization.

5.1 Controller Customization Example

import io.milesoft.app.api.v2.controllers.AbstractUserController;
import io.milesoft.accounts.client.v2.UserClient;
import io.milesoft.app.services.AbstractAccountService;
import io.milesoft.stack.aop.Audited;
import io.milesoft.stack.domain.CurrentUser;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Audited
@RestController
@RequestMapping("/api/v1/users")
public class UserController extends AbstractUserController {

    public UserController(CurrentUser currentUser, UserClient userClient, AbstractAccountService accountService) {
        super(currentUser, userClient, accountService);
    }
    
    // All user list, load, create, modify, delete, and reinvite endpoints are automatically active!
}

6. Secure Third-Party OAuth Encryption

6.1 Token Lifecycle Management (io.milesoft.app.services.AbstractOauthService)

For microservices that integrate with external providers (e.g. Stripe, Square, QuickBooks), AbstractOauthService and AbstractOauthCache provide standard token-caching, symmetric encryption, and lazy refresh cycles.

  • Encrypted Cache Storage: When third-party tokens are obtained, they are symmetrically encrypted using a configured Spring TextEncryptor before being stored in the tokens cache.
  • Lazy Renewals (isFresh): Prior to retrieving tokens, the service validates their lifespan. If the token expires within 5 minutes of the current system time, it bypasses the cache, invokes OauthClient.loadTokens() to rotate them at the IdP level, and re-caches the fresh credentials.
import io.milesoft.app.services.AbstractOauthService;
import io.milesoft.app.domain.EphemeralTokens;
import org.springframework.stereotype.Service;
import java.util.Optional;

@Service
public class OauthService extends AbstractOauthService {
    // Custom service layers request tokens seamlessly:
    public String getStripeAccessToken(String integrationId) {
        Optional<EphemeralTokens> tokens = getTokens(integrationId);
        return tokens.map(EphemeralTokens::accessToken).orElse(null);
    }
}

7. Caching & State Synchronization

To minimize repetitive HTTP requests to upstream central APIs (like the identity service), the app-stack provides automated caching abstractions.

7.1 Cache Managers & Configurations (AbstractCacheConfig & AbstractAccountService)

By subclassing AbstractCacheConfig, applications automatically register standard caching regions and map them to standard time-to-live (TTL) managers (such as the SHORT_TERM cache manager).

Standardized caching regions registered by default:

  • accounts: Caches account details mapped by @currentUser.accountId().
  • profiles: Caches user profile details mapped by @currentUser.accountId() + '_' + @currentUser.userId().
  • users: Caches user records mapped by @currentUser.accountId() + '_' + #userId.
  • integrations: Caches registered third-party OAuth integrations.
  • tokens: Caches active encrypted external credentials.

7.2 Automatic Cache Synchronization

AbstractAccountService uses standard Spring caching annotations to ensure that read operations are rapid, and mutations dynamically update or evict related data:

public abstract class AbstractAccountService {
    
    // Reads from Cache
    @Cacheable(cacheNames = "profiles", cacheManager = SHORT_TERM, key = "@currentUser.accountId() + '_' + @currentUser.userId()")
    public Profile loadProfile() { ... }

    // Overwrites Cache on Mutation
    @CachePut(cacheNames = "profiles", cacheManager = SHORT_TERM, key = "@currentUser.accountId() + '_' + @currentUser.userId()")
    public Profile syncProfileCache(Profile profile) {
        return profile;
    }

    // Automatically Evicts on User Modification (handles model skew)
    @CacheEvict(cacheNames = "profiles", cacheManager = SHORT_TERM, key = "@currentUser.accountId() + '_' + #user.id")
    public void syncProfileCache(User user) { }
}