SETUP GUIDES · SELF-HOSTED

Add self-hosted Claspine to an existing app

Your app keeps its code, database and users; Claspine adds sign-in, notifications, files, AI, payments and audit next to them.

⏱ About 30 minutes

You do not rewrite anything to use Claspine. A Spring Boot app adds Claspine as a library: one dependency, a few settings, and Claspine creates its own tables (all named spine_platform_…) in your database at the first start, next to yours. Any other backend (Node.js, Python, Go and others) keeps running as it is and calls the Claspine engine, a Docker image next to it (Run the Claspine engine). This guide covers Spring Boot step by step; the decisions in it (which database, whose login) are the same for the engine. Tested with Spring Boot 3.5 and Spring Security.

Words you’ll see

License key
The SPINE-FOUNDER-… or SPINE-AGENCY-… code that identifies your self-hosted license. Quote it when you contact support. It is not a password and cannot download anything by itself.
Download token
A secret starting with spk_ that lets your builds download Claspine’s packages. It works like a password for Maven and npm: keep it in an environment variable or a CI secret, never in your code.
Session
What keeps a person signed in. Claspine gives your app a session token after sign-in; every later request carries it. Claspine checks it, ends it at sign-out, and can end every session of a person at once.
Sign-in provider
The service that checks who a person is: Firebase Authentication (email and password, Google, Apple, phone), or Claspine’s own one-time codes sent by SMS, WhatsApp or email. Claspine trusts its answer and opens a session.

Before you start

  • Java 17 or newer and Spring Boot 3.5. (Spring Boot 4 is not supported yet. Other languages: the engine.)
  • A self-hosted license and download token, with Maven or Gradle pointed at Claspine Packages: Install self-hosted Claspine.
  • Your database: PostgreSQL, MongoDB or Google Cloud Firestore. Claspine can share the one your app already uses.

Steps

  1. Add Claspine to the build. Import the BOM com.grahmur:grahmur-platform-bom with the release you use, then add com.grahmur:grahmur-platform-backend (every capability; simplest) and com.grahmur:grahmur-platform-guardrails with test scope (snippet below). Smaller choices: grahmur-platform-starter-core (sign-in, permissions, audit), grahmur-platform-starter-saas (core plus notifications, files, payments, features), or single modules such as grahmur-platform-module-notifications.
  2. Choose where Claspine keeps its data. Set platform.persistence.type: postgres uses your spring.datasource, mongo (the default) uses spring.data.mongodb.uri, firestore uses platform.firestore.project-id. Claspine creates its tables or collections at start and never touches yours. If your app itself uses MongoDB while Claspine uses PostgreSQL or Firestore, add platform.persistence.application-mongo: true so your MongoDB keeps working.
  3. Describe your app once with platform.bootstrap: environment (DEVELOPMENT locally, PRODUCTION for real users; it cannot change later, so use one database per environment), hosts (the hostnames people reach your app on, such as localhost or shop.example.com), product.key (a short lower-case name that never changes) and the sign-in providers. The first start creates everything; later starts add what you add.
  4. Decide whose login it is. Move sign-in to Claspine: follow Sign-in in a Spring Boot app, then remove your own login controller, session table and login filter, so there is one source of truth. Your controllers take an AuthenticatedPrincipal parameter. Keep your own login: change nothing about sign-in, and pass your own user ID wherever Claspine asks who acted (Keep your own login); send email and messages to addresses with DirectMessages (Notifications in self-hosted Claspine).
  5. If the app uses Spring Security and you moved sign-in to Claspine: Claspine checks the session before Spring Security runs, so let Spring Security allow requests through and turn off its CSRF check (Claspine refuses cookie requests that change data without the X-Requested-With header). Use the SecurityFilterChain in the snippet, or remove spring-boot-starter-security if you used it only for login.
  6. Add the guardrail test src/test/java/…/ProductBoundaryTest.java (snippet). It fails the build when code, including code an AI agent wrote, reaches into Claspine’s internals instead of its api packages.
  7. Start the app. The log shows Claspine setup for product 'shop': product=… deployment=… hosts=[localhost]. Sign in once (with sandbox: true locally: POST /api/v1/auth/sessions with {"provider":"sandbox-identity","credential":"sandbox:shop:ravi","transport":"BEARER"}) and call one of your own endpoints with the returned session token.
  8. Before production: switch the sign-in provider from sandbox to Firebase or one-time codes, set environment: PRODUCTION in the production database, and run ./mvnw verify so the guardrail test runs in CI.
pom.xml (Claspine part), application.yml, the Spring Security setting for Claspine sign-in, a controller and the guardrail test. Replace 0.31.0 with the release you use.
<!-- pom.xml -->
<dependencyManagement><dependencies><dependency>
  <groupId>com.grahmur</groupId><artifactId>grahmur-platform-bom</artifactId>
  <version>0.31.0</version><type>pom</type><scope>import</scope>
</dependency></dependencies></dependencyManagement>
<dependency><groupId>com.grahmur</groupId><artifactId>grahmur-platform-backend</artifactId></dependency>
<dependency><groupId>com.grahmur</groupId><artifactId>grahmur-platform-guardrails</artifactId><scope>test</scope></dependency>

# application.yml
spring:
  datasource: { url: jdbc:postgresql://localhost:5432/shop, username: shop, password: ${DB_PASSWORD} }
platform:
  persistence:
    type: postgres                 # or mongo (default) or firestore
  bootstrap:
    enabled: true
    environment: DEVELOPMENT       # PRODUCTION for real users; cannot change later
    hosts: [localhost]             # hostnames only
    product: { key: shop, name: Shop }
    identity-providers:
      sandbox: true                # local testing only
      # firebase-project-id: my-firebase-project

// SecurityConfig.java: only if the app uses Spring Security and sign-in moved to Claspine
@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
    return http.authorizeHttpRequests(a -> a.anyRequest().permitAll())  // Claspine decides who is signed in
            .csrf(c -> c.disable())                                    // Claspine checks X-Requested-With
            .httpBasic(b -> b.disable()).formLogin(f -> f.disable())
            .build();
}

// A controller: signed-out callers get 401 automatically
@GetMapping("/api/me")
Map<String, String> me(AuthenticatedPrincipal principal) {
    return Map.of("accountId", principal.accountId().value());
}

// src/test/java/com/acme/shop/ProductBoundaryTest.java
@AnalyzeClasses(locations = SpineGuardrails.ProjectClasses.class, importOptions = ImportOption.DoNotIncludeTests.class)
class ProductBoundaryTest {
    @ArchTest
    static final ArchTests spine_guardrails = ArchTests.in(SpineGuardrails.class);
}

What goes where in Claspine

Console fieldValue
platform.persistence.typepostgres, mongo (default) or firestore: where Claspine keeps its own tables. Your app’s data stays where it is.
platform.bootstrap.environmentDEVELOPMENT, TEST, STAGING or PRODUCTION. Sandbox sign-in works only in DEVELOPMENT and TEST. Fixed after the first start.
platform.bootstrap.hostsHostnames your users reach the app on (no IP addresses; localhost also covers 127.0.0.1 and the Android emulator in DEVELOPMENT/TEST).
platform.bootstrap.product.keyA short lower-case name for the app, for example shop. Defaults to spring.application.name. Never change it.
Imports in your codeOnly com.grahmur.platform.modules.<capability>.api and com.grahmur.platform.foundation packages. ProductBoundaryTest enforces this.

Keep it safe

  • Never read or change the spine_platform_… tables yourself, not even with a quick SQL fix: Claspine refuses it at runtime, in tests and at startup. Use the capability APIs (java_api on the MCP server lists them).
  • One login: when sign-in moves to Claspine, delete the old session table and login filter instead of keeping both.
  • Sandbox sign-in proves nothing (anyone can claim any user). Claspine refuses it in STAGING and PRODUCTION; never run real users on a DEVELOPMENT deployment.
  • Optional extra defence: give Claspine its own database user (platform.persistence.postgres.datasource.*, platform.persistence.mongo.uri), so your app’s credentials cannot reach Claspine’s tables at all.

Claspine stores the values encrypted, never shows them again in full, and never sends them to your app or SDK.

If something goes wrong

POST /api/v1/auth/sessions answers 401 and the log says “Using generated security password”.
Spring Security is blocking Claspine’s sign-in. Add the SecurityFilterChain from the snippet (permitAll, CSRF off), or remove spring-boot-starter-security if it only did login.
Requests answer 404 HOST_NOT_AVAILABLE.
The Host of the request is not in platform.bootstrap.hosts. Add it and restart (hosts are added, never removed).
PlatformDataAccessDeniedException, or startup stops naming a spine_platform_ table.
Your code names a Claspine table. Replace it with the capability API; for lookups use ProductConfiguration.findByKey, ApplicationBootstrap.current() and similar.
The app tries to connect to MongoDB on localhost:27017 although Claspine uses PostgreSQL.
Your own code uses MongoDB: set spring.data.mongodb.uri, or platform.persistence.application-mongo: false if it does not.
Hibernate wants to change or validate spine_platform_ tables.
Use spring.jpa.hibernate.ddl-auto: validate or none and manage your own tables with migrations; Claspine creates its tables itself.

← All setup guides