IT-Systemhaus der Bundesagentur für Arbeit

Push MFA

Keycloak push MFA Extension

7
Authenticator Apache-2.0

Keycloak Push MFA Extension

Maven Central

A Keycloak extension that adds push-based multi-factor authentication with passkey-inspired key continuity and pseudonymous credential handles.

Quick Start

Add the dependency to your project:

<dependency>
    <groupId>de.arbeitsagentur.opdt</groupId>
    <artifactId>keycloak-push-mfa-extension</artifactId>
    <!-- Check the badge above or Maven Central for the latest version -->
    <version>1.11.0</version>
</dependency>

Copy the JAR to Keycloak's providers/ directory and restart Keycloak.

Building from Source

# Build the provider
mvn -DskipTests package

# Run Keycloak with the demo realm
docker compose up
  • Keycloak Admin Console: http://localhost:8080 (login: admin / admin)
  • Demo Realm: demo with test user test / test
  • Demo Configuration: See config/demo-realm.json for a working example

Introduction

This project extends Keycloak with a push-style second factor that borrows some passkey-like ideas without implementing WebAuthn or passkeys themselves. After initial enrollment, the mobile app never receives the real user identifier from Keycloak; instead, it works with a credential id that only the app can map back to the real user, and later approvals remain bound to the enrolled device key. Everything is implemented with standard Keycloak SPIs plus a small JAX-RS resource exposed under /realms/<realm>/push-mfa.

High Level Flow

sequenceDiagram
    autonumber
    participant Browser as User / Browser
    participant Keycloak as Keycloak Server
    participant Provider as Push Provider (FCM/APNs)
    participant Mobile as Mobile App

    Note over Browser, Mobile: **Phase 1: Enrollment (Register Push MFA Device)**

    Browser->>Keycloak: Login & Trigger Enrollment
    Keycloak-->>Browser: Render QR Code & Start SSE Listener

    par Parallel Actions
        Browser->>Keycloak: SSE Request (Read Current Status)
        Browser->>Mobile: Scan QR Code
    end

    Note over Mobile: Verify Token & Generate User Key Pair

    Mobile->>Keycloak: POST /enroll/complete
    Note right of Mobile: Payload: Device JWT + Public JWK<br/>Signed with new Device Private Key

    Keycloak->>Keycloak: Verify Signature & Store Device Credential
    Keycloak-->>Browser: SSE Event: { status: "APPROVED" }
    Browser->>Keycloak: Auto-Submit Form (Enrollment Complete)

    Note over Browser, Mobile: **Phase 2: Login (Push MFA Confirmation)**

    Browser->>Keycloak: Login (Username/Password)
    Keycloak->>Keycloak: Generate Challenge & ConfirmToken

    par Parallel Actions
        Keycloak-->>Browser: Render "Waiting for approval..." Page
        Browser->>Keycloak: SSE Request (Read Current Challenge Status)
        Keycloak->>Provider: Send Push Notification
        Note right of Keycloak: Payload: ConfirmToken<br/>(Credential ID, ChallengeID)
    end

    Provider->>Mobile: Deliver Push Notification

    Mobile->>Mobile: Decrypt Token & Resolve User ID
    Mobile-->>Browser: (User Prompt: Approve?)
    Browser-->>Mobile: User Taps "Approve"

    Mobile->>Keycloak: POST /login/challenges/{cid}/respond
    Note right of Mobile: Payload: LoginToken (Action: Approve)<br/>Auth: DPoP Header + Access Token<br/>Signed with Device Private Key

    Keycloak->>Keycloak: Verify DPoP, Signature & Challenge ID
    Keycloak-->>Browser: SSE Event: { status: "APPROVED" }
    Browser->>Keycloak: Auto-Submit Form (Login Success)

Mermaid diagram — view rendered on GitHub

The SSE endpoints keep one browser stream per watch page and read challenge state from shared storage. Pending streams get periodic heartbeat comments and are rotated after a configurable maximum lifetime so browsers reconnect cleanly through proxies and firewalls. Cross-node delivery works because every node reads the same challenge state from shared storage; if a node dies, the browser's normal EventSource reconnect can land on another node and that node becomes responsible for the stream.

Documentation

Document Description
Setup Guide Step-by-step configuration instructions and Keycloak concepts
Flow Details Technical details of enrollment, login, SSE, and DPoP authentication
API Reference REST endpoints for mobile apps
Configuration All configuration options reference
App Implementation Guide for mobile app developers
SPI Reference Push notification, event, and rate limiting SPIs
UI Customization Theme and template customization
Security Security model and mobile app obligations
Firebase Cloud Messaging Provider Send real push notifications to your mobile app
Push Mfa Simulator Testing without a real mobile app
Troubleshooting Common issues and solutions
Load Testing Two-node browser+SSE loadtest setup and reproduction commands

Local Development

Prerequisites

  • JDK: See pom.xml for the required Java version.
  • Maven: Used for building the project.
  • Docker: Required for local setup (see docker-compose.yml) and integration tests.

Checks (formatting and tests)

Run the following commands locally to ensure code quality:

  • Formatting: mvn spotless:apply (Ensures consistent code style).
  • Verification: mvn verify (Runs the full test suite and builds the project).
  • Build without tests: mvn -DskipTests package

Indexed automatically from public sources. Report incorrect data or request removal.