Developer Docs

Corporate Identity โ€” SAML & SCIM

Enable enterprise-grade single sign-on (SSO) and automated user provisioning for Organizations on Mallnline. Malet Owners with corporate teams can connect their existing Identity Provider โ€” Okta, Azure AD, Google Workspace, or any SAML 2.0 / SCIM 2.0 compliant provider โ€” to automate member lifecycle management.


How It Works

Corporate Identity has two complementary protocols that work together:

Protocol Purpose Where
SAML 2.0 Single Sign-On โ€” Visitors click "Login with SSO" and authenticate via their corporate IdP Auth service
SCIM 2.0 Automated Provisioning โ€” IdP pushes user creates, updates, and deactivations to Mallnline SCIM service

End-to-End Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        Identity Provider (IdP)                        โ”‚
โ”‚                    (Okta / Azure AD / Google Workspace)                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚                                   โ”‚
           โ”‚ SAML Assertion                    โ”‚ SCIM REST API
           โ”‚ (interactive login)               โ”‚ (automated sync)
           โ–ผ                                   โ–ผ
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚  auth :3008  โ”‚                    โ”‚  scim :3019  โ”‚
    โ”‚              โ”‚                    โ”‚              โ”‚
    โ”‚  SAML ACS    โ”‚                    โ”‚  User CRUD   โ”‚
    โ”‚  Session     โ”‚                    โ”‚  Group CRUD  โ”‚
    โ”‚  Creation    โ”‚                    โ”‚  Discovery   โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚                                   โ”‚
           โ”‚ creates session                   โ”‚ creates/updates users
           โ”‚ + audit event                     โ”‚ + org memberships
           โ–ผ                                   โ–ผ
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚           Shared Platform Identity            โ”‚
    โ”‚     Postgres (users) + MongoDB (memberships)  โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

SAML 2.0 โ€” Single Sign-On

Configuration

Organization admins configure SAML via the Auth subgraph's GraphQL API:

mutation ConfigureSaml($orgId: ID!, $input: ConfigureSamlInput!) {
	configureSaml(orgId: $orgId, input: $input) {
		orgId
		entityId
		ssoUrl
		certificate # masked preview (first/last 20 chars)
		attributeMapping {
			email
			firstName
			lastName
		}
	}
}

Input:

{
	"orgId": "org-enterprise-01",
	"input": {
		"entityId": "https://idp.corp.example.com/saml/metadata",
		"ssoUrl": "https://idp.corp.example.com/saml/sso",
		"certificate": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----",
		"attributeMapping": {
			"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
			"firstName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
			"lastName": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
		}
	}
}

SAML Endpoints

Method Path Description
POST /auth/saml/:org_id/acs Assertion Consumer Service โ€” receives IdP SAML assertion, creates session, redirects to platform
GET /auth/saml/:org_id/metadata SP Metadata โ€” XML metadata for configuring the IdP

Login Flow

  1. Visitor clicks "Login with SSO" on the platform
  2. Platform redirects to the IdP's SSO URL with a SAML AuthnRequest
  3. Visitor authenticates with their corporate credentials (password, MFA, etc.)
  4. IdP posts a SAML assertion to /auth/saml/:org_id/acs
  5. Auth service:
    • Parses and validates the SAML assertion (Base64 โ†’ XML โ†’ NameID extraction)
    • Finds or creates the platform user by email
    • Creates or reuses a device record for the session
    • Issues session + refresh tokens (same dual-token strategy as MFA & Passkeys)
    • Logs a Saml audit event
  6. Visitor is redirected to the platform with active session cookies

Attribute Mapping

The SAML assertion contains user attributes. The attributeMapping configuration tells the auth service which XML attributes correspond to which platform fields:

Platform Field Default SAML Attribute Customizable
email NameID or emailaddress claim โœ…
firstName givenname claim โœ…
lastName surname claim โœ…

If no attribute mapping is configured, the auth service falls back to extracting the NameID element from the assertion as the user's email.

Managing SAML Configuration

# Update an existing config
mutation {
	updateSamlConfig(orgId: "org-01", input: { ssoUrl: "https://new-idp.com/sso" }) {
		orgId
		ssoUrl
	}
}

# Delete a config (disables SSO for the org)
mutation {
	deleteSamlConfig(orgId: "org-01")
}

# Test the config (validates certificate + connectivity)
mutation {
	testSamlConfig(orgId: "org-01") {
		success
		message
	}
}

# View config (certificate is masked for security)
query {
	samlConfig(orgId: "org-01") {
		entityId
		ssoUrl
		certificate
		attributeMapping {
			email
			firstName
			lastName
		}
	}
}

SCIM 2.0 โ€” Automated Provisioning

SCIM handles the automated lifecycle management of users and groups. When an IT admin adds, updates, or removes a user in their IdP, the change is automatically reflected in Mallnline.

For full REST API reference, see the SCIM Subgraph documentation.

Token Management

SCIM tokens are generated via the Auth GraphQL API and scoped to a single Organization:

# Generate a new SCIM token (raw value shown ONCE)
mutation {
	generateScimToken(orgId: "org-enterprise-01", label: "Okta Provisioning") {
		token # โ† copy this immediately โ€” never shown again
		id
		label
	}
}

# List active tokens (no raw values)
query {
	scimTokens(orgId: "org-enterprise-01") {
		id
		label
		lastUsedAt
		createdAt
	}
}

# Revoke a token (immediate)
mutation {
	revokeScimToken(orgId: "org-enterprise-01", tokenId: "token-id")
}

Provisioning Operations

IdP Action SCIM Operation Platform Effect
Assign user to app POST /scim/v2/Users Creates platform user + Organization membership
Update user profile PUT /scim/v2/Users/:id Updates email, display name
Deactivate user PATCH active=false Removes user from Organization (preserves account)
Remove user from app DELETE /scim/v2/Users/:id Same as deactivate
Create group POST /scim/v2/Groups Creates a Team in the Organization
Update group membership PATCH /scim/v2/Groups/:id Adds/removes Team members
Delete group DELETE /scim/v2/Groups/:id Deletes the Team

Membership Tracking

When users are provisioned via SCIM, their Organization membership is tagged with provisionedBy: "scim":

query {
	organization(id: "org-enterprise-01") {
		members {
			userId
			role
			provisionedBy # "scim" | "saml" | "manual"
		}
	}
}

This allows Organization admins to see which members were added manually vs. automatically by the IdP.


Organization Extensions

Corporate Identity adds several fields and capabilities to the Organizations subgraph:

New Fields

Entity Field Type Description
Team externalId String IdP group identifier (sparse unique index)
Membership provisionedBy String Source: "saml", "scim", or "manual"

New Permission

Permission Auto-Granted To Purpose
MANAGE_SSO OWNER, ADMIN Configure SAML, generate/revoke SCIM tokens

Audit Events

All corporate identity operations are logged via the Organization Audit Trail:

Event Type Trigger
SAML_CONFIG_CREATED SAML configuration added
SAML_CONFIG_UPDATED SAML configuration changed
SAML_CONFIG_DELETED SAML configuration removed
SAML_LOGIN Visitor authenticated via SAML SSO
SCIM_TOKEN_GENERATED New SCIM token created
SCIM_TOKEN_REVOKED SCIM token revoked
SCIM_USER_PROVISIONED User created via SCIM
SCIM_USER_UPDATED User updated via SCIM
SCIM_USER_DEPROVISIONED User deactivated via SCIM
SCIM_GROUP_SYNCED Group membership synced via SCIM

Security Model

SAML Security

  • Certificates: IdP X.509 certificates are stored as-is (they are public keys, not secrets)
  • Assertion validation: Base64 โ†’ XML parsing with element extraction and signature verification
  • Session creation: Uses the same dual-token strategy as all other auth methods
  • Audit trail: Every SAML login is logged with AuthMethod::Saml

SCIM Security

  • Zero-knowledge tokens: Raw tokens are never stored โ€” only SHA-256 hashes
  • Per-org isolation: Each token is scoped to exactly one Organization
  • Instant revocation: Revoked tokens are excluded immediately via partial index
  • Usage tracking: last_used_at updated on every authenticated request
  • No gateway: SCIM traffic does not pass through the Apollo gateway โ€” direct to the SCIM service

File Reference

File Purpose
apps/auth/src/models/saml.rs SAML config + SCIM token models
apps/auth/src/services/saml.rs SamlService + ScimTokenService
apps/auth/src/routes/saml.rs SAML ACS + SP metadata endpoints
apps/auth/src/graphql/mutation.rs SAML + SCIM token mutations
apps/auth/src/graphql/query.rs SAML config + SCIM token queries
apps/auth/migrations/20260408000000_saml_scim.sql saml_configs + scim_tokens tables
apps/scim/ Dedicated SCIM 2.0 REST service
apps/organizations/src/audit/org-audit-event.entity.ts Corporate identity audit events
apps/organizations/src/team/team.entity.ts externalId for SCIM group mapping
apps/organizations/src/membership/membership.entity.ts provisionedBy field

Testing

Suite Tests Service
SAML assertion parsing 6 Auth
SAML model serialization 4 Auth
SCIM token hashing 6 Auth
SCIM types + filters 11 SCIM
SCIM discovery 1 SCIM
Service provider config 1 SCIM
Total 29 โ€”
# Auth SAML tests
cd apps/auth && cargo test saml

# SCIM service tests
cd apps/scim && cargo test