セキュリティ
Livewireアプリケーションを安全にし、アプリケーションの脆弱性を露出させないようにすることが重要です。 Livewireには多くのケースを処理するための内部セキュリティ機能がありますが、コンポーネントを安全に保つためには、アプリケーションコードに依存する場合があります。It's important to make sure your Livewire apps are secure and don't expose any application vulnerabilities. Livewire has internal security features to handle many cases, however, there are times when it's up to your application code to keep your components secure.
アクションパラメータの認証Authorizing action parameters
Livewireアクションは非常に強力ですが、Livewireアクションに渡されるパラメータはクライアント上で変更可能であり、信頼できないユーザー入力として扱う必要があります。Livewire actions are extremely powerful, however, any parameters passed to Livewire actions are mutable on the client and should be treated as un-trusted user input.
おそらく、Livewireで最も一般的なセキュリティ上の落とし穴は、データベースに変更を永続化する前に、Livewireアクション呼び出しを検証および認証しないことです。Arguably the most common security pitfall in Livewire is failing to validate and authorize Livewire action calls before persisting changes to the database.
以下は、認証の欠如によるセキュリティ上の脆弱性の例です。Here is an example of an insecurity resulting from a lack of authorization:
<?php
use App\Models\Post;
use Livewire\Component;
class ShowPost extends Component
{
// ...
public function delete($id)
{
// INSECURE! (安全ではありません!)
$post = Post::find($id);
$post->delete();
}
}
<button wire:click="delete({{ $post->id }})">Delete Post</button>
上記の例が安全でない理由は、悪意のあるユーザーがwire:click="delete(...)"
をブラウザで変更して、任意のPost IDを渡すことができるためです。The reason the above example is insecure is that wire:click="delete(...)"
can be modified in the browser to pass ANY post ID a malicious user wishes.
アクションパラメータ(この例の $id
など)は、ブラウザからの信頼できない入力と同じように扱う必要があります。Action parameters (like $id
in this case) should be treated the same as any untrusted input from the browser.
したがって、このアプリケーションを安全に保ち、ユーザーが別のユーザーの投稿を削除できないようにするには、delete()
アクションに認証を追加する必要があります。Therefore, to keep this application secure and prevent a user from deleting another user's post, we must add authorization to the delete()
action.
まず、次のコマンドを実行して、PostモデルのLaravel Policyを作成しましょう。First, let's create a Laravel Policy[https://laravel.com/docs/authorization#creating-policies] for the Post model by running the following command:
php artisan make:policy PostPolicy --model=Post
上記のコマンドを実行すると、新しいPolicyが app/Policies/PostPolicy.php
内に作成されます。次に、以下のように delete
メソッドでその内容を更新できます。After running the above command, a new Policy will be created inside app/Policies/PostPolicy.php
. We can then update its contents with a delete
method like so:
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
/**
* Determine if the given post can be deleted by the user.
*/
public function delete(?User $user, Post $post): bool
{
return $user?->id === $post->user_id;
}
}
これで、Livewireコンポーネントから $this->authorize()
メソッドを使用して、ユーザーが投稿を削除する前に所有していることを確認できます。Now, we can use the $this->authorize()
method from the Livewire component to ensure the user owns the post before deleting it:
public function delete($id)
{
$post = Post::find($id);
// If the user doesn't own the post, (ユーザーが投稿を所有していない場合、)
// an AuthorizationException will be thrown... (AuthorizationExceptionがスローされます...)
$this->authorize('delete', $post); // [tl! highlight]
$post->delete();
}
参考資料:Further reading:
- Laravel GatesLaravel Gates[https://laravel.com/docs/authorization#gates]
- Laravel PoliciesLaravel Policies[https://laravel.com/docs/authorization#creating-policies]
パブリックプロパティの認証Authorizing public properties
アクションパラメータと同様に、Livewireのパブリックプロパティは、ユーザーからの信頼できない入力として扱う必要があります。Similar to action parameters, public properties in Livewire should be treated as un-trusted input from the user.
以下は、投稿の削除に関する上記の例と同じものを、別の方法で安全でない方法で記述したものです。Here is the same example from above about deleting a post, written insecurely in a different manner:
<?php
use App\Models\Post;
use Livewire\Component;
class ShowPost extends Component
{
public $postId;
public function mount($postId)
{
$this->postId = $postId;
}
public function delete()
{
// INSECURE! (安全ではありません!)
$post = Post::find($this->postId);
$post->delete();
}
}
<button wire:click="delete">Delete Post</button>
ご覧のとおり、wire:click
から delete
メソッドに $postId
をパラメータとして渡す代わりに、Livewireコンポーネントのパブリックプロパティとして保存しています。As you can see, instead of passing the $postId
as a parameter to the delete
method from wire:click
, we are storing it as a public property on the Livewire component.
このアプローチの問題は、悪意のあるユーザーがページに次のようなカスタム要素を挿入できることです。The problem with this approach is that any malicious user can inject a custom element onto the page such as:
<input type="text" wire:model="postId">
これにより、彼らは「Delete Post」を押す前に $postId
を自由に修正できます。 delete
アクションは $postId
の値を認証しないため、ユーザーは自分が所有しているかどうかにかかわらず、データベース内の任意の投稿を削除できるようになります。This would allow them to freely modify the $postId
before pressing "Delete Post". Because the delete
action doesn't authorize the value of $postId
, the user can now delete any post in the database, whether they own it or not.
このリスクから保護するためには、2つの可能な解決策があります。To protect against this risk, there are two possible solutions:
モデルプロパティの使用Using model properties
パブリックプロパティを設定する場合、Livewireは文字列や整数などのプレーンな値とは異なり、モデルを特別に扱います。 このため、コンポーネントのプロパティとして投稿モデル全体を保存すると、LivewireはIDが改ざんされないようにします。When setting public properties, Livewire treats models differently than plain values such as strings and integers. Because of this, if we instead store the entire post model as a property on the component, Livewire will ensure the ID is never tampered with.
以下は、単純な $postId
プロパティの代わりに $post
プロパティを保存する例です。Here is an example of storing a $post
property instead of a simple $postId
property:
<?php
use App\Models\Post;
use Livewire\Component;
class ShowPost extends Component
{
public Post $post;
public function mount($postId)
{
$this->post = Post::find($postId);
}
public function delete()
{
$this->post->delete();
}
}
<button wire:click="delete">Delete Post</button>
このコンポーネントは、悪意のあるユーザーが $post
プロパティを別のEloquentモデルに変更する方法がないため、安全になりました。This component is now secured because there is no way for a malicious user to change the $post
property to a different Eloquent model.
プロパティのロックLocking the property
プロパティが不要な値に設定されるのを防ぐ別の方法は、ロックされたプロパティ(和訳)を使用することです。 プロパティのロックは、#[Locked]
属性を適用することで行われます。 ユーザーがこの値を改ざんしようとすると、エラーがスローされます。Another way to prevent properties from being set to unwanted values is to use locked properties[https://livewire.laravel.com/docs/locked]. Locking properties is done by applying the #[Locked]
attribute. Now if users attempt to tamper with this value an error will be thrown.
Locked属性を持つプロパティはバックエンドで変更できますが、信頼できないユーザー入力がLivewire関数に渡されないように注意する必要があります。Note that properties with the Locked attribute can still be changed in the back-end, so care still needs to taken that untrusted user input is not passed to the property in your own Livewire functions.
<?php
use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\Locked;
class ShowPost extends Component
{
#[Locked] // [tl! highlight]
public $postId;
public function mount($postId)
{
$this->postId = $postId;
}
public function delete()
{
$post = Post::find($this->postId);
$post->delete();
}
}
プロパティの認証Authorizing the property
モデルプロパティの使用が望ましくない場合は、delete
アクション内で投稿の削除を手動で認証することもできます。If using a model property is undesired in your scenario, you can of course fall-back to manually authorizing the deletion of the post inside the delete
action:
<?php
use App\Models\Post;
use Livewire\Component;
class ShowPost extends Component
{
public $postId;
public function mount($postId)
{
$this->postId = $postId;
}
public function delete()
{
$post = Post::find($this->postId);
$this->authorize('delete', $post); // [tl! highlight]
$post->delete();
}
}
<button wire:click="delete">Delete Post</button>
これで、悪意のあるユーザーは $postId
の値を自由に修正できますが、delete
アクションが呼び出されると、ユーザーが投稿を所有していない場合、$this->authorize()
は AuthorizationException
をスローします。Now, even though a malicious user can still freely modify the value of $postId
, when the delete
action is called, $this->authorize()
will throw an AuthorizationException
if the user does not own the post.
参考資料:Further reading:
- Laravel GatesLaravel Gates[https://laravel.com/docs/authorization#gates]
- Laravel PoliciesLaravel Policies[https://laravel.com/docs/authorization#creating-policies]
ミドルウェアMiddleware
Livewireコンポーネントが、次のようなルートレベルのAuthorization Middlewareを含むページにロードされると:When a Livewire component is loaded on a page containing route-level Authorization Middleware[https://laravel.com/docs/authorization#via-middleware], like so:
Route::get('/post/{post}', App\Livewire\UpdatePost::class)
->middleware('can:update,post'); // [tl! highlight]
Livewireは、これらのミドルウェアが後続のLivewireネットワークリクエストに再度適用されるようにします。 これは、Livewireのコアで「永続ミドルウェア」と呼ばれます。Livewire will ensure those middlewares are re-applied to subsequent Livewire network requests. This is referred to as "Persistent Middleware" in Livewire's core.
永続ミドルウェアは、初期ページロード後に認証ルールまたはユーザー権限が変更されたシナリオから保護します。Persistent middleware protects you from scenarios where the authorization rules or user permissions have changed after the initial page-load.
そのようなシナリオのより詳細な例を次に示します。Here's a more in-depth example of such a scenario:
Route::get('/post/{post}', App\Livewire\UpdatePost::class)
->middleware('can:update,post'); // [tl! highlight]
<?php
use App\Models\Post;
use Livewire\Component;
use Livewire\Attributes\Validate;
class UpdatePost extends Component
{
public Post $post;
#[Validate('required|min:5')]
public $title = '';
public $content = '';
public function mount()
{
$this->title = $this->post->title;
$this->content = $this->post->content;
}
public function update()
{
$this->post->update([
'title' => $this->title,
'content' => $this->content,
]);
}
}
ご覧のとおり、can:update,post
ミドルウェアはルートレベルで適用されます。 これは、投稿を更新する権限を持たないユーザーはページを表示できないことを意味します。As you can see, the can:update,post
middleware is applied at the route-level. This means that a user who doesn't have permission to update a post cannot view the page.
ただし、ユーザーが次のことを行うシナリオを考えてください。However, consider a scenario where a user:
- ページをロードするLoads the page
- ページロード後に更新権限を失うLoses permission to update after the page loads
- 権限を失った後に投稿を更新しようとするTries updating the post after losing permission
Livewireが既にページを正常にロードしているため、こう自問するかもしれません。「Livewireがポストを更新するために後続のリクエストを行うとき、can:update,post
ミドルウェアは再度適用されるのか?あるいは、権限のないユーザーがポストを正常に更新できるようになるのか?」Because Livewire has already successfully loaded the page you might ask yourself: "When Livewire makes a subsequent request to update the post, will the can:update,post
middleware be re-applied? Or instead, will the un-authorized user be able to update the post successfully?"
Livewireには、元のエンドポイントからミドルウェアを再度適用するための内部メカニズムがあるため、このシナリオでは保護されています。Because Livewire has internal mechanisms to re-apply middleware from the original endpoint, you are protected in this scenario.
永続的なミドルウェアの設定Configuring persistent middleware
デフォルトでは、Livewireは以下のミドルウェアをネットワークリクエスト全体で永続化します。By default, Livewire persists the following middleware across network requests:
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Laravel\Jetstream\Http\Middleware\AuthenticateSession::class,
\Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\RedirectIfAuthenticated::class,
\Illuminate\Auth\Middleware\Authenticate::class,
\Illuminate\Auth\Middleware\Authorize::class,
上記のいずれかのミドルウェアが最初のページロードに適用されている場合、それらは永続化され(再度適用され)、将来のネットワークリクエストに適用されます。If any of the above middlewares are applied to the initial page-load, they will be persisted (re-applied) to any future network requests.
ただし、アプリケーションからカスタムミドルウェアを最初のページロードに適用し、それをLivewireリクエスト間で永続化したい場合は、次のようにアプリケーションのService Providerからこのリストに追加する必要があります。However, if you are applying a custom middleware from your application on the initial page-load, and want it persisted between Livewire requests, you will need to add it to this list from a Service Provider[https://laravel.com/docs/providers#main-content] in your app like so:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Livewire;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Livewire::addPersistentMiddleware([ // [tl! highlight:2]
App\Http\Middleware\EnsureUserHasRole::class,
]);
}
}
Livewireコンポーネントが、アプリケーションのEnsureUserHasRole
ミドルウェアを使用するページにロードされる場合、それは永続化され、そのLivewireコンポーネントへの将来のネットワークリクエストに再度適用されます。If a Livewire component is loaded on a page that uses the EnsureUserHasRole
middleware from your application, it will now be persisted and re-applied to any future network requests to that Livewire component.
[!warning] Middleware arguments are not supported Livewire currently doesn't support middleware arguments for persistent middleware definitions.
Warning! ミドルウェアの引数はサポートされていません Livewireは現在、永続的なミドルウェア定義のミドルウェア引数をサポートしていません。// Bad... Livewire::addPersistentMiddleware(AuthorizeResource::class.':admin'); // Good... Livewire::addPersistentMiddleware(AuthorizeResource::class);
グローバルなLivewireミドルウェアの適用Applying global Livewire middleware
あるいは、すべてのLivewire更新ネットワークリクエストに特定のミドルウェアを適用したい場合は、任意のミドルウェアを使用して独自のLivewire更新ルートを登録することでそれを行うことができます。Alternatively, if you wish to apply specific middleware to every single Livewire update network request, you can do so by registering your own Livewire update route with any middleware you wish:
Livewire::setUpdateRoute(function ($handle) {
return Route::post('/livewire/update', $handle)
->middleware(App\Http\Middleware\LocalizeViewPaths::class);
});
サーバへのLivewire AJAX/fetchリクエストはすべて上記のエンドポイントを使用し、コンポーネントの更新を処理する前にLocalizeViewPaths
ミドルウェアを適用します。Any Livewire AJAX/fetch requests made to the server will use the above endpoint and apply the LocalizeViewPaths
middleware before handling the component update.
Installation page(和訳)で、更新ルートのカスタマイズについて詳しく学んでください。Learn more about customizing the update route on the Installation page[https://livewire.laravel.com/docs/installation#configuring-livewires-update-endpoint].
スナップショットのチェックサムSnapshot checksums
すべてのLivewireリクエストの間で、Livewireコンポーネントのスナップショットが取得され、ブラウザに送信されます。このスナップショットは、次のサーバラウンドトリップ中にコンポーネントを再構築するために使用されます。Between every Livewire request, a snapshot is taken of the Livewire component and sent to the browser. This snapshot is used to re-build the component during the next server round-trip.
Livewireスナップショットの詳細については、Hydrationドキュメントを参照してください。(和訳)Learn more about Livewire snapshots in the Hydration documentation.[https://livewire.laravel.com/docs/hydration#the-snapshot]
fetchリクエストはブラウザで傍受および改ざんされる可能性があるため、Livewireは各スナップショットの「チェックサム」を生成し、それと一緒に送信します。Because fetch requests can be intercepted and tampered with in a browser, Livewire generates a "checksum" of each snapshot to go along with it.
このチェックサムは、次のネットワークリクエストで、スナップショットがまったく変更されていないことを確認するために使用されます。This checksum is then used on the next network request to verify that the snapshot hasn't changed in any way.
Livewireがチェックサムの不一致を検出すると、CorruptComponentPayloadException
がスローされ、リクエストは失敗します。If Livewire finds a checksum mismatch, it will throw a CorruptComponentPayloadException
and the request will fail.
これにより、悪意のある改ざんのあらゆる形態から保護され、そうでない場合はユーザーに無関係のコードを実行または変更する機能を与えることになります。This protects against any form of malicious tampering that would otherwise result in granting users the ability to execute or modify unrelated code.