Ogiri adds database-backed opaque sessions and devise_token_auth (DTA) account endpoints to a Spring Boot application. Existing DTA frontends keep working. Every authenticated request checks the stored session and the current account. Your application keeps its account model and authorization rules.
Requirements: Spring Boot 4.x (built against 4.1.2), Java 17+, Jackson 3, PostgreSQL or MySQL 8.4 (InnoDB). Boot 3 and Jackson 2 are unsupported. Version 0.1.0 is an unpublished prototype; before 1.0, API and schema changes can break compatibility (see the changelog).
Coding agents: use the ogiri-integration skill to add Ogiri to an app, and auth-design to plan auth first.
Ogiri is not on Maven Central yet. Build it from a checkout with the tools in mise.toml:
mise exec -- bash scripts/platform/java.sh sampleThis stages the artifacts in target/java/consumer-cache and writes target/java/consumer-settings.xml; pass that file with mvn -s from your application. It needs no database and does not touch your shared Maven cache.
In your application's POM (with spring-boot-starter-parent 4.1.2):
<properties>
<jackson-bom.version>3.2.4</jackson-bom.version>
<tomcat.version>11.0.27</tomcat.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.quantipixels.ogiri</groupId>
<artifactId>ogiri-bom</artifactId>
<version>0.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.quantipixels.ogiri</groupId>
<artifactId>ogiri-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>For MySQL use com.mysql:mysql-connector-j. The Jackson override matches the Jackson version Ogiri is built against; the Tomcat pin matches Boot 4.1.2's default and keeps older Boot 4.1.x releases on a patched Tomcat. The BOM aligns only Ogiri artifacts.
Ogiri runs no DDL at startup. Through your migration tool, apply from the ogiri JAR:
META-INF/ogiri/ogiri-postgresql.sql(sessions and lock tables), always;META-INF/ogiri/users-postgresql.sql(a Devise-shaped account table), unless you already have one.
MySQL uses the -mysql.sql files. The same files are in contracts/schema. An existing account table needs the columns in users-<engine>.sql, including ogiri_generation. To upgrade a schema from before account generations, see Schema and upgrades.
For disposable local databases, mise run db:up starts PostgreSQL and MySQL with Docker (credentials and ports in compose.yaml).
spring.datasource.url=jdbc:postgresql://localhost:5432/app
spring.datasource.username=app
spring.datasource.password=secret
spring.mail.host=localhost
spring.mail.port=1025
ogiri.public-base-url=https://app.example.com
ogiri.redirect-allowlist=https://app.example.comStartup needs ogiri.public-base-url (an absolute HTTP(S) URL used in mail links) and a mailer: either spring.mail.* or an OgiriMailer bean, even with confirmation off. Set ogiri.redirect-allowlist before using confirmation, password-reset or unlock links; SECURITY.md explains what an empty list accepts.
This gives one realm, users, mounted at /auth on table users. Common settings (all in OgiriProperties):
| Property | Default | Purpose |
|---|---|---|
ogiri.base-path |
/auth |
Mount for the endpoints |
ogiri.account.table |
users |
Account table for the default store |
ogiri.account.columns |
empty | Extra account columns returned in responses |
ogiri.permitted-fields |
name,nickname,image |
Extra fields accepted on sign-up and update |
ogiri.token-lifespan |
14d |
Session lifetime |
ogiri.max-number-of-devices |
10 |
Sessions per account; the soonest-expiring is evicted |
ogiri.change-headers-on-each-request |
false |
Rotate the token on each request |
ogiri.confirmable / ogiri.lockable |
false |
Email confirmation / lock after maximum-attempts (20) failures |
ogiri.remove-tokens-after-password-reset |
false |
End other sessions after a reset |
ogiri.cookie-enabled |
false |
Also issue an HttpOnly auth cookie |
ogiri.cookie-secure |
true |
Set false only for local HTTP |
ogiri.cleanup.interval |
unset | Enable deleting expired sessions, e.g. 1h |
ogiri.realms.<name>.* |
Additional realms; inherit root settings |
Without your own SecurityFilterChain, the starter supplies a stateless chain: sign-up, sign-in, confirmation, unlock and password recovery are public, and everything else requires authentication.
| Method and path | Purpose |
|---|---|
POST /auth |
Sign up (signs in unless confirmable) |
PUT/PATCH /auth, DELETE /auth |
Update or delete the current account |
POST /auth/sign_in, DELETE /auth/sign_out |
Sign in, sign out |
GET /auth/validate_token |
Check credentials and return the account |
POST /auth/password, GET /auth/password/edit, PUT/PATCH /auth/password |
Password recovery and change |
GET/POST /auth/confirmation, GET/POST /auth/unlock |
Email confirmation and unlock |
GET /auth/sessions, DELETE /auth/sessions/{client}, DELETE /auth/sessions |
List and revoke the account's sessions |
contracts/PROTOCOL.md specifies every request and response.
curl -i "$BASE_URL/auth" -H 'Content-Type: application/json' \
--data '{"email":"demo@example.com","password":"test-password","password_confirmation":"test-password"}'
curl -i "$BASE_URL/auth/sign_in" -H 'Content-Type: application/json' \
--data '{"email":"demo@example.com","password":"test-password"}'
curl -i "$BASE_URL/auth/validate_token" \
-H "access-token: $ACCESS_TOKEN" -H "client: $CLIENT" -H "uid: $AUTH_UID"Responses carry access-token, client, uid, expiry and token-type headers, plus an Authorization: Bearer header that encodes the same set as base64 JSON (not a JWT). Send either form back. Credentials in query strings or form fields are ignored.
Cookie mode (ogiri.cookie-enabled=true): first send a safe request such as GET /auth/sign_in (a 405 is fine) and keep the XSRF-TOKEN cookie. Send it back with its value in X-XSRF-TOKEN on sign-in, sign-up and every unsafe cookie-authenticated request. Header credentials need no CSRF token. Ogiri's own CSRF filter does this check; do not disable your application's CSRF protection.
Browsers on other origins: configure CORS in your application and call OgiriConfigurer.exposeHeaders(cors) so the credential headers are readable.
Use Spring Security as usual. The principal is OgiriAccount (a UserDetails):
@GetMapping("/me")
Map<String, String> me(@AuthenticationPrincipal OgiriAccount account) {
return Map.of("uid", account.uid());
}Map roles with an OgiriAuthorities bean. With your own SecurityFilterChain, add http.with(OgiriConfigurer.ogiri(realms), c -> {}) and permit Ogiri's public workflows; see Spring Security and credentials.
To replace a part of the default realm, declare a bean of that type: AccountStore (your own account model), OgiriMailer (mail delivery). For an additional realm, name the bean after the realm:
| Bean name | Type |
|---|---|
<realm>OgiriAccountStore |
com.quantipixels.ogiri.account.AccountStore |
<realm>OgiriMailer |
com.quantipixels.ogiri.mail.OgiriMailer |
<realm>OgiriPasswordEncoder |
com.quantipixels.ogiri.account.internal.OgiriPasswords |
<realm>OgiriAccountValidator |
com.quantipixels.ogiri.account.internal.AccountValidator |
Declare realm-only beans with @Bean(defaultCandidate = false). The last two use internal types that can change before 1.0. Only the 18 public types listed in the architecture notes are supported; *.internal packages are not. A custom AccountStore must use Ogiri's DataSource and supply an immutable ogiri_generation for each account.
- Spring architecture: realms, bean resolution, transactions, cookies, events and operations.
- Security: threat model, defaults and how to report vulnerabilities.
- Sample application: a working host with roles, two realms and cookie mode.
- Protocol: HTTP contract and differences from DTA.
- Contributing and publishing.
Licensed under Apache-2.0.