CSRF-Token für Backend-Links im Frontend
Quell-ID: GitHub Discussion #15
Use Case
Aus dem Frontend soll ein Link zum Bearbeiten eines YForm-Datensatzes im Backend erstellt werden. Dabei wird der CSRF-Token benötigt, jedoch unterscheiden sich die Tokens für Frontend und Backend.
Verwendete AddOns
- REDAXO Core
- YForm
Problemstellung
Der folgende Code generiert einen CSRF-Token, der im Backend nicht akzeptiert wird:
if (rex_backend_login::hasSession() && $newsDataId != "") {
$table = rex_yform_manager_table::get('rex_news');
$_csrf_key = $table->getCSRFKey();
$_csrf_params = rex_csrf_token::factory($_csrf_key)->getUrlParams();
$token = $_csrf_params['_csrf_token'];
$edit = '<a href="' . rex_url::backendPage(
'yform/manager/data_edit',
[
'table_name' => 'rex_news',
'func' => 'edit',
'data_id' => $newsDataId,
'_csrf_token' => $token
]
) . '">News bearbeiten</a>';
}
Lösung
Die CSRF-Tokens für das Frontend und das Backend unterscheiden sich. Die Umgebung muss temporär umgeschaltet werden, damit die CSRF-Generierung im richtigen Kontext erfolgt.
<?php
if (rex_backend_login::hasSession() && $newsDataId != "") {
// Umgebung temporär auf Backend umschalten
rex::setProperty('redaxo', true);
$table = rex_yform_manager_table::get('rex_news');
$_csrf_key = $table->getCSRFKey();
$_csrf_params = rex_csrf_token::factory($_csrf_key)->getUrlParams();
$token = $_csrf_params['_csrf_token'];
// Umgebung zurücksetzen
rex::setProperty('redaxo', false);
$edit = '<a class="btn btn-primary" href="' . rex_url::backendPage(
'yform/manager/data_edit',
[
'table_name' => 'rex_news',
'func' => 'edit',
'data_id' => $newsDataId,
'_csrf_token' => $token
]
) . '" target="_blank">
<i class="fa fa-edit"></i> News bearbeiten
</a>';
}
Wichtiger Hinweis
Bei der CLI-Umgebung gibt es kein $_REQUEST-Objekt. Falls der Code auch in CLI-Kontexten ausgeführt werden könnte, sollte dies geprüft werden:
<?php
if (PHP_SAPI !== 'cli' && rex_backend_login::hasSession() && $newsDataId != "") {
// ... Token-Generierung
}
Besserer Ansatz
Als wiederverwendbare Helper-Funktion
<?php
class BackendEditLink
{
/**
* Erstellt einen Edit-Link für einen YForm-Datensatz
*/
public static function create(string $tableName, int $dataId, string $linkText = 'Bearbeiten'): string
{
if (PHP_SAPI === 'cli' || !rex_backend_login::hasSession()) {
return '';
}
// Umgebung temporär umschalten
$wasBackend = rex::isBackend();
rex::setProperty('redaxo', true);
try {
$table = rex_yform_manager_table::get($tableName);
if (!$table) {
return '';
}
$csrfKey = $table->getCSRFKey();
$csrfParams = rex_csrf_token::factory($csrfKey)->getUrlParams();
$url = rex_url::backendPage(
'yform/manager/data_edit',
[
'table_name' => $tableName,
'func' => 'edit',
'data_id' => $dataId,
'_csrf_token' => $csrfParams['_csrf_token']
]
);
return sprintf(
'<a href="%s" target="_blank" class="backend-edit-link">%s</a>',
rex_escape($url),
rex_escape($linkText)
);
} finally {
// Umgebung zurücksetzen
rex::setProperty('redaxo', $wasBackend);
}
}
}
// Verwendung im Template/Modul
echo BackendEditLink::create('rex_news', $newsDataId, 'News bearbeiten');
Mit Berechtigungsprüfung
<?php
public static function create(string $tableName, int $dataId, string $linkText = 'Bearbeiten'): string
{
$user = rex_backend_login::createUser();
if (!$user) {
return '';
}
// Berechtigung prüfen
$table = rex_yform_manager_table::get($tableName);
if (!$table || !$user->hasPerm('yform[' . $tableName . ']')) {
return '';
}
// ... Rest der Logik
}
Diese Lösung ist sicherer und wiederverwendbar.