Skip to content

Magic Token Link (MTL) - Authentication Pattern

A server-issued, time-limited authentication token that combines login and navigation into a single click — without embedding end-user credentials in the URL.

Version: 1.0.0 Last Updated: 2026-02-19


Table of Contents

  1. Overview
  2. Why Use MTL?
  3. How It Works
  4. URL Pattern
  5. Usage Examples
  6. Security
  7. Integration Patterns
  8. Comparison with BAL

Overview

The Magic Token Link (MTL) endpoint authenticates a user and redirects them to a target page in a single HTTP request. The authentication token is generated server-side by the API user and encodes:

  • username — which user account will be authenticated
  • targetUrl — where to land after authentication
  • expiresAt — token expiry (tokens are self-expiring)

Endpoint: GET /mtl/{token} (optionally ?m={memento})

Purpose: Give an external system (e.g., a hospital KIS) the ability to hand off a pre-authenticated, pre-filled browser session to an end user — without putting credentials in the URL.

Typical flow:

  1. External system calls a product API endpoint (e.g., POST /api/duba/v1/memento)
  2. The API response includes a ready-to-use magicLink URL (relative path)
  3. External system prepends the host and sends the absolute URL to the end user (email, portal link, etc.)
  4. End user clicks link → authenticates as the API user account → lands on the pre-filled form

Why Use MTL?

Embedding credentials in a URL to give users direct, authenticated access is tempting but flawed:

https://user:password@host/duba/BetreuungAnregung?m=memento123

Problems: - Credentials exposed: Password appears in server logs, browser history, email headers - No expiry: Link is valid forever (until the password changes) - Session confusion: Browser caches Basic Auth credentials — users cannot log out cleanly

The BAL pattern (/bal/) is an improvement (credentials → session, one-time use), but it still requires the end-user's password to be embedded in the link.

The MTL Solution

MTL replaces end-user credentials with a server-issued encrypted token:

https://host/mtl/eyJ...token.../duba/BetreuungAnregung?m=memento123
             ↓ MTL endpoint decrypts token
             ↓ Verifies expiry
             ↓ Authenticates the encoded user
             ↓ Creates secure session
             ↓ Redirects to target with memento

Benefits: - No end-user credentials in the URL — only an encrypted server token - Tokens are time-limited (configurable expiry, default 1 hour) - Tokens are server-signed — cannot be forged or tampered with - Standard session-based auth after redirect (logout works normally) - The magicLink is returned by API endpoints — no manual URL construction needed


How It Works

Request Flow

1. External system (KIS) calls API:
   POST /api/duba/v1/memento  (Basic Auth with API credentials)
   → Response: { "memento": "...", "magicLink": "/mtl/eyJ.../duba/form?m=..." }

2. External system prepends host and delivers the URL to end user:
   https://elim.example.com/mtl/eyJ.../duba/BetreuungAnregung?m=...

3. End user clicks link → Browser sends GET /mtl/{token}?m={memento}

4. MTL endpoint:
   a. Decrypts and decodes the token
   b. Validates expiry (rejects if expired → redirect to /login?error)
   c. Validates target URL (prevents open redirects)
   d. Loads the user account (verifies it still exists)
   e. Creates session-based authentication

5. 302 Redirect → /duba/BetreuungAnregung?m={memento}

6. User sees pre-filled form, fully authenticated with session cookie

Token Structure

Tokens are generated using the same AES-256-GCM encryption as all other mementos in the system. A token encodes:

{
  "username": "hospital-api-user",
  "targetUrl": "/duba/BetreuungAnregung",
  "expiresAt": "2026-02-19T15:30:00Z"
}

The token is URL-safe and cannot be modified without invalidating the signature.

The ?m= Memento Parameter

The form pre-fill memento travels separately from the token as a plain ?m= query parameter. It is not embedded in the token. Reasons:

  • The memento is already server-encrypted (AES-256-GCM) — double-encrypting adds size for no gain
  • Separating them keeps token size small and tokens reusable (in principle)
  • The ?m= value is appended to the redirect URL after successful authentication

URL Pattern

Syntax

/mtl/{token}
/mtl/{token}?m={memento}

Where: - /mtl/ — Magic Token Link endpoint prefix - {token} — Encrypted, URL-safe token (generated by product API endpoints) - ?m={memento} — Optional form pre-fill data (appended to final redirect URL)

Product API endpoints that support form pre-fill return a magicLink field in their JSON response. This is a relative URL — prepend your host to make it absolute:

MAGIC_LINK=$(curl ... | jq -r '.magicLink')
FULL_URL="https://elim.example.com$MAGIC_LINK"

Security Validations

The target URL embedded in the token is validated before use:

Check Purpose Example Blocked
Must start with / Prevent external redirects http://evil.com
Cannot start with // Prevent protocol-relative URLs //evil.com/path
Cannot contain .. Prevent path traversal /duba/../../etc/passwd
Cannot start with /mtl Prevent redirect loops /mtl/nested/token

Usage Examples

Scenario: Hospital KIS creates a court guardianship request and hands off a pre-filled form link to a hospital employee for review and submission.

#!/bin/bash
HOST="https://elim.example.com"
API_USER="hospital-api"
API_PASS="api-secret"

# Step 1: Create memento — response includes both memento and magicLink
RESPONSE=$(curl -s -X POST \
  -u "$API_USER:$API_PASS" \
  -H "Content-Type: application/json" \
  -d '{
    "jobId": "KH-2026-00456",
    "absender": { "aktenzeichen": "KH-2024-00123", "egvp_account_id": 42 },
    "empfaenger": {
      "name": "Amtsgericht Musterstadt",
      "type": "Gericht",
      "safeId": "de.justiz.bund.egvp.bea.12345"
    },
    "betroffener": {
      "name": { "vorname": "Max", "nachname": "Mustermann" },
      "geburtsdatum": "1950-01-15"
    }
  }' \
  "$HOST/api/duba/v1/memento")

# Step 2: Extract the magic link (already includes token + memento)
MAGIC_LINK=$(echo "$RESPONSE" | jq -r '.magicLink')
FULL_URL="$HOST$MAGIC_LINK"

# Step 3: Send to end user
echo "Please review and submit: $FULL_URL"
# Or: mail, portal display, QR code, etc.

What happens when the user clicks: 1. MTL endpoint decrypts token → identifies user as hospital-api 2. Validates expiry (default: 1 hour from generation) 3. Creates session for hospital-api account 4. Redirects to /duba/BetreuungAnregung?m={memento} 5. User sees pre-filled guardianship form ready to review and submit

Example 2: Testing MTL Tokens (curl)

# Generate token manually (for testing)
TOKEN=$(curl -s -X POST \
  -u "user:pass" \
  -H "Content-Type: application/json" \
  -d '{"jobId":"test-job","absender":{"egvp_account_id":42}}' \
  "https://elim.example.com/api/duba/v1/memento" | jq -r '.magicLink')

echo "Magic link (relative): $TOKEN"

# Follow the magic link (with redirect following)
curl -L -c cookies.txt "https://elim.example.com$TOKEN"
# After redirect, cookies.txt contains the SESSION cookie

Security

Token Properties

Property Value
Encryption AES-256-GCM (same key as memento system)
Default expiry 1 hour from generation
Forgeability Cannot be forged — server's encryption key required
Tamper detection Any modification invalidates the token (GCM auth tag)
Reuse Stateless — no single-use enforcement (by design, for simplicity)

Error Handling

All validation failures redirect to /login?error without revealing the reason:

Failure HTTP Result
Malformed/invalid token 302 → /login?error
Expired token 302 → /login?error
Unknown username 302 → /login?error
Invalid target URL 302 → /login?error

No SESSION cookie is set on failure.

Operational Notes

  • HTTPS is required in production — tokens are URL-safe but should not travel in plain text
  • Token expiry is configured by the server — default is 1 hour; contact your administrator for details
  • No revocation before expiry — if a token needs to be invalidated early, change the user's password (this rotates the KEK, making all existing tokens undecryptable)
  • Tokens are not single-use — a valid, unexpired token can be used multiple times; this is acceptable because the token is time-limited and user-specific

Integration Patterns

Use case: Hospital system creates court forms; hospital employees review and submit.

Hospital KIS
    ↓
[1] POST /api/duba/v1/memento with patient/case data
    ↓
[2] Response: { "memento": "...", "magicLink": "/mtl/.../duba/form?m=..." }
    ↓
[3] KIS prepends host → absolute URL
    ↓
[4] Send URL to hospital employee via email or portal
    ↓
Hospital Employee clicks link (no login required)
    ↓
[5] GET /mtl/{token}?m={memento} → authenticated session
    ↓
[6] 302 → /duba/BetreuungAnregung?m={memento}
    ↓
[7] Employee reviews pre-filled form and submits to court

Pattern 2: Direct Console Access (Testing/Development)

Use case: Developer or administrator needs quick authenticated access to a specific page.

# Get a magic link for a known page
LINK=$(curl -s -X POST -u "$USER:$PASS" \
  -H "Content-Type: application/json" \
  -d '{"jobId":"dev-test"}' \
  "$HOST/api/duba/v1/memento" | jq -r '.magicLink')

echo "Open in browser: $HOST$LINK"

Comparison with BAL

Both BAL (/bal/) and MTL (/mtl/) convert an authentication mechanism into a session. They serve different use cases:

Aspect BAL MTL
Credential in URL Yes (username:password) No (server-issued token)
Generated by External system (constructs URL manually) Server (API response includes magicLink)
Expiry No (valid until password changes) Yes (configurable, default 1 hour)
Forgeable by external party Yes (if credentials known) No (requires server encryption key)
Supports memento pre-fill Yes (via ?m= parameter) Yes (via ?m= parameter, included automatically)
Use case Legacy integrations; manual URL construction New integrations; API returns link directly

Recommendation: - New integrations → use MTL. The API endpoint returns magicLink ready to use. - Existing BAL integrations continue to work. BAL is not deprecated.



This is external-facing documentation for integration partners. For internal implementation details, see src/main/java/de/vertama/web/magic/Controller.kt and Payload.kt in the elim repository.