Business-Portale entwickeln: Kunden-, Partner- & Mitarbeiter-Portale
Business & Enterprise

Business-Portale entwickeln: Kunden-, Partner- & Mitarbeiter-Portale

Carola Schulte
Invalid Date
18 min

“Können wir das Portal nicht schnell mit WordPress machen?” – Diese Frage höre ich oft. Die Antwort: Ja, für 50 User vielleicht. Aber für 5.000 User mit komplexen Rollen, SSO-Anbindung und Audit-Logs? Nein. Nach 15+ Jahren Portal-Entwicklung zeige ich Ihnen, wie robuste Business-Portale funktionieren – von 10 bis 15.000 User.

Executive Summary: ROI & Business Impact

Mitarbeiter-Portal (Real Case, 15.000 User):

  • Papier-Prozesse digitalisiert: Urlaubsanträge, Führerscheinkontrolle, Zeiterfassung
  • HR-Aufwand: 40h/Woche → 5h/Woche (87% Reduktion)
  • Durchlaufzeit Urlaubsantrag: 5 Tage → 2 Stunden
  • Self-Service-Quote: 85% (HR behandelt nur noch Ausnahmen)

Investition: 120k Entwicklung + 1.500€/Monat Betrieb, ROI nach 11 Monaten.

TL;DR – Technical Deep Dive

  • SSO-Integration: SAML 2.0, OAuth 2.0, LDAP/AD → Single Sign-On für Enterprise
  • Rollen & Rechte: Hierarchisches RBAC-System mit Vererbung
  • Self-Service: Dokumenten-Download, Datenänderungen, Workflow-Freigaben
  • Audit-Logs: Vollständige Nachvollziehbarkeit (DSGVO, ISO 27001)
  • Skalierung: Von 10 bis 15.000 concurrent User

Was ist ein Business-Portal?

Definition

Ein Business-Portal ist eine Webanwendung, die verschiedene Stakeholder (Kunden, Partner, Mitarbeiter) Zugriff auf zentrale Funktionen gibt:

  • Self-Service: User können Daten selbst abrufen/ändern
  • Single Sign-On (SSO): Eine Anmeldung für alle Systeme
  • Rollenbasierter Zugriff: Jeder sieht nur, was er darf
  • Integration: Anbindung an ERP, CRM, HR-Systeme

Typische Portal-Arten

1. Kundenportal

  • Bestellhistorie, Rechnungen, Support-Tickets
  • Self-Service für Stammdaten (Adresse, Ansprechpartner)
  • Dokumenten-Download (Verträge, Zertifikate)

2. Partner-Portal

  • Lead-Verwaltung, Co-Marketing-Material
  • Provisionsübersicht, Verkaufsstatistiken
  • Schulungsunterlagen, Zertifizierungen

3. Mitarbeiter-Portal

  • Urlaubsanträge, Zeiterfassung, Gehaltsabrechnungen
  • Führerscheinkontrolle, Schulungsnachweise
  • Unternehmensnews, Mitarbeiterverzeichnis

Architektur: Die 4 Kern-Layer

Layer 1: Authentication & SSO

Problem: User haben bereits 5+ Logins (AD, Salesforce, SAP, …). Noch eins? Nein.

Lösung: Single Sign-On via SAML 2.0 oder OAuth 2.0.

SAML 2.0 (für Enterprise)

// SAML-Integration mit SimpleSAMLphp
use SimpleSAML\Auth\Simple;

class SamlAuthService {
    private $auth;

    public function __construct() {
        $this->auth = new Simple('default-sp');
    }

    public function login(): void {
        $this->auth->requireAuth();
    }

    public function getUser(): array {
        $attributes = $this->auth->getAttributes();

        return [
            'email' => $attributes['mail'][0] ?? null,
            'name' => $attributes['displayName'][0] ?? null,
            'groups' => $attributes['memberOf'] ?? [],
            'employee_id' => $attributes['employeeNumber'][0] ?? null,
        ];
    }

    public function logout(): void {
        $this->auth->logout('/');
    }
}

// In Controller
$saml = new SamlAuthService();
$saml->login();
$user = $saml->getUser();

// User-Session mit DB synchronisieren
$userRepository->findOrCreateByEmail($user['email'], $user);

SAML-Flow:

1. User → Portal: Zugriff auf /dashboard
2. Portal → IdP: SAML-Request senden
3. IdP: User authentifizieren (Firmen-Login)
4. IdP → Portal: SAML-Response (Assertion) mit User-Attributen
5. Portal: Session erstellen, User einloggen

OAuth 2.0 (für SaaS/Cloud)

// OAuth 2.0 mit Microsoft Azure AD
use League\OAuth2\Client\Provider\GenericProvider;

class AzureAdAuthService {
    private $provider;

    public function __construct(string $clientId, string $clientSecret, string $tenantId) {
        $this->provider = new GenericProvider([
            'clientId' => $clientId,
            'clientSecret' => $clientSecret,
            'redirectUri' => 'https://portal.example.com/auth/callback',
            'urlAuthorize' => "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/authorize",
            'urlAccessToken' => "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token",
            'urlResourceOwnerDetails' => 'https://graph.microsoft.com/v1.0/me',
            'scopes' => 'openid profile email User.Read',
        ]);
    }

    public function getAuthorizationUrl(): string {
        return $this->provider->getAuthorizationUrl();
    }

    public function handleCallback(string $code): array {
        $accessToken = $this->provider->getAccessToken('authorization_code', [
            'code' => $code,
        ]);

        $resourceOwner = $this->provider->getResourceOwner($accessToken);
        return $resourceOwner->toArray();
    }
}

LDAP/AD-Anbindung (für On-Premise)

// LDAP-Integration
class LdapAuthService {
    private $conn;

    public function __construct(string $host, int $port, string $baseDn) {
        $this->conn = ldap_connect($host, $port);
        ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($this->conn, LDAP_OPT_REFERRALS, 0);
    }

    public function authenticate(string $username, string $password): bool {
        $userDn = "cn=$username,ou=users,dc=example,dc=com";
        return @ldap_bind($this->conn, $userDn, $password) !== false;
    }

    public function getUserGroups(string $username): array {
        $filter = "(cn=$username)";
        $result = ldap_search($this->conn, $this->baseDn, $filter, ['memberOf']);
        $entries = ldap_get_entries($this->conn, $result);

        if ($entries['count'] === 0) {
            return [];
        }

        return $entries[0]['memberof'] ?? [];
    }
}

SSO-Entscheidungsmatrix:

Use-CaseProtokollBegründung
On-Premise Active DirectorySAML 2.0 oder LDAPAD unterstützt SAML via AD FS
Microsoft 365 / Azure ADOAuth 2.0 (OpenID Connect)Native OAuth-Unterstützung
Google WorkspaceOAuth 2.0Google OAuth einfacher als SAML
Multi-IdP (mehrere Identity Provider)SAML 2.0Standard für Federation
Einfacher Username/PasswordNative LoginFür kleine Portale unter 50 User

Layer 2: Rollen & Rechte (RBAC)

Problem: “Marketing-Team darf Kampagnen sehen, aber nicht Budget. Teamleiter dürfen zusätzlich Budget sehen.”

Lösung: Role-Based Access Control (RBAC) mit Hierarchie.

Datenbank-Schema

-- Rollen
CREATE TABLE rollen (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL UNIQUE,
    beschreibung TEXT,
    parent_id INTEGER REFERENCES rollen(id),  -- Vererbung
    created_at TIMESTAMP DEFAULT NOW()
);

-- Rechte
CREATE TABLE rechte (
    id SERIAL PRIMARY KEY,
    ressource VARCHAR(100) NOT NULL,  -- z.B. "kampagnen"
    aktion VARCHAR(50) NOT NULL,      -- z.B. "lesen", "schreiben"
    beschreibung TEXT,
    UNIQUE(ressource, aktion)
);

-- Rollen → Rechte (M:N)
CREATE TABLE rollen_rechte (
    rolle_id INTEGER REFERENCES rollen(id) ON DELETE CASCADE,
    recht_id INTEGER REFERENCES rechte(id) ON DELETE CASCADE,
    PRIMARY KEY (rolle_id, recht_id)
);

-- User → Rollen (M:N)
CREATE TABLE user_rollen (
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    rolle_id INTEGER REFERENCES rollen(id) ON DELETE CASCADE,
    gueltig_von DATE DEFAULT CURRENT_DATE,
    gueltig_bis DATE,
    PRIMARY KEY (user_id, rolle_id)
);

Beispiel-Rollen (Hierarchisch)

-- Basis-Rolle
INSERT INTO rollen (id, name, parent_id) VALUES
(1, 'Mitarbeiter', NULL),
(2, 'Marketing-Team', 1),
(3, 'Marketing-Teamleiter', 2),
(4, 'Geschäftsführung', 1);

-- Rechte
INSERT INTO rechte (id, ressource, aktion) VALUES
(1, 'kampagnen', 'lesen'),
(2, 'kampagnen', 'schreiben'),
(3, 'budget', 'lesen'),
(4, 'budget', 'schreiben');

-- Rechte-Vergabe
-- Mitarbeiter: Basis-Rechte (Profil bearbeiten, News lesen)
-- Marketing-Team: Kampagnen lesen
INSERT INTO rollen_rechte (rolle_id, recht_id) VALUES
(2, 1);  -- Marketing-Team darf Kampagnen lesen

-- Marketing-Teamleiter: Zusätzlich Budget lesen (erbt Kampagnen-Rechte)
INSERT INTO rollen_rechte (rolle_id, recht_id) VALUES
(3, 3);  -- Teamleiter darf Budget lesen

-- Geschäftsführung: Alles
INSERT INTO rollen_rechte (rolle_id, recht_id) VALUES
(4, 1), (4, 2), (4, 3), (4, 4);

PHP-Service für Rechte-Prüfung

class RbacService {
    private PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    /**
     * Prüft, ob User Berechtigung hat (inkl. Vererbung)
     */
    public function userHatRecht(int $userId, string $ressource, string $aktion): bool {
        $sql = "
            WITH RECURSIVE rollen_hierarchie AS (
                -- Direkte Rollen des Users
                SELECT r.id, r.parent_id
                FROM rollen r
                JOIN user_rollen ur ON r.id = ur.rolle_id
                WHERE ur.user_id = :user_id
                  AND (ur.gueltig_von IS NULL OR ur.gueltig_von <= CURRENT_DATE)
                  AND (ur.gueltig_bis IS NULL OR ur.gueltig_bis >= CURRENT_DATE)

                UNION ALL

                -- Parent-Rollen (Vererbung)
                SELECT r.id, r.parent_id
                FROM rollen r
                JOIN rollen_hierarchie rh ON r.id = rh.parent_id
            )
            SELECT 1
            FROM rollen_hierarchie rh
            JOIN rollen_rechte rr ON rh.id = rr.rolle_id
            JOIN rechte re ON rr.recht_id = re.id
            WHERE re.ressource = :ressource
              AND re.aktion = :aktion
            LIMIT 1
        ";

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([
            'user_id' => $userId,
            'ressource' => $ressource,
            'aktion' => $aktion,
        ]);

        return $stmt->fetchColumn() !== false;
    }

    /**
     * Holt alle Rechte eines Users (für Caching)
     */
    public function getUserRechte(int $userId): array {
        $sql = "
            WITH RECURSIVE rollen_hierarchie AS (
                SELECT r.id, r.parent_id
                FROM rollen r
                JOIN user_rollen ur ON r.id = ur.rolle_id
                WHERE ur.user_id = :user_id
                  AND (ur.gueltig_von IS NULL OR ur.gueltig_von <= CURRENT_DATE)
                  AND (ur.gueltig_bis IS NULL OR ur.gueltig_bis >= CURRENT_DATE)

                UNION ALL

                SELECT r.id, r.parent_id
                FROM rollen r
                JOIN rollen_hierarchie rh ON r.id = rh.parent_id
            )
            SELECT DISTINCT re.ressource, re.aktion
            FROM rollen_hierarchie rh
            JOIN rollen_rechte rr ON rh.id = rr.rolle_id
            JOIN rechte re ON rr.recht_id = re.id
        ";

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(['user_id' => $userId]);

        return $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
    }
}

// In Controller/Middleware
if (!$rbac->userHatRecht($userId, 'budget', 'lesen')) {
    http_response_code(403);
    echo "Keine Berechtigung";
    exit;
}

Performance: Rechte-Caching

// Session-Cache für User-Rechte (reduziert DB-Last)
class CachedRbacService {
    private RbacService $rbac;

    public function userHatRecht(int $userId, string $ressource, string $aktion): bool {
        // Cache-Key pro User
        $cacheKey = "user_rechte_$userId";

        if (!isset($_SESSION[$cacheKey])) {
            // Alle Rechte holen und cachen
            $_SESSION[$cacheKey] = $this->rbac->getUserRechte($userId);
        }

        $rechte = $_SESSION[$cacheKey];
        $key = "$ressource:$aktion";

        return isset($rechte[$key]);
    }

    public function clearCache(int $userId): void {
        unset($_SESSION["user_rechte_$userId"]);
    }
}

Layer 3: Self-Service-Funktionen

Ziel: User können 80% ihrer Anfragen selbst erledigen (ohne HR/Support).

Beispiel: Urlaubsantrag mit Workflow

class UrlaubsantragService {
    public function antragStellen(int $userId, string $von, string $bis, string $grund): int {
        $this->pdo->beginTransaction();

        try {
            // 1. Antrag erstellen
            $stmt = $this->pdo->prepare("
                INSERT INTO urlaubsantraege (user_id, von_datum, bis_datum, grund, status, erstellt_am)
                VALUES (:user_id, :von, :bis, :grund, 'pending', NOW())
                RETURNING id
            ");
            $stmt->execute([
                'user_id' => $userId,
                'von' => $von,
                'bis' => $bis,
                'grund' => $grund,
            ]);
            $antragId = $stmt->fetchColumn();

            // 2. Workflow: Vorgesetzten ermitteln
            $vorgesetzter = $this->getVorgesetzter($userId);

            // 3. Freigabe-Task erstellen
            $this->pdo->prepare("
                INSERT INTO freigabe_tasks (antrag_id, genehmiger_id, status, deadline)
                VALUES (:antrag_id, :genehmiger_id, 'open', CURRENT_DATE + INTERVAL '3 days')
            ")->execute([
                'antrag_id' => $antragId,
                'genehmiger_id' => $vorgesetzter,
            ]);

            // 4. E-Mail an Vorgesetzten
            $this->mailService->send($vorgesetzter, "Neuer Urlaubsantrag", "...");

            // 5. Audit-Log
            $this->auditLog->log($userId, 'urlaubsantrag.erstellt', $antragId);

            $this->pdo->commit();
            return $antragId;

        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }

    public function genehmigen(int $antragId, int $genehmigerequestId, bool $genehmigt, string $kommentar = ''): void {
        // Prüfen: Ist User der zuständige Genehmiger?
        if (!$this->istZustaendigerGenehmiger($antragId, $genehmigerId)) {
            throw new UnauthorizedException("Keine Berechtigung");
        }

        $this->pdo->beginTransaction();

        try {
            // Status setzen
            $status = $genehmigt ? 'genehmigt' : 'abgelehnt';

            $this->pdo->prepare("
                UPDATE urlaubsantraege
                SET status = :status, genehmigt_am = NOW(), genehmigt_von = :genehmiger_id
                WHERE id = :antrag_id
            ")->execute([
                'status' => $status,
                'antrag_id' => $antragId,
                'genehmiger_id' => $genehmigerId,
            ]);

            // Freigabe-Task schließen
            $this->pdo->prepare("
                UPDATE freigabe_tasks
                SET status = :status, erledigt_am = NOW(), kommentar = :kommentar
                WHERE antrag_id = :antrag_id AND genehmiger_id = :genehmiger_id
            ")->execute([
                'status' => $status,
                'antrag_id' => $antragId,
                'genehmiger_id' => $genehmigerId,
                'kommentar' => $kommentar,
            ]);

            // E-Mail an Antragsteller
            $antragsteller = $this->getAntragsteller($antragId);
            $this->mailService->send($antragsteller, "Urlaubsantrag $status", "...");

            // Audit-Log
            $this->auditLog->log($genehmigerId, "urlaubsantrag.$status", $antragId);

            $this->pdo->commit();

        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
}

Layer 4: Audit-Logs (DSGVO, ISO 27001)

Anforderung: Jede Änderung muss nachvollziehbar sein.

CREATE TABLE audit_logs (
    id BIGSERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES users(id),
    aktion VARCHAR(100) NOT NULL,
    ressource_typ VARCHAR(50) NOT NULL,
    ressource_id INTEGER,
    alte_daten JSONB,
    neue_daten JSONB,
    ip_adresse INET,
    user_agent TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Index für schnelle Suche
CREATE INDEX idx_audit_logs_user ON audit_logs(user_id, created_at DESC);
CREATE INDEX idx_audit_logs_ressource ON audit_logs(ressource_typ, ressource_id, created_at DESC);
class AuditLogService {
    public function log(
        int $userId,
        string $aktion,
        string $ressourceTyp,
        ?int $ressourceId = null,
        ?array $alteDaten = null,
        ?array $neueDaten = null
    ): void {
        $stmt = $this->pdo->prepare("
            INSERT INTO audit_logs (
                user_id, aktion, ressource_typ, ressource_id,
                alte_daten, neue_daten, ip_adresse, user_agent
            ) VALUES (
                :user_id, :aktion, :ressource_typ, :ressource_id,
                :alte_daten::jsonb, :neue_daten::jsonb, :ip, :ua
            )
        ");

        $stmt->execute([
            'user_id' => $userId,
            'aktion' => $aktion,
            'ressource_typ' => $ressourceTyp,
            'ressource_id' => $ressourceId,
            'alte_daten' => $alteDaten ? json_encode($alteDaten) : null,
            'neue_daten' => $neueDaten ? json_encode($neueDaten) : null,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
            'ua' => $_SERVER['HTTP_USER_AGENT'] ?? null,
        ]);
    }
}

// Anwendung
$audit->log($userId, 'user.email_changed', 'users', $userId,
    ['email' => 'alt@example.com'],
    ['email' => 'neu@example.com']
);

Skalierung: Von 10 zu 15.000 User

Performance-Optimierungen

1. Connection Pooling (PgBouncer)

# pgbouncer.ini
[databases]
portal = host=localhost port=5432 dbname=portal_db

[pgbouncer]
pool_mode = transaction
max_client_conn = 2000
default_pool_size = 50

2. Redis für Sessions

// Session-Handler mit Redis
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://127.0.0.1:6379?database=1');
session_start();

3. Materialized Views für Dashboards

-- Dashboard-KPIs (statt Live-Aggregation)
CREATE MATERIALIZED VIEW dashboard_stats AS
SELECT
    DATE_TRUNC('day', created_at) AS datum,
    COUNT(*) FILTER (WHERE status = 'pending') AS offene_antraege,
    COUNT(*) FILTER (WHERE status = 'genehmigt') AS genehmigte_antraege,
    AVG(EXTRACT(EPOCH FROM (genehmigt_am - erstellt_am)) / 3600) AS avg_bearbeitungszeit_h
FROM urlaubsantraege
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE_TRUNC('day', created_at);

-- Refresh nightly
CREATE OR REPLACE FUNCTION refresh_dashboard_stats()
RETURNS void AS $$
BEGIN
    REFRESH MATERIALIZED VIEW CONCURRENTLY dashboard_stats;
END;
$$ LANGUAGE plpgsql;

-- Cron: 03:00 Uhr

Real-World Case Study: Mitarbeiter-Portal (15.000 User)

Ausgangslage

Kunde: Sicherheitsdienst, 15.000 Mitarbeiter (Wachdienst, Pförtner) Problem: Papier-basierte Prozesse (Urlaubsanträge per Fax, Führerscheinkopien per Post) Anforderung: Self-Service-Portal mit SSO (AD-Anbindung)

Lösung: Phasen-Rollout

Phase 1: MVP (8 Wochen, 40k)

  • SAML-SSO via AD FS
  • Profilverwaltung (Adresse, Bankdaten)
  • Urlaubsanträge mit Workflow
  • Gehaltsabrechnungen-Download

Phase 2: Compliance (6 Wochen, 30k)

  • Führerscheinkontrolle (Upload, Ablauf-Erinnerungen)
  • Schulungsnachweise (Zertifikate, Fristen)
  • Audit-Logs für ISO 27001

Phase 3: Integration (8 Wochen, 50k)

  • Zeiterfassungs-Integration (SAP HR)
  • Export für Lohnbuchhaltung (DATEV)
  • Mobile-Optimierung

Ergebnisse

  • HR-Aufwand: 40h/Woche → 5h/Woche (nur noch Ausnahmen)
  • Durchlaufzeit Urlaubsantrag: 5 Tage → 2h (85% am selben Tag genehmigt)
  • Führerschein-Compliance: 100% (vorher 60% wegen Papier-Chaos)
  • User-Akzeptanz: 92% (App-Store-Bewertung 4,6/5)

Kosten

  • Entwicklung: 120.000 EUR (22 Wochen)
  • Betrieb: 1.500 EUR/Monat (Hosting, Monitoring, Support)
  • ROI: 11 Monate

Kosten-Kalkulation

Small (10-100 User)

Features:

  • Native Login (Username/Password)
  • Basis-RBAC (3-5 Rollen)
  • 3-5 Self-Service-Funktionen
  • Dokumenten-Download

Kosten: 30.000-50.000 EUR

Medium (100-1.000 User)

Features:

  • SSO (SAML oder OAuth)
  • Hierarchisches RBAC (10+ Rollen)
  • 10+ Self-Service-Funktionen
  • Workflow-Engine (Freigaben)
  • Audit-Logs

Kosten: 60.000-100.000 EUR

Enterprise (1.000-15.000 User)

Features:

  • Multi-IdP SSO (SAML + OAuth + LDAP)
  • Komplexes RBAC mit Vererbung
  • 20+ Self-Service-Funktionen
  • ERP/CRM-Integration (SAP, Salesforce)
  • Hochverfügbarkeit (Load Balancing)
  • Audit-Logs + SIEM-Integration

Kosten: 100.000-200.000 EUR

Technologie-Stack

Backend

PHP 8.2+ (Framework-frei)
PostgreSQL 14+ (RBAC-Queries, JSONB für Audit-Logs)
Redis (Session-Store, Caching)
PgBouncer (Connection Pooling)

Authentication

SimpleSAMLphp (SAML 2.0)
League OAuth2 Client (OAuth 2.0)
PHP LDAP Extension (AD-Anbindung)

Frontend

Alpine.js (leichtgewichtige Interaktivität)
TailwindCSS (Styling)
Stimulus.js (optional für komplexere Komponenten)

Infrastructure

Nginx (Reverse Proxy, TLS)
Debian 12 (OS)
Let's Encrypt (TLS-Zertifikate)
Grafana + Prometheus (Monitoring)

Checkliste: Portal-Entwicklung

  • Authentication:

    • SSO-Protokoll gewählt? (SAML/OAuth/LDAP)
    • Fallback bei SSO-Ausfall? (Native Login)
    • Multi-Factor-Auth (MFA) nötig?
  • Authorization:

    • RBAC-Hierarchie definiert?
    • Rechte-Vererbung klar?
    • Zeitliche Befristung von Rollen?
  • Self-Service:

    • Top-5-Use-Cases identifiziert?
    • Workflow-Freigaben nötig?
    • Dokumenten-Download strukturiert?
  • Audit-Logs:

    • Alle Änderungen geloggt?
    • Personenbezogene Daten anonymisiert nach DSGVO?
    • Logs gegen Manipulation gesichert?
  • Performance:

    • Connection Pooling aktiv? (PgBouncer)
    • Session-Store skalierbar? (Redis)
    • Dashboard-KPIs vorberechnet? (Materialized Views)
  • Security:

    • TLS 1.3 erzwungen?
    • CSRF-Protection aktiv?
    • Rate-Limiting für Login?
    • Security-Headers (CSP, HSTS)?

Mein Angebot

Ich entwickle Business-Portale seit 2008 für Mittelstand und Enterprise:

  • Portal-Assessment (1 Tag): Use-Case-Analyse, SSO-Strategie, Kosten-Schätzung
  • MVP-Entwicklung (8-12 Wochen): Schneller Start mit Kern-Features
  • Enterprise-Portale (ab 12 Wochen): Komplexe RBAC, Multi-IdP, ERP-Integration

Kostenlose Erstberatung:

Senden Sie mir Ihre Use-Cases → Ich schätze Aufwand + gebe Architektur-Empfehlung (kostenlos, 30 Min Call).

📧 die@entwicklerin.net 🌐 www.entwicklerin.net 📍 Lüneburg & Remote


Über die Autorin: Carola Schulte entwickelt seit 2008 Business-Portale für Mittelstand und Enterprise. Schwerpunkte: SSO-Integration, RBAC, Self-Service-Workflows. 15+ Produktions-Portale, 10-15.000 User, 24/7-Betrieb.


Hat Ihnen dieser Artikel geholfen? Teilen Sie ihn mit Kollegen, die Portal-Projekte planen.

Weitere Artikel in dieser Serie:

Carola Schulte

Über Carola Schulte

Software-Architektin mit 25+ Jahren Erfahrung. Spezialisiert auf robuste Business-Apps mit PHP/PostgreSQL, Security-by-Design und DSGVO-konforme Systeme. 1,8M+ Lines of Code in Produktion.

Projekt im Kopf?

Lassen Sie uns besprechen, wie ich Ihre Anforderungen umsetzen kann – kostenlos und unverbindlich.

Kostenloses Erstgespräch