pdphilip/cf-request
cf-request is a Laravel package for accessing Cloudflare request data. It exposes headers like the connecting IP, visitor country, Ray ID, and other Cloudflare metadata, making edge information easy to use without manual header parsing.
Requirements: Laravel 11, 12 or 13, PHP 8.3+ (the CI matrix now targets 8.3/8.4 only). If you are still on Laravel 10, stay on the 3.0.x line — v3.1.0 drops it.
composer require pdphilip/cf-request
The service provider is auto-discovered. Look at these three places first, in order:
config/cf-request.php (publish with php artisan vendor:publish --tag=cf-request-config) — this is where the trust/verification behaviour is toggled. Read every key before going live; the defaults are sensible for a site that sits entirely behind Cloudflare, and wrong for a site that only partially does.^3.0 bump).First use case: you have an app behind the orange cloud, and $request->ip() is returning 172.70.x.x instead of your user's address, which breaks rate limiting, audit logs, and geo logic. Register the package's middleware (or call the resolver) in your bootstrap/app.php / HTTP kernel and confirm $request->ip() changes for a request arriving through Cloudflare. Do that in staging against a real proxied hostname — you cannot validate this from php artisan serve on localhost.
Middleware-first, not controller-first. Add the package's middleware to your global or web stack so every downstream consumer ($request->ip(), RateLimiter, Auth::attempt logging, request()->ips()) sees the corrected address. Resolving the IP ad hoc inside a controller means every other middleware still sees the Cloudflare edge.
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\Pdphilip\CfRequest\...\Middleware::class);
})
Ordering matters when you have your own proxy layer. If the request path is user → Cloudflare → ALB/Nginx → app, the package must run after whatever rewrites REMOTE_ADDR, and your TrustProxies configuration must not clobber the header the package reads. Decide which layer is authoritative and disable the other — two systems both "fixing" the client IP is the most common source of a silently wrong address.
Wrap the IP in a value object at the boundary. Rather than sprinkling $request->ip() everywhere, resolve once in middleware and attach a ClientIp object to the request attributes. When the package's resolution strategy changes (or you add a second CDN), you have one place to update and your domain code stays clean.
Cache the Cloudflare range fetch. The package performs outbound HTTP calls to retrieve Cloudflare's published ranges. In production, ensure that fetch is cached (and warmed), otherwise you have an external dependency in the hot path of every request — or worse, in the path of your first request after a deploy. Put a long TTL behind it and add a fallback: if the range list cannot be fetched, fail closed on verification but still log loudly.
Testing pattern — fake the network, not the resolver. In tests/, use Http::fake() for the range endpoint and set the Cloudflare headers (CF-Connecting-IP, CF-Ray, X-Forwarded-For) manually on the test request. Assert both the happy path (trusted range → real IP surfaced) and the adversarial one (spoofed CF-Connecting-IP from an untrusted IP → header ignored). The second test is the one that protects you.
Version-pinned CI. Adopt the new scripts to match the maintainer's matrix:
composer test:l11
composer test:l12
composer test:l13
composer test:all
Use test:all in your own CI if you support multiple Laravel majors, and pin laravel/framework per job via a matrix rather than testing whatever resolves.
Queue and job context. The resolved IP lives on the request, so it is gone by the time a job runs. Snapshot it into the job payload (or into the audit record) at dispatch time — do not re-resolve later.
^3.1 will not install on L10. If you maintain a package or an app split across majors, constrain to ^3.0.1 || ^3.1 and let Composer pick, or you will be fighting resolution errors in CI.composer.json require.php), but you are outside the supported/tested set — treat any bug there as yours to fix.^2 → ^3.0. This only bites you if you extend or publish the interactive artisan commands. If you have resources/views/vendor/ overrides or subclasses touching Omniterm APIs, re-test those commands after upgrading; a major bump there is not guaranteed to be signature-compatible.->body() change is a fix, not a behaviour change — getBody()->getContents() and ->body() both return the response body string. But it is a signal: if you mock the HTTP client in tests with a hand-rolled ResponseInterface, your fake must support body(). Http::fake() handles this correctly; a manual Mockery double of PSR-7 may not.CF-Connecting-IP is just a header — anyone can send it. Verification against Cloudflare's ranges is what makes it trustworthy. Never enable the "just trust the header" mode on a hostname that is reachable directly (grey-clouded DNS record, origin IP leaked in DNS history, direct-to-IP scans). If the origin is reachable, the header is attacker-controlled.TrustProxies accordingly.RateLimiter::for() keys on $request->ip() and the middleware is not applied to that route group, all your users share one bucket keyed on the edge IP. Add an assertion in tests that the key differs between two simulated clients.$request->ips() (the full chain, not just the resolved one) plus REMOTE_ADDR for a day when rolling this out. The resolved IP alone hides why it resolved that way, and the chain makes proxy-ordering bugs obvious.@phpstan-ignore or a baseline entry covering the old getBody()->getContents() call path, remove it or the baseline entry will start reporting as unused.How can I help you explore Laravel packages today?