DEV Community

Cover image for HPE Morpheus Enterprise & VM Essentials SAML Integration with Keycloak: A Complete Technical Guide
Emre Baykal
Emre Baykal

Posted on Edited on

HPE Morpheus Enterprise & VM Essentials SAML Integration with Keycloak: A Complete Technical Guide

1. Introduction

1.1 What is SAML 2.0?

SAML (Security Assertion Markup Language) 2.0 is an XML-based open standard for exchanging authentication and authorization data between two parties: an Identity Provider (IdP) that authenticates users, and a Service Provider (SP) that hosts the application. Instead of every application managing its own username/password database, SAML lets you delegate authentication to a central IdP. When a user logs in once at the IdP, they get access to all connected SPs without entering credentials again — this is Single Sign-On (SSO).

In practical terms: the user clicks "Login with SSO" on the application, gets redirected to the IdP login page, authenticates there, and is sent back to the application with a cryptographically signed XML document (the "SAML assertion") that proves who they are and what groups they belong to.

1.2 Why Keycloak?

There are several IdP options available (Okta, Azure AD, ADFS, Ping Identity, etc.), so why Keycloak?

  • Open-source and free — No per-user licensing costs, which matters at scale
  • Self-hosted — Full control over your identity infrastructure; no dependency on external SaaS providers
  • Protocol versatility — Supports SAML 2.0, OpenID Connect, and OAuth 2.0 in a single platform
  • LDAP/AD federation — Connects directly to Active Directory without migrating users
  • Kubernetes-native — Runs well as a containerized deployment with built-in clustering
  • CNCF project — Active community, regular releases, and long-term sustainability

1.3 What We Will Build

Enterprise environments demand centralized identity management. When you operate HPE Morpheus Enterprise as your cloud management platform, integrating it with a dedicated Identity Provider (IdP) via SAML 2.0 eliminates password sprawl and gives you single sign-on (SSO) across the entire infrastructure stack.

In this post, we walk through the entire journey: understanding how Morpheus handles SAML under the hood, deploying Keycloak on Kubernetes as your IdP, wiring the two together, and diagnosing issues when things do not go as planned. Every configuration value and YAML snippet comes from a real lab deployment, so you can replicate it in your own environment.

Lab environment: Kubernetes (single-node / Minikube compatible), Keycloak 26.x, Morpheus Enterprise 8.x, Active Directory for user federation.


2. Morpheus SAML Architecture

HPE Morpheus Enterprise supports SAML 2.0 as an Identity Source type within its Administration panel. Understanding how Morpheus participates in the SAML exchange is essential before configuring anything on the Keycloak side.

📸 IMAGE: Morpheus > Login Screen

📸 IMAGE: Morpheus > Forward Keycloak Login Screen

2.1 Morpheus as a SAML Service Provider (SP)

Morpheus acts exclusively as a SAML Service Provider. It does not function as an Identity Provider itself. When a user attempts to log in via SSO, Morpheus generates a SAML AuthnRequest, redirects the browser to the configured IdP, and then consumes the SAML Response (assertion) returned by the IdP.

2.2 Key SAML Endpoints in Morpheus

When you create a SAML Identity Source in Morpheus, the platform automatically generates two critical values:

Endpoint Description
SP Entity ID A unique identifier for Morpheus as a Service Provider. Auto-generated from the hostname, e.g. https://morpheus-url/saml/<uniqueID>
SP ACS URL The callback URL where the IdP posts the SAML Response after authentication, e.g. https://morpheus-url/externalLogin/callback/<uniqueID>
Login Redirect URL The IdP's SAML SSO endpoint where Morpheus sends the AuthnRequest
SAML Logout Redirect URL The IdP's SAML SLO endpoint for single logout

Note: The SP Entity ID and ACS URL are generated only after you save the Identity Source for the first time. You must save first, then copy these values to configure the IdP.

2.3 SAML Request and Response Configuration

Morpheus provides granular control over how SAML requests are signed and responses are validated:

Setting Options & Description
SAML Request No Signature / Self Signed / Custom RSA Signature — Controls whether AuthnRequest messages are signed
SAML Response Do Not Validate / Validate Assertion Signature — Controls signature validation on the IdP's assertion
POST Binding Mode ON/OFF — Uses HTTP-POST binding instead of HTTP-Redirect
Includes SAML Request Parameter Yes/No — Whether the SAML request is included in the redirect

2.4 Assertion Attribute Mappings

Morpheus maps SAML assertion attributes to internal user fields:

Morpheus Field Expected SAML Attribute
Given Name firstName
Surname lastName
Email email (or NameID)

2.5 Role Mapping Mechanism

Morpheus supports role-based access control through SAML group assertions:

Role Mapping Field Description
Default Role The role assigned to all authenticated users (e.g., Standard User)
Role Attribute Name The SAML attribute containing group/role info (e.g., groups)
Required Role Attribute Value A group name the user must belong to for authorization (e.g., mspusers)

📸 IMAGE: Morpheus > Identity Sources > Keycloak

Morpheus SAML SSO Configuration


3. Keycloak-SAML Ecosystem: How It Works

Keycloak is an open-source Identity and Access Management (IAM) solution maintained by the CNCF. It supports OpenID Connect, OAuth 2.0, and SAML 2.0 protocols natively. In our setup, Keycloak serves as the SAML Identity Provider (IdP) that authenticates users against an Active Directory backend via LDAP federation.

3.1 Core Keycloak Concepts

Concept Description
Realm A tenant-level isolation boundary. Each realm has its own users, clients, roles, and identity providers. Our realm: morpheus-lab
Client An application that delegates authentication to Keycloak. Morpheus is registered as a SAML client
User Federation Allows Keycloak to pull users from LDAP/Active Directory without duplicating credentials
Protocol Mappers Transform user attributes and group memberships into SAML assertions
Roles & Groups Realm-level and client-level roles. AD groups can be synced and mapped into assertions

3.2 SAML 2.0 Authentication Flow (SP-Initiated SSO)

The diagram below illustrates the SP-Initiated SAML SSO flow between Morpheus and Keycloak:

SAML 2.0 SP-Initiated SSO Flow Diagram

Step-by-step:

  1. The user navigates to the Morpheus login page and clicks the SSO login button.
  2. Morpheus generates a SAML AuthnRequest and redirects the user's browser to Keycloak's SAML endpoint: https://keycloak-server:30443/realms/morpheus-lab/protocol/saml
  3. Keycloak presents the login form. The user enters their AD credentials.
  4. Keycloak authenticates the user against the federated LDAP/AD backend.
  5. On successful authentication, Keycloak constructs a SAML Response containing signed assertions with user attributes (firstName, lastName) and group memberships (groups).
  6. Keycloak POSTs the SAML Response to the Morpheus ACS URL.
  7. Morpheus validates the assertion signature, maps attributes and roles, creates or updates the user session, and grants access.

Step 5 is where group resolution happens, and it is the step most likely to fail on a real Active Directory. Section 5.2 covers how to scope the group mapper so the assertion can actually be built.

3.3 Authentication vs Authorization

Authentication (Who are you?)

  • Keycloak acts as the authentication broker, sitting between the application (Morpheus) and the identity store (Active Directory).
  • User Federation (LDAP) allows Keycloak to verify credentials against AD without storing passwords locally. Keycloak performs LDAP BIND operations to authenticate users.
  • MFA can be layered on top through Keycloak's authentication flow configuration. Step 10 in section 5 covers this.
  • Session Management: Once authenticated, Keycloak creates a session. Subsequent SAML requests within the session's lifetime do not require re-authentication (SSO behavior).

Keycloak authentication flow diagram

Authorization (What can you do?)

  • Keycloak syncs AD groups via the LDAP Group Mapper (mspusers, apparchitech, selfservice).
  • The SAML Group List Mapper serializes group memberships into the SAML assertion as a groups attribute.
  • Morpheus reads the groups attribute and maps it to internal roles:
    • mspusers → authorized user (Required Role)
    • apparchitech → Application Architect role
    • selfservice → Self-Service User role
  • Users not in the required group are denied access even if authentication succeeds.

3.4 Single Logout (SLO) Flow

User clicks Logout → Morpheus sends LogoutRequest → Keycloak terminates session
→ Keycloak POSTs LogoutResponse to /login/auth → User lands on login page
Enter fullscreen mode Exit fullscreen mode

Warning: The Logout Service POST Binding URL must be set to /login/auth (the login page), NOT the ACS callback URL. Morpheus's ACS handler cannot process LogoutResponse objects and throws a GroovyCastException.

3.5 Session Lifetime: Two Clocks, Not One

There are two independent sessions in this architecture, and forgetting the second one produces surprising behaviour. Morpheus keeps its own browser session with its own inactivity timer, while Keycloak keeps an SSO session backed by a browser cookie. Closing the browser ends the Morpheus session but not necessarily the Keycloak one, so clicking the SSO button again can put the user straight back in without a password prompt.

Keycloak's SSO Session Idle must therefore be equal to or shorter than the Morpheus session timeout. Section 5.11 gives the values we run.


4. Deploying Keycloak on Kubernetes

This section walks through deploying a production-grade Keycloak cluster on Kubernetes using a single YAML manifest.

The complete Kubernetes manifest used in this guide is available on GitHub:
keycloak.yaml on GitHub

4.1 Architecture Overview

The deployment stack:

  • Keycloak StatefulSet (2 replicas) with Infinispan clustering for HA
  • PostgreSQL Deployment with PersistentVolumeClaim (10Gi)
  • Kubernetes Secret for admin and database passwords
  • Self-signed TLS certificates generated by init containers
  • NodePort Service for external HTTPS access on port 30443
  • Headless Service for Infinispan/JGroups cluster discovery

4.2 Prerequisites

Infrastructure:

  • A running Kubernetes cluster or Minikube instance
  • kubectl configured and connected to your cluster
  • At least 4GB RAM and 2 CPU cores available for the Keycloak + PostgreSQL pods
  • A storage provisioner (default StorageClass or Rook-Ceph). For Minikube, enable it with:
minikube addons enable default-storageclass
minikube addons enable storage-provisioner
Enter fullscreen mode Exit fullscreen mode

Minikube users: Start Minikube with sufficient resources:
minikube start --cpus=4 --memory=8192 --driver=docker

Check what your cluster actually offers before applying anything:

kubectl get sc
Enter fullscreen mode Exit fullscreen mode

If no StorageClass is marked (default), the PVC in this manifest must name one explicitly. Step 2 shows where.

Network & DNS requirements:

  • The Kubernetes node IP must be reachable from the Morpheus server (for SAML redirects)
  • The Morpheus server hostname (e.g., morpheus-server) must be resolvable from both the user's browser and the Keycloak pods. If you're using a local domain, add entries to /etc/hosts on the machines or configure your internal DNS
  • Firewall rules: Ensure these ports are open between the components:
Source Destination Port Protocol Purpose
User Browser Morpheus Server 443 HTTPS Access Morpheus UI
User Browser K8s Node 30443 HTTPS Keycloak login page (SAML redirect)
Morpheus Server K8s Node 30443 HTTPS SAML backchannel (POST binding)
K8s Pod Network AD Domain Controller 389 LDAP User federation / authentication

Active Directory requirements:

  • A dedicated service account for Keycloak LDAP binding (e.g., svc-keycloak). This account needs read-only access to the Users container (CN=Users,DC=yourdomain,DC=local). It does not need Domain Admin privileges — basic "Read all user information" permission is sufficient
  • AD groups that will map to Morpheus roles (e.g., mspusers, apparchitech, selfservice) must exist and users must be members of the appropriate groups
  • Ideally, put those three groups in a dedicated OU rather than leaving them in CN=Users alongside the built-in groups. Section 5.2 explains why this matters for group resolution

4.3 Step 1: Prepare Secrets

The YAML uses a Kubernetes Secret to store sensitive credentials. The passwords are Base64-encoded:

# Encode your passwords
echo -n 'YourAdminPassword!' | base64
# Output: WW91*****UGFzc3***cmQh

echo -n 'YourDBPassword!' | base64
# Output: WW9************mQh
Enter fullscreen mode Exit fullscreen mode

The Secret resource in the YAML:

apiVersion: v1
kind: Secret
metadata:
  name: keycloak-secret
  namespace: keycloak
type: Opaque
data:
  admin-password: <base64-encoded-admin-password>
  db-password: <base64-encoded-db-password>
Enter fullscreen mode Exit fullscreen mode

Note: Never commit plain-text passwords to version control. Base64 is encoding, not encryption — anyone with the file has the password. Use a secrets manager (Vault, Sealed Secrets) in production.

4.4 Step 2: Namespace and Storage

apiVersion: v1
kind: Namespace
metadata:
  name: keycloak
  labels:
    app.kubernetes.io/part-of: sso-stack
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: keycloak
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: rook-ceph-block
  resources:
    requests:
      storage: 10Gi
Enter fullscreen mode Exit fullscreen mode

Two things in that snippet are easy to get wrong and both fail quietly.

Every object needs its own --- separator. Without it, two objects merge into a single YAML document with duplicate apiVersion and kind keys, and one of them silently disappears (or the parser rejects the file outright, depending on your kubectl version).

storageClassName should name the class from kubectl get sc. Leave it out only if your cluster has a class marked (default). When neither is true the PVC sits in Pending forever, PostgreSQL never schedules, and the Keycloak pod stays in Init:0/1 waiting on a database that will never come up. The value above is what we run on Rook-Ceph; substitute yours.

Note that storageClassName is immutable. If you need to change it after the fact, delete and recreate the PVC:

kubectl -n keycloak scale deploy/postgres --replicas=0
kubectl -n keycloak delete pvc postgres-pvc
kubectl apply -f keycloak.yaml
Enter fullscreen mode Exit fullscreen mode

4.5 Step 3: PostgreSQL Database

Keycloak requires an external database in production mode. Key points from our manifest:

containers:
  - name: postgres
    image: postgres:17
    env:
      - name: POSTGRES_USER
        value: keycloak
      - name: POSTGRES_PASSWORD
        valueFrom:
          secretKeyRef:
            name: keycloak-secret
            key: db-password
      - name: POSTGRES_DB
        value: keycloak
      - name: PGDATA
        value: /var/lib/postgresql/data/pgdata
    readinessProbe:
      exec:
        command: ["pg_isready", "-U", "keycloak"]
      initialDelaySeconds: 10
      periodSeconds: 5
    resources:
      requests:
        memory: 256Mi
        cpu: 100m
      limits:
        memory: 512Mi
        cpu: 500m
Enter fullscreen mode Exit fullscreen mode

This database is not just configuration storage. Once you enable MFA (Step 10), it also holds every user's OTP secret, which means losing this volume forces every user to re-enrol their authenticator. Back up the PVC accordingly.

4.6 Step 4: Keycloak StatefulSet

The Keycloak deployment uses a StatefulSet with 2 replicas for high availability.

Init Containers:

  1. wait-for-postgres: Polls PostgreSQL port 5432 before Keycloak starts
  2. generate-tls-cert: Generates a self-signed TLS certificate (10 years validity)
initContainers:
  - name: generate-tls-cert
    image: alpine:3.19
    command: ["sh", "-c"]
    args:
      - |
        apk add --no-cache openssl
        openssl req -x509 -nodes -days 3650 \
          -newkey rsa:2048 \
          -keyout /certs/tls.key \
          -out /certs/tls.crt \
          -subj "/CN=keycloak-morpheus-lab/O=Lab/C=TR"
Enter fullscreen mode Exit fullscreen mode

That init container pulls the openssl package at runtime, so the pod network needs outbound access to the Alpine repositories. In an air-gapped cluster, bake the certificate into a Secret instead.

Key Environment Variables:

Variable Purpose
KC_BOOTSTRAP_ADMIN_USERNAME Initial admin username (admin)
KC_BOOTSTRAP_ADMIN_PASSWORD Admin password from Secret
KC_DB / KC_DB_URL_HOST Database type and host (postgres)
KC_HTTPS_CERTIFICATE_FILE Path to TLS certificate
KC_HTTPS_CERTIFICATE_KEY_FILE Path to TLS key
KC_CACHE Clustering mode (ispn = Infinispan)
KC_HOSTNAME_STRICT Disabled for NodePort/self-signed setups
KC_FEATURES Additional features (token-exchange)
KC_HEALTH_ENABLED Enables /health/* endpoints for probes

The KC_BOOTSTRAP_ADMIN_* variables apply only on first boot against an empty database. After the admin user exists, editing them and re-applying the manifest changes nothing; rotate the password from the admin console or kcadm.sh instead.

Health Probes:

  • startupProbe: /health/started — 1s interval, 600 retries (10-minute startup window)
  • readinessProbe: /health/ready — Every 10s, 3 failures to mark unready
  • livenessProbe: /health/live — Every 10s, 3 failures to restart pod

4.7 Step 5: Services

Service Type & Purpose
keycloak (ClusterIP) Internal access: ports 8080 (HTTP) and 8443 (HTTPS)
keycloak-discovery (Headless) JGroups cluster member discovery on port 7800
keycloak-nodeport (NodePort) External access: port 30080 (HTTP) and 30443 (HTTPS)

4.8 Step 6: Deploy

# Apply the complete manifest
kubectl apply -f keycloak.yaml

# Watch the rollout
kubectl rollout status statefulset/keycloak -n keycloak --timeout=10m

# Verify all pods are running
kubectl get pods -n keycloak

# Access the admin console
# https://<node-ip>:30443/admin
Enter fullscreen mode Exit fullscreen mode

Stop here and confirm the PVC bound before moving on. If it did not, everything downstream will look like a Keycloak problem when it is a storage one:

kubectl -n keycloak get pvc postgres-pvc
Enter fullscreen mode Exit fullscreen mode

Also confirm the two replicas formed a single Infinispan cluster, since split members break login flows in ways that are hard to attribute later:

kubectl -n keycloak logs -l app=keycloak --prefix | grep "Received new cluster view"
Enter fullscreen mode Exit fullscreen mode

Tip: For Minikube, use minikube ip to get the node IP. For a multi-node cluster, any node's IP will work with NodePort.

📸 IMAGE: Terminal output of kubectl get pods -n keycloak showing all pods running

Terminal output of kubectl get pods -n keycloak showing all pods running


5. Keycloak-Morpheus SAML Integration Guide

With Keycloak deployed and running, we can now configure the SAML integration. The configuration involves both the Keycloak side (IdP) and the Morpheus side (SP), and the order matters.

Important — Configuration Order:
There is a chicken-and-egg situation here. Keycloak needs the SP Entity ID and ACS URL to create the SAML client, but these values are auto-generated by Morpheus only after you save an Identity Source. The correct order is:

  1. Create the Keycloak Realm and LDAP Federation first (Steps 1-2)
  2. Create a preliminary Identity Source in Morpheus to obtain the SP Entity ID and ACS URL (Step 3)
  3. Use those values to create the SAML Client in Keycloak (Steps 4-7)
  4. Come back to Morpheus and complete the Identity Source configuration (Step 8)

5.1 Step 1: Create the Keycloak Realm

  1. Log in to Keycloak Admin Console at https://keycloak-server:30443/admin
  2. Click the realm dropdown (top-left) and select "Create Realm"
  3. Set Realm Name to: morpheus-lab
  4. Click Create

📸 IMAGE: Keycloak Admin Console > Manage Realm > Create Realm

Keycloak Admin Console > Create Realm dialog

5.2 Step 2: Configure LDAP User Federation

Navigate to morpheus-lab > User Federation > Add New provider > LDAP and configure:

Setting Value
Name LAB AD
Vendor Active Directory
Connection URL ldap://active-directory:389
Bind Type simple
Bind DN CN=svc-keycloak,CN=Users,DC=domain,DC=domain
Users DN CN=Users,DC=domain,DC=domain
Username LDAP Attribute sAMAccountName
Edit Mode READ_ONLY
Import Users ON

Add Group Mapper (Mappers > Add mapper):

Setting Value
Mapper Type group-ldap-mapper
Groups DN CN=Users,DC=domain,DC=domain
Group Name LDAP Attribute cn
Membership LDAP Attribute member
Mode READ_ONLY
Preserve Group Inheritance OFF
User Roles Retrieve Strategy LOAD_GROUPS_BY_MEMBER_ATTRIBUTE

Preserve Group Inheritance deserves a paragraph, because leaving it on is the single most likely reason this integration fails after everything else is correct.

With inheritance enabled, Keycloak resolves the full parent/child tree for every group under Groups DN, and every DN listed as a member has to resolve to a group. CN=Users on a default Active Directory holds users and built-in groups side by side, and several of those built-in groups list user objects as members. Administrator inside Schema Admins is the usual one. The tree cannot be resolved, the whole group sync aborts, and the failure surfaces at SAML assertion time rather than at sync time.

Turning inheritance off flattens the hierarchy, which is all Morpheus needs: it reads groups as a flat list, and the SAML Group List mapper in Step 6 is configured with Full Group Path off anyway. If you do want the hierarchy, scope Groups DN to a dedicated OU that contains only your role groups, or add an LDAP Filter to the mapper:

(|(cn=mspusers)(cn=apparchitech)(cn=selfservice))
Enter fullscreen mode Exit fullscreen mode

Note: After saving, click 'Sync all users' and 'Sync LDAP groups to Keycloak' to import users and groups from Active Directory.

📸 IMAGE: Keycloak > User Federation > LAB AD settings

LDAP 1

LDAP 2

📸 IMAGE: Keycloak > User Federation > LAB AD > Mappers > group mapper, showing Preserve Group Inheritance set to OFF

5.3 Step 3: Create Preliminary Identity Source in Morpheus (Get SP Entity ID)

Before creating the SAML client in Keycloak, we need to obtain the SP Entity ID and ACS URL from Morpheus. These values are auto-generated and unique to your Morpheus instance.

  1. Log in to Morpheus at https://morpheus-server
  2. Navigate to Administration > Identity Sources
  3. Click + Add Identity Source
  4. Set Type to SAML SSO
  5. Set Name to Keycloak-SSO
  6. For now, enter any placeholder URL in Login Redirect URL (e.g., https://placeholder.local) — we will update this later
  7. Click Save Changes

After saving, Morpheus generates and displays two critical values in the Identity Source list:

  • SP Entity ID — e.g., https://morpheus-server/saml/N3h***B***O
  • SP ACS URL — e.g., https://morpheus-server/externalLogin/callback/N***3***O

Copy both of these values! You will need them in the next step to configure the SAML client in Keycloak. The N3****BO part is a unique identifier generated by your Morpheus instance — yours will be different.

📸 IMAGE: Morpheus > Identity Sources list showing the auto-generated SP Entity ID and ACS URL

5.4 Step 4: Create the SAML Client in Keycloak

Now go back to the Keycloak Admin Console and create the SAML client using the values from the previous step:

  1. Navigate to morpheus-lab > Clients > Create Client
  2. Set Client Type to SAML
  3. Set Client ID to the SP Entity ID you copied from Morpheus: https://morpheus-server/saml/N3h**K**5**
  4. Set Name to: Morpheus Enterprise

Access Settings:

Setting Value
Root URL https://morpheus-server
Valid Redirect URIs (ACS URL) https://morpheus-server/externalLogin/callback/N3h**K**5**
Master SAML Processing URL https://morpheus-server/externalLogin/callback/N3h**K**5**
IDP-Initiated SSO URL Name morpheus

SAML Capabilities:

Setting Value
Name ID Format username
Force POST Binding ON
Include AuthnStatement ON

Signature & Encryption:

Setting Value
Sign Documents ON
Sign Assertions ON
Signature Algorithm RSA_SHA256
Client Signature Required OFF (CRITICAL!)
Encrypt Assertions OFF

Warning: Client Signature Required must be OFF. Morpheus signs requests with a self-signed certificate that does not match the certificate registered in Keycloak. If this is ON, every SAML request from Morpheus will be rejected.

Keep Encrypt Assertions off as well. Turning it on makes Keycloak look for a client encryption certificate that Morpheus does not supply, and assertion building fails.

📸 IMAGE: Keycloak > Clients > Morpheus Enterprise > Settings

5.5 Step 5: Configure Logout (Advanced Settings)

Navigate to Advanced tab > Fine Grain SAML Endpoint Configuration:

Setting Value
Logout Service POST Binding URL https://morpheus-server/login/auth
Logout Service Redirect Binding URL https://morpheus-server/login/auth

This is crucial! Setting these URLs to /login/auth prevents the GroovyCastException bug. The Morpheus ACS callback handler cannot process SAML LogoutResponse objects.

Fill in both bindings. When these fields are empty, Keycloak falls back to the Master SAML Processing URL, which is the ACS callback, and the LogoutResponse lands on a handler that only understands AuthnResponse. Which binding gets used depends on how Morpheus sends the LogoutRequest, so setting only the POST field leaves the other path broken.

📸 IMAGE: Keycloak > Clients > Morpheus Enterprise > Advanced > Fine Grain SAML Endpoint Configuration, with both logout binding URLs filled in

Fine Grain SAML Endpoint Configuration

5.6 Step 6: Configure SAML Mappers

Navigate to Client Scopes > dedicated > Mappers and add:

Mapper 1: Groups (Group List)

Setting Value
Mapper Type Group list
SAML Attribute Name groups
SAML Attribute NameFormat Basic
Single Group Attribute OFF
Full Group Path OFF

Mapper 2: firstName (User Attribute)

Setting Value
Mapper Type User Attribute
User Attribute firstName
SAML Attribute Name firstName

Mapper 3: lastName (User Attribute)

Setting Value
Mapper Type User Attribute
User Attribute lastName
SAML Attribute Name lastName

📸 IMAGE: Keycloak > Client Scopes > dedicated > Mappers list

5.7 Step 7: Copy the Realm Certificate

  1. Navigate to morpheus-lab > Realm Settings > Keys
  2. Find the RS256 key row and click the Certificate button
  3. Copy the entire certificate string

📸 IMAGE: Keycloak > Realm Settings > Keys > RS256 certificate

5.8 Step 8: Complete the Morpheus Identity Source Configuration

Now go back to the preliminary Identity Source you created in Step 3 and update it with the real values. Navigate to Administration > Identity Sources > Keycloak-SSO > Edit:

Setting Value
Login Redirect URL https://keycloak-server:30443/realms/morpheus-lab/protocol/saml
SAML Logout Redirect URL https://keycloak-server:30443/realms/morpheus-lab/protocol/saml
Includes SAML Request Parameter Yes
POST Binding Mode ON
SAML Request Self Signed
SAML Response Validate Assertion Signature
SAML Response Public Key (paste RS256 certificate from Keycloak)

Assertion Attribute Mappings:

Morpheus Field Value
Given Name Attribute Name firstName
Surname Attribute Name lastName

Role Mappings:

Morpheus Field Value
Default Role Standard User
Role Attribute Name groups
Required Role Attribute Value mspusers
Application Architect Role apparchitech
Self Service User Role selfservice

Click Save Changes.

📸 IMAGE: Morpheus > Identity Sources > Keycloak-SSO configuration

5.9 Step 9: Test the Integration

  1. Open a new browser / incognito window
  2. Navigate to https://morpheus-server
  3. Click the SSO login option (Keycloak-SSO should appear)
  4. You will be redirected to Keycloak's login page
  5. Enter an AD username (e.g., firstname.lastname) and password
  6. On success, you will be redirected back to Morpheus and logged in

Test logout too, not just login. A broken logout binding does not show up anywhere in the login path.

5.10 Step 10: Enable MFA (Optional)

MFA is entirely an IdP concern here. Morpheus receives a signed assertion and has no interest in how many factors produced it, so nothing on the Morpheus side changes. Keycloak's built-in option is OTP (TOTP/HOTP), which works with Google Authenticator, Microsoft Authenticator or FreeOTP. WebAuthn is also available, though it is awkward on a NodePort endpoint with a self-signed certificate, so start with OTP.

The obvious route — setting Configure OTP as a default required action under Authentication > Required Actions — does not work with LDAP federation. Default actions attach to users created through Keycloak, and federated users do not go through that path, so nobody is ever prompted to enrol. The default browser flow compounds this: its Browser - Conditional OTP subflow is gated on Condition - user configured, meaning OTP is requested only from users who already have it. Nobody has it, so nobody is asked.

Bind a flow that requires it instead:

  1. Go to morpheus-lab > Authentication > Flows > browser and click Duplicate. Name it browser-mfa.
  2. In the copy, change the Browser - Conditional OTP subflow requirement from Conditional to Required.
  3. Delete the Condition - user configured execution inside that subflow.
  4. Confirm OTP Form is set to Required.
  5. Back on the flow list, open Action > Bind flow on browser-mfa and bind it as Browser flow.

📸 IMAGE: Keycloak > Authentication > Flows > browser-mfa, showing the Conditional OTP subflow set to Required and bound as the Browser flow

Users without an enrolled authenticator now get the QR enrolment screen on their next login, including users who have never logged in before. Because the SAML flow uses the bound browser flow, test through the Morpheus SSO button rather than the Keycloak console directly.

📸 IMAGE: Keycloak OTP enrolment screen with the QR code, as the user sees it after entering AD credentials

One consequence worth planning for: OTP secrets live in the Keycloak database, not in Active Directory. Losing the PostgreSQL volume means every user re-enrols.

5.11 Step 11: Align Session Timeouts

Morpheus has its own inactivity timer under Administration > Settings > Appliance, with Session Expires and Session Warning in minutes. Ours is set to 20 and 15.

📸 IMAGE: Morpheus > Administration > Settings > Appliance, showing Session Expires and Session Warning

![Morpheus session settings]

Keycloak's SSO session is independent, and if it outlives the Morpheus one, a user whose Morpheus session expired gets logged straight back in without a password prompt when they click the SSO button.

Set Keycloak's idle timeout slightly below the Morpheus value under morpheus-lab > Realm Settings > Sessions:

Setting Value
SSO Session Idle 18 minutes
SSO Session Max 8 hours
SSO Session Idle Remember Me 0
SSO Session Max Remember Me 0

📸 IMAGE: Keycloak > Realm Settings > Sessions, showing SSO Session Idle and SSO Session Max

Then turn Remember me off under morpheus-lab > Realm Settings > Login. While it is on, the Remember Me lifespans replace the values above and the idle timeout you just set does nothing.

The same change from the CLI, with lifespans in seconds:

kubectl -n keycloak exec -it keycloak-0 -- /opt/keycloak/bin/kcadm.sh config credentials \
  --server http://localhost:8080 --realm master --user admin

kubectl -n keycloak exec -it keycloak-0 -- /opt/keycloak/bin/kcadm.sh update realms/morpheus-lab \
  -s ssoSessionIdleTimeout=1080 \
  -s ssoSessionMaxLifespan=28800 \
  -s rememberMe=false
Enter fullscreen mode Exit fullscreen mode

Existing sessions keep their original lifespans, so clear them before testing: morpheus-lab > Sessions > Action > Sign out all active sessions. Test in a private window, since browsers that restore tabs on startup also restore session cookies and will make the old behaviour look like it never changed.

SSO Session Max is an absolute ceiling that forces re-authentication even during active use. Eight hours covers a working day; shortening it mostly annoys people mid-afternoon.


6. Troubleshooting SAML Integration

SAML integrations can fail silently or produce cryptic errors. This section covers the most common issues.

6.1 Diagnostic Tools

Tool Usage
SAML Tracer (Browser Extension) Captures SAML requests/responses in real-time. Available for Firefox and Chrome
Keycloak Events Enable in Realm Settings > Events. Shows auth attempts and errors
Keycloak Logs kubectl logs -l app=keycloak -n keycloak --prefix -f
Morpheus Logs /var/log/morpheus/morpheus-ui/current
Base64 Decoder `echo '' \

With two replicas, always read logs with the label selector and {% raw %}--prefix. Targeting a single pod by name will hide half the failures, since the browser can land on either replica.

6.2 Common Issues and Solutions

Issue 1: 'Invalid Requester' Error

Symptom Keycloak returns 'Invalid requester' or 'Client not found'
Cause SP Entity ID in Morpheus doesn't match Client ID in Keycloak
Solution Copy the exact SP Entity ID from Morpheus Identity Source and use it as Client ID in Keycloak

Issue 2: Signature Validation Failure

Symptom 'Signature validation failed' or 'Invalid signature'
Cause SAML Response Public Key in Morpheus doesn't match Keycloak's RS256 certificate
Solution Copy fresh certificate from Keycloak > Realm Settings > Keys > RS256 > Certificate. Must be updated after every Keycloak redeployment

Issue 3: User Authenticated but Access Denied

Symptom User authenticates at Keycloak but gets 'Access Denied' in Morpheus
Cause User not in the required group, or Group List mapper missing
Solution 1) Verify user is in mspusers AD group. 2) Check Group List mapper in client scopes. 3) Use SAML Tracer to verify groups attribute in assertion

Issue 4: GroovyCastException on Logout

Symptom Login fails after logout with GroovyCastException: Cannot cast object ... LogoutResponseImpl ... to class ... Response
Cause LogoutResponse sent to ACS URL instead of login page, because the logout binding fields are empty and Keycloak falls back to Master SAML Processing URL
Solution Set both Logout Service POST Binding URL and Logout Service Redirect Binding URL to https://morpheus-server/login/auth in Keycloak Advanced settings

Issue 5: Login Loop / Redirect Loop

Symptom Browser keeps redirecting between Morpheus and Keycloak
Cause Mixed HTTP/HTTPS, clock skew, or invalid ACS URL
Solution 1) Ensure both use HTTPS. 2) Verify NTP sync (SAML assertions are time-sensitive). 3) Check Valid Redirect URIs matches exactly

Issue 6: Attributes Not Mapped

Symptom firstName/lastName are empty, roles not assigned
Cause SAML mappers missing or attribute names don't match
Solution 1) Add User Attribute mappers for firstName and lastName. 2) Names are case-sensitive. 3) Use SAML Tracer to verify attributes in assertion XML

Issue 7: LDAP Users Not Appearing

Symptom No users appear after LDAP federation setup
Cause Wrong Bind DN, Users DN, or network connectivity
Solution 1) Test connectivity from pod. 2) Verify Bind DN has read access. 3) Click 'Sync all users'. 4) Check Keycloak logs for LDAP errors

Issue 8: 'Failed to process response' After Successful Login

Symptom Credentials are accepted, then Keycloak's own error page appears with 'We are sorry... Failed to process response'. The browser never returns to Morpheus
Cause Assertion building failed. The log shows ModelException: Couldn't resolve groups from LDAP with a GroupTreeResolveException naming a user object referenced as a member of a built-in AD group
Solution Set Preserve Group Inheritance to OFF on the LDAP group mapper and re-run 'Sync LDAP groups to Keycloak'. Alternatively scope Groups DN to an OU holding only your role groups. Restart the pods afterwards to clear the cached user: kubectl -n keycloak rollout restart sts/keycloak

📸 IMAGE: Keycloak error page reading 'We are sorry... Failed to process response'

Read the stack trace before assuming this one. The same error page appears for any failure during assertion building, including an enabled Encrypt Assertions with no client certificate, so the exception in the log is what tells them apart.

Issue 9: Keycloak Pod Stuck in Init

Symptom keycloak-0 sits at Init:0/1, the init container logs repeat 'Waiting for PostgreSQL', and the postgres pod is Pending
Cause The PVC is unbound: kubectl -n keycloak describe pvc postgres-pvc reports 'no persistent volumes available for this claim and no storage class is set'
Solution Set storageClassName in the PVC to a class from kubectl get sc, or mark one class as the cluster default. The field is immutable, so delete and recreate the PVC

Issue 10: MFA Enabled but No QR Code

Symptom Configure OTP is set as a default required action, but users log in without ever being prompted to enrol
Cause Default required actions do not attach to LDAP-federated users, and the stock browser flow only asks for OTP from users who already configured it
Solution Duplicate the browser flow, set the Conditional OTP subflow to Required, remove the Condition - user configured execution, and bind the new flow as the Browser flow (Step 10)

6.3 SAML Assertion Debugging Checklist

When debugging, use SAML Tracer to capture the assertion and verify:

  • NameID: Present and in expected format (username)?
  • Issuer: Matches the Keycloak realm URL?
  • AudienceRestriction: Audience matches Morpheus SP Entity ID?
  • Conditions/NotBefore/NotOnOrAfter: Valid timestamps? Check for clock skew
  • AuthnStatement: Present? (Required by Morpheus)
  • AttributeStatement: firstName, lastName, groups attributes present with correct values?
  • Signature: Assertion signed? Certificate matches?

6.4 Useful Debug Commands

# Check Keycloak pod logs across both replicas
kubectl logs -l app=keycloak -n keycloak --prefix -f

# Find the exception behind a Keycloak error page
kubectl logs -l app=keycloak -n keycloak --prefix --tail=400 | grep -B5 -A40 "ERROR"

# Verify storage bound before blaming Keycloak
kubectl -n keycloak get pvc postgres-pvc

# Confirm the two replicas formed one Infinispan cluster
kubectl -n keycloak logs -l app=keycloak --prefix | grep "Received new cluster view"

# Decode a SAML Response from browser
echo '<base64-saml-response>' | base64 -d | xmllint --format -

# Test LDAP connectivity from inside the cluster
kubectl exec -it keycloak-0 -n keycloak -- \
  sh -c 'nc -zv domain-controller 389'

# Check Keycloak health
kubectl exec -it keycloak-0 -n keycloak -- \
  curl -sk https://localhost:8443/health/ready
Enter fullscreen mode Exit fullscreen mode

7. Conclusion

Integrating Morpheus Enterprise with Keycloak via SAML provides a robust, centralized authentication solution for enterprise cloud management. Key takeaways:

  • Morpheus acts as a SAML SP; Keycloak acts as the SAML IdP with AD backend
  • The Kubernetes deployment uses a StatefulSet with Infinispan clustering for HA
  • Self-signed TLS certificates are generated by init containers — no external cert-manager required for lab environments
  • Name a storageClassName in the PVC unless your cluster has a default class, or the whole stack stalls on unbound storage
  • Set Preserve Group Inheritance to OFF when Groups DN points at a container that mixes users and built-in groups
  • Client Signature Required must be OFF in Keycloak for Morpheus compatibility
  • Both Logout Service binding URLs must point to /login/auth to avoid the GroovyCastException bug
  • For MFA with federated users, bind a browser flow with OTP set to Required rather than relying on default required actions
  • Keep Keycloak's SSO Session Idle at or below the Morpheus session timeout, with Remember Me off
  • Always update the SAML Response Public Key in Morpheus after redeploying Keycloak

Have questions or ran into a different issue? Drop a comment below!


Written by Emre Baykal — March 2026 - Update September 2026

Top comments (0)