Business-Portale entwickeln: Kunden-, Partner- & Mitarbeiter-Portale
“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-Case | Protokoll | Begründung |
|---|---|---|
| On-Premise Active Directory | SAML 2.0 oder LDAP | AD unterstützt SAML via AD FS |
| Microsoft 365 / Azure AD | OAuth 2.0 (OpenID Connect) | Native OAuth-Unterstützung |
| Google Workspace | OAuth 2.0 | Google OAuth einfacher als SAML |
| Multi-IdP (mehrere Identity Provider) | SAML 2.0 | Standard für Federation |
| Einfacher Username/Password | Native Login | Fü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:
- SSO implementieren: SAML, OAuth, LDAP (erscheint 15.07.2025)
- Workflow-Automatisierung: Digitale Freigabeprozesse (15.03.2025)
- DSGVO für Webapplikationen: Was wirklich nötig ist (01.04.2025)
Ü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