Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Ipv6 Patch Laravel Package

murilochianfa/laravel-ipv6-patch

A small compatibility patch that adds IPv6 support to Laravel, fixing framework internals that assume IPv4 so requests, validation, and stored addresses handle IPv6 correctly.

View on GitHub
Deep Wiki
Context7

Getting Started

murilochianfa/laravel-ipv6-patch is a small compatibility layer that fills the IPv6 gaps Laravel's HTTP/validation stack leaves behind: normalizing $request->ip() / ips(), accepting bracketed and CIDR-form IPv6 in validation, and keeping trusted-proxy handling sane when X-Forwarded-For contains v6 addresses. Treat it as infrastructure, not a feature — you install it once, verify it, and then it should be invisible.

Minimal install and first check:

composer require murilochianfa/laravel-ipv6-patch
php artisan vendor:publish --tag=ipv6-patch-config   # check the exact tag in the README
php artisan config:cache

Read the published config/ipv6-patch.php (or similarly named) first — it tells you which patch groups exist (request IP resolution, validation, URL/host parsing, storage casts) and which are enabled by default. Then read the README's "what this patches" table and skim src/ to see which Laravel internals are being decorated; that list is what will break on a Laravel major upgrade, so it's the thing you want to know about before you ship.

Your first real use case is almost always "we're behind an ALB/Cloudflare and $request->ip() is returning ::ffff:203.0.113.7, an empty string, or something with a port on it." Add a throwaway route and test it from a v6-capable host:

Route::get('/whoami', fn (Request $r) => [
    'ip'   => $r->ip(),
    'ips'  => $r->ips(),
    'host' => $r->getHost(),
]);
curl -6 https://your-app.test/whoami
curl    https://your-app.test/whoami -H 'X-Forwarded-For: 2001:db8::1'

If ip() now comes back as a canonical, compressed, lowercase address with no brackets/port, the patch is doing its job and you can start wiring it into rate limiting, audit logs, and validation.

On v1.1.0: the release notes are only a compare/v1.0.0...v1.1.0 link, so read the diff before bumping. A 1.x minor should be additive (new patch targets, config keys, or normalization edge cases), with no breaking changes and no deprecations — but since there is no prior assessment to compare against, verify the composer.json constraint hasn't shifted (^10.0|^11.0 style) and skim the diff for changed default config values.

Implementation Patterns

Normalize at the edge, once. Do the address cleanup in the patch's middleware (registered after TrustProxies, before throttling/auth), and let everything downstream assume canonical form. If you re-normalize in each controller you will eventually miss one, and one missed spot is enough to split a rate-limit bucket or create a duplicate audit row.

Configure trusted proxies with real CIDRs, not *. In Laravel 11 this is bootstrap/app.php → ->withMiddleware(fn ($m) => $m->trustProxies(at: [...])); in 10 and below it's App\Http\Middleware\TrustProxies::$proxies. Include v6 ranges (2001:db8::/32, 2600:1f00::/24 for AWS, 2400:cb00::/32 for Cloudflare). The patch can strip ports and brackets from [2001:db8::1]:443, but it cannot guess which hops are yours.

Validation. Laravel's ip rule already accepts v6, so the patch's value is in the shapes people actually paste in:

'ip'      => ['required', 'ip'],                          // 2001:db8::1
'bracket' => ['required', 'ip'],                          // [2001:db8::1]  — may need the patched rule
'cidr'    => ['required', 'ip', 'ipv6'],                  // 2001:db8::/32

Use ipv6 explicitly when a field must not accept v4, and canonicalize before Rule::unique / Rule::in comparisons against a stored address column — string equality on IPs is the single most common source of "duplicate" tenant/allowlist rows.

Storage. Prefer Postgres inet or a VARBINARY(16)/BINARY(16) column plus a cast. If you must stay on VARCHAR, 45 chars covers every plain textual form (39 for full v6, 45 for IPv4-mapped) but not the bracketed form at 47 — normalize before persisting and you avoid the whole class of problem. A simple accessor/mutator pair that stores canonical compressed lowercase is usually enough:

protected function ipAddress(): Attribute
{
    return Attribute::make(
        set: fn (string $v) => inet_pton($v) === false ? $v : inet_ntop(inet_pton($v)),
    );
}

Rate limiting. This is where the patch pays for itself:

RateLimiter::for('api', fn (Request $r) => Limit::perMinute(60)->by($r->ip()));

Because a single user typically controls an entire /64, IP-keyed limits on v6 are effectively 2^64 free retries. Bucket by /64 for abuse cases (substr(inet_pton($ip), 0, 8) on the binary form) and keep the full address for logging. Do this deliberately — the patch normalizes the string, it will not make that policy decision for you.

Testing. Exercise both families, not just the default:

$this->withServerVariables(['REMOTE_ADDR' => '2001:db8::1']);
$this->withHeaders(['X-Forwarded-For' => '2001:db8::1, 10.0.0.1']);

::1 is what you get from artisan serve and most local setups, so feature tests that pass locally can be silently exercising the v6 path while production is v4 — assert on the exact normalized string in at least one test per route that keys on IP.

Integration points to wire up deliberately: audit/login logs, password-reset and 2FA allowlists, ThrottleRequests middleware, geo/ASN lookup jobs (pass the canonical string into the job payload, never the raw header), and any where('ip', ...) lookups in existing code.

Gotchas and Tips

  • IPv4-mapped addresses (::ffff:203.0.113.7) are the #1 footgun. Whether you collapse them to 203.0.113.7 or keep them, be consistent across request handling, storage, and rate limits — otherwise the same client gets two buckets and two audit identities depending on the hop.
  • Zone IDs break validation. fe80::1%eth0 fails filter_var(..., FILTER_VALIDATE_IP). If you accept addresses from operators (allowlists, peer config), strip %... explicitly rather than assuming the patch does.
  • Config caching. The patch registers via its service provider; if you edit the published config, php artisan config:cache — a stale cached config silently disables patch groups and sends you hunting for a bug that isn't there.
  • Middleware ordering. After TrustProxies, before ThrottleRequests and auth. If you're on Laravel 11's bootstrap/app.php, middleware registration is centralized — don't assume the package's auto-registration lands where you need it; check php artisan middleware:list (or dump the resolved kernel) once.
  • withoutMiddleware() in tests bypasses the patch. Any test that disables middleware is testing an IPv6 behavior that doesn't exist in production.
  • Debugging an unexpected IP is a two-liner before you blame the package: dump($request->server->get('REMOTE_ADDR'), $request->headers->get('X-Forwarded-For'), $request->getTrustedProxies()). The answer is nearly always proxy config or header order, not normalization.
  • Host/URL parsing is a separate concern from ip(). Bracketed hosts (https://[2001:db8::1]/) can still trip getHost()/url() helpers; if you generate absolute URLs from a v6 host, test that path independently.
  • Extension points. Prefer adding a Request::macro() or a custom validation rule over forking the package — forks of small patch packages rot fast against Laravel majors. If you do need to override behavior, check whether the relevant class is container-bound (and therefore swappable) before reaching for a fork.
  • Upgrade hygiene. Because this package patches framework internals rather than extending public APIs, pin it (^1.1) and run your IP-dependent tests on every Laravel minor bump. For v1.1.0 specifically: read the compare diff, confirm your composer.json framework constraint still matches, and re-run the v4/v6 test matrix — a 1.x minor shouldn't require code changes, but the diff is the only source of truth here since the release notes carry no description.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core