php 123 lines · 3 tabs

Authorize Post Editing With a Laravel Policy and Gate the Controller

Shared by codesnips Jul 2026
3 tabs
<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    public function before(User $user, string $ability)
    {
        if ($user->is_admin) {
            return true;
        }

        return null;
    }

    public function viewAny(User $user): bool
    {
        return true;
    }

    public function view(User $user, Post $post): bool
    {
        return $post->published || $user->id === $post->user_id;
    }

    public function create(User $user): bool
    {
        return $user->hasVerifiedEmail();
    }

    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}
3 files · php Explain with highlit

This snippet shows how Laravel separates who can do what from the controller logic that responds to a request, using a Policy class and the framework's authorization gate. The pattern keeps permission rules in one testable place instead of scattering if ($user->id === $post->user_id) checks across every action.

In PostPolicy, each ability maps to a method that receives the authenticated User and (optionally) the model instance. update and delete return true only when the acting user owns the post, while create allows any verified user and viewAny is open. The important subtlety is the before hook: it runs ahead of every other check and short-circuits the whole class when it returns a non-null value. Returning true for an administrator grants blanket access; returning null (not false) lets the specific ability methods decide, which is why before must not return false for normal users — doing so would deny everything.

AuthServiceProvider wires the model to its policy in the $policies map. Laravel's convention-based discovery often finds this automatically, but registering it explicitly documents the relationship and survives non-standard namespaces. Once mapped, calling authorize('update', $post) anywhere knows to consult PostPolicy@update.

PostController demonstrates two idioms. The constructor calls authorizeResource, which automatically maps each resource action to the matching policy ability, so edit/update require update and destroy requires delete without a line inside those methods. For actions that need explicit control, store still calls $this->authorize('create', Post::class) — passing the class name because no instance exists yet. When a check fails, Laravel throws an AuthorizationException that the framework renders as a 403, so the controller code never branches on permissions itself.

The trade-off is a small amount of indirection: a reader must open the policy to see the rule. In return, the rules become unit-testable in isolation, reusable from Blade via @can, and consistent across HTTP, console, and queued contexts. Reaching for a policy makes sense whenever a permission depends on the relationship between a user and a specific record; simpler, model-agnostic checks are better expressed as standalone Gate::define closures.


Related snips

ruby
payload = {
  sub: user.id,
  iss: 'https://auth.example.com',
  aud: 'codesnips-api',
  exp: 15.minutes.from_now.to_i,
  iat: Time.now.to_i,

JWT issuance and verification without common footguns

jwt authentication api
by Kai Nakamura 2 tabs
ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["form"]
  static values = { delay: { type: Number, default: 250 } }

Debounced live search with Stimulus + Turbo Streams

rails hotwire stimulus
by codesnips 4 tabs
bash
#!/usr/bin/env bash
set -euo pipefail

export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"

Secrets management with environment isolation and Vault

secrets-management vault environment-variables
by Kai Nakamura 1 tab
php
<?php

namespace App\Providers;

use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;

Laravel service container and dependency injection

laravel dependency-injection service-container
by Carlos Mendez 2 tabs
typescript
import crypto from 'node:crypto';
import jwt from 'jsonwebtoken';

export function signAccessToken(payload: object) {
  return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: '15m' });
}

JWT access + refresh token rotation (conceptual)

auth security node
by Mateo Rodriguez 1 tab

Share this code

Here's the card — post it anywhere.

Authorize Post Editing With a Laravel Policy and Gate the Controller — share card
Link copied