-
-
Notifications
You must be signed in to change notification settings - Fork 133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Logout as POST request #185
Conversation
Unprocessable entity is caused by CSRF protection. You should add CSRF token to the form. |
That would mean I have to pass CSRF to every page. That sounds like a bad idea somehow. |
There are two solutions to this:
Both aren't ideal but first solution looks a bit better to me. |
Is it possible to inject token inside |
Yes. |
@samdark Is this what you meant? I am somewhat coding blind, because |
Yes, something like that but likely that exact code won't work as is. You're passing token as an option but it should be a hidden form field. @BoShurik idea may be interesting as well but looks a bit more complicated. Something like the following: <?php
namespace App\Widget;
use Yiisoft\Csrf\CsrfToken;
use Yiisoft\Html\Html;
use Yiisoft\Http\Method;
use Yiisoft\Widget\Widget;
use Yiisoft\Form\Widget\Form as BasicForm;
final class Form extends Widget
{
private string $action = '';
private string $method = Method::POST;
private array $options = [];
private CsrfToken $csrfToken;
public function __construct(CsrfToken $csrfToken)
{
$this->csrfToken = $csrfToken;
}
public function begin(): ?string
{
$config = [
'action()' => [$this->action],
'method()' => [$this->method],
'options()' => [$this->options],
];
return BasicForm::widget($config)->begin() . $this->renderCsrfToken();
}
private function renderCsrfToken(): string
{
return Html::hiddenInput('_csrf', $this->csrfToken->getValue());
}
protected function run(): string
{
return BasicForm::end();
}
public function action(string $value): self
{
$new = clone $this;
$new->action = $value;
return $new;
}
public function method(string $value): self
{
$new = clone $this;
$new->method = $value;
return $new;
}
public function options(array $value = []): self
{
$new = clone $this;
$new->options = $value;
return $new;
}
} @BoShurik am I correct? |
@samdark See https://github.com/yiisoft/form/blob/master/src/Widget/Form.php#L45, csrf passed as option creates a hidden input ;) |
It would be great to hide csrf protection under the $enableCsrfToken = ArrayHelper::remove($this->options, 'csrf', true); // boolean flag, not a token itself
if ($enableCsrfToken && strcasecmp($this->method, Method::POST) === 0) {
$hiddenInputs[] = Html::hiddenInput('_csrf', $this->createCsrfToken());
} |
But then form will depend on csrf and I think that is not good. |
It may be optional. On the other hand 90% of all csrf tokens will be used in forms so it improve DX |
I guess the conclusion is we leave Form the way it is, at least for now.
I'm an idiot. All I had to do was install Code works! |
Verified that it works well. Checking code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great and works well but I think we can do it simpler by reusing existing CsrfViewInjection
. Then withCsrf()
could be removed from ViewRenderer
and controllers.
I'm not quite sure how to do that. Also this is currently implemented for layout, not view (not sure if this is of any importance when done from Controllers). |
|
Gotcha 👍🏻 |
@vjik what do you think about the need for |
If I understand
I'll leave it up to you to decide to keep or remove those. |
Thank you! |
Useful code in case anyone want to use the jQuery (function() {
var DataMethod = {
init: function() {
this.links = document.querySelectorAll('a[data-method]');
this.registerEvents();
},
registerEvents: function() {
Array.from(this.links).forEach(function(l) {
l.addEventListener('click', DataMethod.render);
});
},
render: function(e) {
var el = this, httpMethod, form;
httpMethod = el.getAttribute('data-method').toUpperCase();
if (['POST', 'PUT', 'PATCH', 'DELETE'].indexOf(httpMethod) === -1) {
return;
}
if (el.hasAttribute('data-confirm') && !DataMethod.verifyConfirm(el)) {
e.preventDefault();
return false;
}
form = DataMethod.createForm(el);
form.submit();
e.preventDefault();
},
verifyConfirm: function(link) {
return confirm(link.getAttribute('data-confirm'));
},
createForm: function(link) {
var form = document.createElement('form');
DataMethod.setAttributes(form, {
method: 'POST',
action: link.getAttribute('href')
});
var csrfToken = document.querySelector("meta[name=csrf]").getAttribute('content');
if (!csrfToken) {
console.error('CSRF token not found!');
}
var inputToken = document.createElement('input');
DataMethod.setAttributes(inputToken, {
type: 'hidden',
name: '_csrf',
value: csrfToken
});
form.appendChild(inputToken);
document.body.appendChild(form);
return form;
},
setAttributes: function(el, attrs) {
for (var key in attrs) {
el.setAttribute(key, attrs[key]);
}
}
};
DataMethod.init();
})(); |
Currently leads toUnprocessable entity
, need to find source and fix it.