Skip to main content

Keycloak Connector

The Humifortis Keycloak Connector brings adaptive authentication directly into your Keycloak instance. Every login is evaluated against a live risk score, and Keycloak enforces the decision — without changing your application code.


What It Does

The connector is a lightweight Java plugin (JAR) that runs inside Keycloak. It provides four SPI components you wire into your authentication flow:

ComponentPurpose
HumifortisDeviceCollectorCollects 40+ device signals silently via FingerprintJS after credentials are submitted, before the risk decision is made
HumifortisRiskAuthenticatorCalls POST /evaluate with the full login context; enforces the returned decision
HumifortisHighConditionCondition in the conditional MFA sub-flow — triggers MFA only when the engine returned CHALLENGE_MFA
HumifortisStepUpRouterRoutes the MFA challenge to the best available method (EMAIL_OTP, TOTP, or WEBAUTHN) based on the engine recommendation

The connector also ships a HumifortisEventListener that captures background events (login success, logout, password change, etc.) and forwards them asynchronously — separate from the inline RBA decision.

Authentication flow at login

User submits credentials


HumifortisDeviceCollector
├─ Generates nonce, serves device-collection page
├─ FingerprintJS collects 40+ signals (browser, screen, fonts, etc.)
├─ Computes binding: SHA-256(nonce:timestamp:visitorId)
├─ Stores signals in auth session notes


HumifortisRiskAuthenticator
├─ Calls POST /evaluate (IP, UA, device_id, MFA profile, roles, geo, device signals…)
├─ Receives: { "action": "ALLOW" | "CHALLENGE_MFA" | "BLOCK" }

├─ ALLOW → context.success() — user proceeds
├─ CHALLENGE_MFA → sets NOTE_MFA_ENFORCED in session, continues
└─ BLOCK → context.failure(ACCESS_DENIED) — login rejected

▼ (only when CHALLENGE_MFA)
HumifortisHighCondition (in conditional sub-flow)
└─ Reads NOTE_MFA_ENFORCED; if set → triggers HumifortisStepUpRouter


HumifortisStepUpRouter
├─ Sends EMAIL_OTP, TOTP, or WEBAUTHN challenge (based on engine recommendation)
├─ Validates response
└─ On success: fires auth_mfa_success event, grants access

Events Captured (Async Listener)

The HumifortisEventListener runs in the background and captures:

Keycloak EventCanonical SignalRisk Delta
LOGINAUTH_LOGIN_SUCCESS+2
LOGIN_ERRORAUTH_LOGIN_FAILED+8
LOGOUTAUTH_LOGOUT−5
MFA challenge passedAUTH_MFA_SUCCESS−25
MFA challenge failedAUTH_MFA_FAILED+12
UPDATE_PASSWORDAUTH_PASSWORD_CHANGE+3
UPDATE_EMAILAUTH_EMAIL_CHANGE+5
REMOVE_TOTPAUTH_TOTP_REMOVED+10
UPDATE_TOTPAUTH_TOTP_UPDATED+5
IMPERSONATEAUTH_IMPERSONATE+20
REGISTERAUTH_REGISTER0

All events are enriched server-side with IP geolocation, Tor/VPN/proxy detection, and velocity.


Quick Setup

1. Place the JAR

cp humifortis-keycloak-connector.jar /opt/keycloak/providers/

# Docker:
docker run ... \
-v /path/to/humifortis-keycloak-connector.jar:/opt/keycloak/providers/humifortis-keycloak-connector.jar:ro \
quay.io/keycloak/keycloak:26.1 start-dev

2. Configure environment variables in Keycloak

# Required
HUMIFORTIS_API_KEY=your-api-key-here

# Optional (these have sensible defaults)
HUMIFORTIS_API_URL=https://demo.humifortis.com # or your self-hosted URL
HUMIFORTIS_TIMEOUT_MS=5000 # default: 5000ms
HUMIFORTIS_FALLBACK_ALLOW=true # default: true (fail-open)
Enforcement mode is server-side

There is no HUMIFORTIS_MODE env var on the connector. The enforcement mode (enforce, dry_run, shadow) is configured in the Humifortis engine per-tenant, and returned as part of the POST /evaluate response. The connector always executes whatever mode the engine declares.

3. Configure the Keycloak authentication flow

  1. Keycloak Admin → your realm → Authentication
  2. Duplicate the default browser flow (never modify the built-in directly)
  3. In your copy, add these executions in order:
    • HumifortisDeviceCollector — set to REQUIRED, after the password form and before HumifortisRiskAuthenticator
    • HumifortisRiskAuthenticator — set to REQUIRED, after credentials
  4. Add a conditional sub-flow after HumifortisRiskAuthenticator:
    • Condition: HumifortisHighCondition — set to REQUIRED
    • Execution: HumifortisStepUpRouter — set to REQUIRED
  5. Bind this flow to the browser binding for your realm

4. Register the event listener

  1. Keycloak Admin → your realm → Events
  2. Under Event listeners, add humifortis

5. Restart Keycloak

docker compose restart keycloak
# or
/opt/keycloak/bin/kc.sh build && /opt/keycloak/bin/kc.sh start

Docker Compose Example

keycloak:
image: quay.io/keycloak/keycloak:26.1
command: start-dev --import-realm
environment:
KC_HTTP_PORT: "8088"
KC_HOSTNAME_STRICT: "false"
HUMIFORTIS_API_URL: "http://humifortis:8080/api/v1"
HUMIFORTIS_API_KEY: "${HUMIFORTIS_API_KEY}"
HUMIFORTIS_TIMEOUT_MS: "5000"
HUMIFORTIS_FALLBACK_ALLOW: "true"
volumes:
- ./humifortis-keycloak-connector.jar:/opt/keycloak/providers/humifortis-keycloak-connector.jar:ro
- ./realm-config.json:/opt/keycloak/data/import/realm.json:ro
depends_on:
- humifortis

Entity ID Format

The connector identifies users with:

user:keycloak:{realm_id}:{user_id}

For example: user:keycloak:myrealm:e4b96c3a-1234-5678-abcd-ef0123456789

This is the entity ID you use when querying GET /risk/:entity_id from outside Keycloak.


Fail-Open vs Fail-Closed

SettingHUMIFORTIS_FALLBACK_ALLOW=trueHUMIFORTIS_FALLBACK_ALLOW=false
Engine unreachableLogin proceeds (fail-open)Login blocked (fail-closed)
Timeout exceededLogin proceedsLogin blocked
Recommended forHigh-availability, public-facingRegulated, high-security environments

When a user passes MFA, the connector can set a first-party browser cookie (hf_device_trust) that marks this device as trusted. On a subsequent login from the same device, the engine can return ALLOW even at higher risk scores — with a configurable trust expiry window.


OTP Expiry Policy

Email OTP expiry is automatically scaled to risk level:

Risk LevelOTP Validity
CRITICAL60 seconds
HIGH120 seconds
MEDIUM / LOW180 seconds (default)

Troubleshooting

Login blocked unexpectedly: Check the Humifortis dashboard → Entities → find the user → review the risk timeline. The contributing signals explain the decision.

Connector can't reach engine: Verify HUMIFORTIS_API_URL is reachable from Keycloak's container/host. If HUMIFORTIS_FALLBACK_ALLOW=true (the default), logins should still proceed when the engine is unreachable.

No events appearing in the engine: Confirm the humifortis event listener is added to your realm under Events → Event listeners.

Users not getting MFA despite high risk: Verify the conditional sub-flow is set up with HumifortisHighCondition and HumifortisStepUpRouter. The sub-flow must follow HumifortisRiskAuthenticator in the flow.


See Also

  • Quick Start — send your first event via the HTTP API
  • API Reference — full request/response schema for /evaluate
  • Self-Hosted — run the Humifortis engine on your own infrastructure