Skip to content

cloud-iam/demo-saml-skew

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SAML Clock Skew Demo

An interactive Docker-based demo that shows how clock skew between a SAML Identity Provider (IdP) and Service Provider (SP) causes authentication failures, and how allowedClockDrift fixes them.

What you will learn

  • Why SAML assertions carry NotBefore and NotOnOrAfter timestamps
  • How even a small clock difference between IdP and SP can break authentication
  • How the allowedClockDrift (clock skew tolerance) setting compensates for drift
  • A visual timeline that makes the math immediately obvious

Prerequisites

Quick start

docker compose up --build

Then open http://localhost:8080 in your browser.

Keycloak takes ~30-40 seconds to start. The SP waits automatically.

Port conflict? If port 8080 is already in use, change the SP port mapping in docker-compose.yml (e.g. "8081:5000") and update SP_BASE_URL accordingly.

Demo walkthrough

Step 1 — Baseline (no skew)

  1. Open http://localhost:8080 in your browser.
  2. You will see two cards:
    • "Controls" — two live clocks (IdP / Real Time and SP Perceived Time), two sliders (SP Clock Offset and Allowed Clock Drift), an Apply Settings button, and a Login with SAML button.
    • "What is Clock Skew?" — a brief explanation and an architecture diagram.
  3. Both sliders should already be at 0. No need to change anything for this first test.
  4. Click the blue Login with SAML button. You will be redirected to the Keycloak login page at localhost:8180.
  5. Enter username demo and password demo, then click Sign In.
  6. Keycloak redirects you back to the SP. You should see a green "Authentication Succeeded" result page, with a timeline showing the SP's time (green marker) sitting inside the blue assertion validity window.

Step 2 — Introduce clock drift

  1. Back on the SP page (http://localhost:8080), drag the SP Clock Offset slider to +300 (or type 300 in the number field). This simulates the SP's clock being 5 minutes ahead of the IdP.
  2. Click Apply Settings to save the new offset.
  3. Click Login with SAML. Because you already authenticated in Step 1, Keycloak has an active session — it will skip the login form and immediately issue a new assertion.
  4. You should see a red "Authentication Failed" result. The SP rejected the assertion because, from its perspective, the assertion has already expired — look at the timeline: the red SP marker sits to the right of the blue validity window.

Step 3 — Compensate with allowedClockDrift

  1. Click Back — Try Different Settings to return to the controls page.
  2. Keep Clock Offset at +300.
  3. Set Allowed Drift to 300 (drag the second slider or type the value). This tells the SP to tolerate up to 300 seconds of clock difference.
  4. Click Apply Settings, then Login with SAML.
  5. This time you should see a green "Authentication Succeeded". The timeline now shows an orange extended window (NotOnOrAfter + 300s) that covers the SP's perceived time.

Step 4 — Try a negative offset

  1. Click Back — Try Different Settings.
  2. Set Clock Offset to -300 (SP clock is 5 minutes behind the IdP) and Allowed Drift back to 0.
  3. Click Apply Settings, then Login with SAML.
  4. The SP rejects the assertion because, from its perspective, the assertion is not yet valid — the SP marker now sits to the left of the blue window.
  5. Set Allowed Drift to 300, click Apply Settings, and Login with SAML again — it succeeds because the extended window now starts at NotBefore − 300s.

Resetting the demo

Click Logout & Reset Demo to clear the SP session, reset sliders to zero, and log out of Keycloak. The next login will prompt for credentials again.

How it works

                          SAML Assertion
                   ┌─────────────────────────┐
                   │                         │
              NotBefore               NotOnOrAfter
                   │                         │
    ───────────────┼─────────────────────────┼───────────────  time
                   │    valid window          │
                   │                         │

  With allowedClockDrift = D:

         NB − D                                NOA + D
           │                                      │
    ───────┼──────────────────────────────────────┼─────────  time
           │         extended valid window         │

The SP checks: (NotBefore − drift) ≤ now < (NotOnOrAfter + drift)

If the SP's clock is ahead or behind by less than the drift tolerance, the assertion is still accepted.

Architecture

Service Internal port External port Image
Keycloak (IdP) 8080 8180 quay.io/keycloak/keycloak:26.5.6
SP (Flask) 5000 8080 Custom (Python 3.11)

The SP simulates clock drift by monkey-patching OneLogin_Saml2_Utils.now() — the function python3-saml uses for all timestamp validation. This is functionally equivalent to the SP's OS clock being offset.

Key files

File Purpose
docker-compose.yml Service definitions and networking
sp/app.py Flask SP — SAML endpoints, clock simulation, timestamp parsing
sp/templates/index.html Controls — offset slider, drift input, live clocks
sp/templates/result.html Result — success/failure, timeline visualization, assertion details
idp/realm-export.json Keycloak realm with pre-configured SAML client and demo user

Configuration

All settings can be changed via environment variables in docker-compose.yml:

Variable Default Description
KEYCLOAK_URL http://keycloak:8080 Internal Keycloak URL
KEYCLOAK_REALM demo Keycloak realm name
SP_ENTITY_ID saml-sp-demo SAML SP entity ID
SP_BASE_URL http://localhost:8080 SP URL as seen by the browser
SECRET_KEY (set in compose) Flask session secret

Keycloak admin console

Available at http://localhost:8180 with credentials admin / admin. You can inspect the SAML client configuration, view the signing certificate, and check event logs.

Troubleshooting

Symptom Fix
SP fails to start Keycloak may still be booting — the SP retries for up to 120 seconds
"Invalid issuer" error Make sure SP_BASE_URL matches the URL in your browser
Signature validation fails Restart both services (docker compose down -v && docker compose up --build)
Port conflict on 8080 Change ports in docker-compose.yml and update SP_BASE_URL

Real-world guidance

In production, prefer fixing the root cause (NTP synchronization) over increasing clock drift tolerance. A reasonable allowedClockDrift is 30-120 seconds — enough to cover minor NTP jitter without opening a wide replay window.

References

About

Interactive Docker demo showing how SAML clock skew breaks authentication and how allowedClockDrift fixes it with Keycloak, a Flask SP, and a visual timeline

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors