ICE - identity for the whole product family

One login. One scope catalog.
One root of trust.

ICE is the single sign-in and authorization server for the whole Splenta family. Every product validates its tokens; nobody re-implements identity, tenancy or login.

ICE access tokenone token, checked everywhere
  • issuerice.<customer>who signed it
  • tenantmercy-generalacting in
  • scopehms:patient:read, ice:user:readwhat it may do
  • audiencehms-apiwho it is for

One RS256 token. Every product verifies it against ICE's public key - no shared secret, no callback home.

Abstract streams of code on a dark screen
An abstract security visualization. Photo is illustrative, not a screen from ICE.

What ICE is

Identity across the Splenta family.

Not a bolt-on login for one app - the shared identity and access layer every product is built on. One short line each, no jargon.

Single sign-in

One login, one issuer

Sign in once through ICE. Every other product (SCM, Arin, RMS, WMS, FMS, HMS) trusts the same token - none ships its own login form.

One vocabulary

One scope catalog

129 permissions, defined once as code. Every product reads the same vocabulary; none invents its own.

Tenancy

Multi-tenant IAM

One global identity across many tenants. Switch tenant and the token is re-minted - so no product builds tenancy for itself.

Access model

Roles, permissions, groups

App-roles bundle permissions; groups carry roles to their members. Business roles stay in the product that owns them.

Policy

ABAC policy

A closed, fail-closed condition policy grants or revokes access at sign-in time. Revoke always beats grant.

Migration

Gradual enforcement

Move each product from off to report to enforce on its own schedule - never one irreversible switch for everyone.

The trust chain

ICE signs. Each product verifies.

Resource servers never hold a signing key and never call back to ICE to check a token. They verify a signature against a key ICE publishes, confirm the token is meant for them, and decide on their own what its scopes allow. One issuer decides; each product enforces.
  • A person signs in once - no product ships its own login form.
  • Products cache the public key, so a brief ICE blip never stops verification.
  • No shared secret ever leaves ICE, and no token is phoned home to check.
A corridor lined with server racks in a data centerInfrastructure
A data-center corridor. Illustrative photo.

Enforcement modes

Migrate at your own pace.

Flipping identity enforcement on in one step is how you lock people out. ICE has three modes and a per-product override, so a tenant migrates gradually - watch what would be denied, close the gaps, then flip one product to enforce when it is ready, not all of them at once.
  • off - nothing is checked, before a tenant is modelled.
  • report - the default: ICE logs what it would deny, but issues the scopes anyway.
  • enforce - scopes are narrowed to exactly what the permissions allow.
A close-up of a processor chip on a circuit boardCompute
A processor on a circuit board. Illustrative photo.

Platform identity

One issuer. Consistent enforcement.

ICE gives every Splenta product, user, service and agent one shared identity and authorization model, deployed within each customer's environment.

ICEAuth0 / OktaKeycloakOryCognitoEntra ID
What it isSelf-hosted OAuth 2.1 server + IAM for one product familyTurnkey hosted consumer identity (CIAM)Open-source IdP with realms and brokeringCertified, headless OAuth2 / OIDCManaged user pools, AWS-nativeEnterprise SSO and conditional access
DeploymentOne dedicated instance per customer, inside your own VPCHosted SaaS (self-host on higher tiers)Self-hosted or managedCloud or self-hostedManaged AWS serviceManaged Microsoft cloud
Shared scope catalogOne code-verified 129-scope catalog across all productsDefine your own APIs and scopesClient scopes per realmOAuth2 scopes you defineResource-server scopes you defineApp roles and scopes you define
MFA / SSO / SAML / SCIMNone today - not a general CIAMExtensive, built inBroad, built inVia the wider Ory stackMFA and SAML / OIDC federationEnterprise-grade, built in
Best fitThe Splenta family's shared identity planeSecuring your own app fastA self-hosted general-purpose IdPCloud-native API authorization at scaleApps already on AWSMicrosoft-centric enterprises

Under the hood

Central signing. Product-level enforcement.

The story above is the plain version. For evaluators, here is the mechanism - ICE is built on Spring Authorization Server (Java 17, PostgreSQL), and every other product is a pure resource server that holds no signing key.

The signed claim contract

Access decisions travel inside the token as claims. ICE mints an RS256 JWT; the audience is derived from the scope prefixes, never configured per client. The public JWKS on the right is the only thing a product needs to verify it.

decoded access token :: ICE claim contractRS256
{
"iss": "https://ice.<customer>",
"sub": "usr_9f3c1a7e",
"token_type": "access",
"principal_type": "user",
"tenant": "mercy-general",
"app_roles": ["hms:hms-admin"],
"scope": ["hms:patient:read", "ice:user:read"],
"aud": ["hms-api"]
}
// aud derived from scope prefixes, never configured per client
GET /oauth2/jwks :: public verification keyno shared secrets
{ "keys": [ {
"kty": "RSA",
"kid": "ice-rs256",
"alg": "RS256",
"use": "sig",
"n": "0vx7ag...Kd9GY",
"e": "AQAB"
} ] }

Verify, then authorize

Resource servers never hold a secret from ICE and never call back to check a token. They verify a signature against a published key, confirm the token is for them, then decide on their own what its scopes allow.

sign in

A person signs in

authorization_code + PKCE, once

PDP - decides

ICE mints a token

RS256 JWT of resolved claims

public key

Product reads the JWKS

Cached from /oauth2/jwks

PEP - enforces

Validate + enforce scopes

Per request, holds no signing key

resource server :: validate before authorizeholds no signing key
// four checks, then per-request scope enforcement
1iss== https://ice.<customer>
2sigverified against /oauth2/jwks (RS256)
3audcontains this server's audience
4token_type== "access"
// then, per request:
require SCOPE_hms:patient:read
tenant of record = the tenant claim

One scope catalog - 129 scopes

Every scope in the family is defined once, as code, in a single catalog ICE owns. It exists as both a document and a source file, held in strict parity by a test - so what a product reads is exactly what ICE issues. Authority reads the same everywhere: domain:resource:action, and the audience comes from the prefix.

iceaud=ice-api

ICE - identity + IAM

  • ice:user:read
  • ice:tenant:write
  • ice:policy:write
scmaud=scm-api

SCM - procurement

  • scm:po:approve
arinaud=arin-api

Arin - agent layer

  • arin:agent-action:write
rmsaud=rms-api

RMS - retail

  • rms:order:write
wmsaud=wms-api

WMS - warehousing

  • wms:stock:write
fmsaud=fms-api

FMS - finance

  • fms:gl:write
hmsaud=hms-api

HMS - healthcare

  • hms:patient:read

129 total

Across all seven domains. The names above are representative, not the whole set - the catalog is the source of truth, parity-gated in CI.

The seeded client roster

Two grant types keep the boundary clear: authorization_code + PKCE for human workbenches, and client_credentials for agents and services. The shared client catalog remains the source of truth.

Human clients

authorization_code + PKCE - public - 15-min access / 7-day refresh

  • ice-workbench
  • scm-workbench
  • arin-workbench
  • rms-merchant
  • rms-admin
  • wms-workbench
  • hms-workbench

Machine clients

client_credentials - agent_id-tagged - identity-neutral claims

  • arin-scm-client
  • scm-service
  • wms-service
  • arin-rms-client
  • arin-wms-client
  • arin-hms-client
  • ice-mcp-dev

Audience routing, live

Mint one token, then watch its audience recompute from the scope prefixes as independent resource servers accept or ignore it. One signature, checked against the published JWKS - each server honours only the scopes prefixed with its own domain.

POST /oauth2/tokenissuing
Principal

grant: client_credentials

Scopes granted

Toggle scopes. The audience is derived from their prefixes - never configured per client.

Minted access token

{
  alg: "RS256", kid: "ice-rs256"
  iss: "https://ice.splenta.internal"
  sub: "arin-scm-client"
  principal_type: "CLIENT"
  aud: ["arin-api", "scm-api"]
}

signed RS256 - verifiable against /oauth2/jwks

Verified independently by

  • SCM aud=scm-apiaccepts
  • Arin aud=arin-apiaccepts
  • RMS aud=rms-apino authority
  • HMS aud=hms-apino authority

One signature, checked against the published JWKS - no shared secrets ever leave ICE. A token can target several products at once; each server honors only the scopes prefixed with its own domain.

Family-internal platform identity

Host the identity layer your apps share.

ICE is the identity backbone the rest of the Splenta family is built on - not a product you buy to secure your own app. If you are evaluating the family, or want to walk the trust chain and the claim contract in detail, talk to us and we will show you the architecture.