<?php
namespace App\Policies;
use App\Models\Article;
use App\Models\User;
class ArticlePolicy
{
public function before(User $user, string $ability)
{
if ($user->is_admin) {
return true;
}
return null;
}
public function update(User $user, Article $article): bool
{
return $user->id === $article->user_id
|| $user->hasRole('editor');
}
public function delete(User $user, Article $article): bool
{
return $user->id === $article->user_id;
}
}
<?php
namespace App\Providers;
use App\Models\Article;
use App\Policies\ArticlePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
Article::class => ArticlePolicy::class,
];
public function boot(): void
{
$this->registerPolicies();
}
}
<?php
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function edit(Article $article)
{
$this->authorize('update', $article);
return view('articles.edit', compact('article'));
}
public function update(Request $request, Article $article)
{
$this->authorize('update', $article);
$data = $request->validate([
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
]);
$article->update($data);
return redirect()
->route('articles.show', $article)
->with('status', 'Article updated.');
}
}
@extends('layouts.app')
@section('content')
<article class="prose">
<h1>{{ $article->title }}</h1>
<p class="meta">by {{ $article->author->name }}</p>
<div class="body">
{!! nl2br(e($article->body)) !!}
</div>
@can('update', $article)
<a href="{{ route('articles.edit', $article) }}" class="btn btn-primary">
Edit article
</a>
@endcan
</article>
@endsection
This snippet shows the canonical Laravel way to answer a single question — may this user edit this article? — once, in a Policy, and then reuse that answer everywhere. Centralizing authorization logic in a policy class avoids the classic bug where the controller allows an action the view hides, or vice versa: both derive their decision from the same method.
The ArticlePolicy file defines per-action methods (update, delete) that receive the authenticated User and the Article instance. Each returns a boolean, and Laravel's authorization layer treats truthy as allow. The update method permits either the article's author ($article->user_id === $user->id) or anyone with an editor role, which demonstrates how business rules live in one place. The before hook is a common escape hatch: returning true for admins short-circuits every ability so admins never get blocked; note that it returns null (not false) for non-admins so the specific methods still run — returning false there would deny everyone.
The AuthServiceProvider file wires the model to its policy in the $policies map so Gate and helpers like $user->can() resolve automatically. This registration is what lets the framework infer the right policy from the model class alone.
In ArticleController, the edit and update actions call $this->authorize('update', $article). That helper throws an AuthorizationException (rendered as a 403) when the policy denies, so no unauthorized write reaches the database. The edit action also loads the form; because authorization runs first, a tampered URL cannot open the editor for someone else's article. Route-model binding supplies the $article, keeping the controller thin.
Finally, articles/show.blade.php uses the @can('update', $article) directive to render the Edit button only when the same policy permits it. This is purely cosmetic hardening — the server-side authorize call is the real gate — but it produces a coherent UI. The key takeaway is layering: policy defines the rule, controller enforces it, and Blade reflects it, all from one source of truth. A pitfall to avoid is enforcing only in Blade; hidden buttons are not security.
Related snips
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
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
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
#!/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
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
Laravel service container and dependency injection
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
Share this code
Here's the card — post it anywhere.