oihana / php-middleware
Composable PHP middleware helpers โ security headers (HSTS, CSP, X-Frame-Options, Referrer-Policy, X-Content-Type-Options), CORS with preflight, CSRF, request-id, maintenance mode, rate limiting. PSR-7 compatible, zero magic strings.
Requires
- php: >=8.4
- oihana/php-enums: dev-main
- oihana/php-http: dev-main
- psr/http-message: ^2.0
Requires (Dev)
- nunomaduro/collision: ^8.8
- phpdocumentor/shim: ^3.8
- phpunit/phpunit: ^12
- slim/psr7: ^1.7
Suggests
- oihana/php-memcached: Provides MemcachedRateLimitStore (production-grade backend for enforceRateLimit()).
Provides
None
Conflicts
None
Replaces
None
README
Composable PHP HTTP middleware helpers.
Part of the Oihana PHP ecosystem, this package ships procedural helpers for HTTP security headers, CORS, CSRF, request-id propagation, maintenance mode, fixed-window rate limiting, observability, content negotiation, distributed tracing (W3C Trace Context), RFC 9457 Problem Details, webhook signature verification, request-defense guards, HTTP caching & conditional requests, and pagination headers โ PSR-7 compatible, zero magic strings.
๐ Documentation
Full API reference (generated with phpDocumentor): https://bcommebois.github.io/oihana-php-middleware
User guides (FR + EN) live under wiki/.
๐ฆ Installation
Requires PHP 8.4+. Install via Composer:
composer require oihana/php-middleware
โจ What you can do
Security headers
withSecurityHeaders()โ single helper to apply HSTS,Content-Security-Policy,X-Frame-Options,Referrer-Policy,X-Content-Type-Options, the three Cross-Origin policies (COOP / COEP / CORP) andPermissions-Policyto a PSR-7Responsein one call. Typed values viaReferrerPolicy,FrameOptions,CrossOriginOpenerPolicy,CrossOriginEmbedderPolicy,CrossOriginResourcePolicyandPermissionsPolicyFeatureenums โ no magic strings.buildCspHeader()โ compose aContent-Security-Policyvalue from an associative array of directives.CspDirectiveenum exposes the canonical directive names.buildPermissionsPolicyHeader()โ compose aPermissions-Policyvalue with a smart allowlist API (false,true/'*','self', single origin or array โselfstays a token, origins auto-quoted).withDefaultSecurityBaseline()โ opinionated alias ofwithSecurityHeaders()shipping a "safe-for-most-apps" baseline (HSTS 1 year,X-Frame-Options: DENY, nosniff,Referrer-Policy: strict-origin-when-cross-origin, COOP / CORPsame-origin) with caller-supplied overrides merged on top.
CORS
applyCorsHeaders()โ origin allowlist with configurable methods, headers, exposed headers, credentials and max-age. Handles the preflightOPTIONSrequest automatically. Defensive defaults: no*whencredentials = true,Vary: Originadded when the allowlist is dynamic.isCorsRequest()/isCorsPreflight()โ pure predicates so middlewares can short-circuit on same-origin requests or detect a preflight without spelling out the underlying header names.
CSRF
generateCsrfToken()/verifyCsrfToken()โ stateless signed double-submit pattern. Wire format<id>.<exp>.<sig>with a 128-bit base64url random<id>, an absolute expiry timestamp and a base64url HMAC-SHA256 signature keyed by your secret. Optional TTL. Constant-time verification โ never throws on bad input, returnsbool.CsrfFieldenum ships the conventionalcookieandheadernames.
Request ID
requestIdFromRequest()โ readsX-Request-Idfrom the incoming request and returns it when it passes a conservative shape check (1 to 128 chars, URL-safe alphabet), otherwise generates a fresh 128-bit base64url identifier. Defense-in-depth against log-pollution attacks via a forged incoming header.withRequestIdHeader()โ stamps the request ID on the response (PSR-7 immutable).RequestIdFieldenum โHEADER_NAME(X-Request-Id) andATTRIBUTE_NAME(requestId) for wiring the propagation through the middleware chain.
Maintenance mode
respondMaintenanceMode()โ turns a PSR-7 response into a clean503 Service Unavailablewith optionalRetry-Afterheader (acceptsintdelta-seconds,DateTimeInterfaceformatted as IMF-fixdate, or rawstring) and optional body.MaintenanceOptionenum carries the option keys.
Rate limiting
enforceRateLimit()โ fixed-window rate-limit enforcement on PSR-7 requests. Identity resolved verbatim fromKEY(string), via callablefn(Request): string, or by fallback to the client IP. Returns an immutableRateLimitDecision(allowed,limit,remaining,reset,retryAfter).withRateLimitHeaders()โ stampsLimit / Remaining / Reseton the response from a decision, plusRetry-Afterwhen blocked. Defaults to the legacyX-RateLimit-*family, opt-in to the RFC 9421 draftRateLimit-*family.RateLimitStoreinterface โ single atomic methodincrement(string $key, int $window): int. ShippedInMemoryRateLimitStore(process-local, for tests and CLI tools) ; production-gradeMemcachedRateLimitStoreinoihana/php-memcached.
Observability
withResponseTime()โ stamps the elapsed processing time on the response. Two output formats: de-factoX-Response-Time: 42.50ms(default) or W3CServer-Timing: total;dur=42.50(opt-in viaResponseTimeOption::USE_SERVER_TIMING).
Content negotiation
negotiateMimeType()โ thin PSR-7 wrapper overoihana\http\helpers\negotiation\negotiate()to select the best server-side MIME type from the clientAcceptheader. Honours RFC 7231 quality values, standard wildcards (universal andtype/*) andq=0explicit refusals.negotiateCharset()/negotiateEncoding()/negotiateLanguage()โ sibling wrappers selecting the best charset, content-encoding or language from the matchingAccept-Charset/Accept-Encoding/Accept-Languagerequest header, with the same quality-value semantics.
Distributed tracing
traceContextFromRequest()/withTracingAttribute()/withTraceparentResponseHeader()/parseTraceparent()โ W3C Trace Context propagation. Extract or generate atraceparent, expose theTraceContextvalue object as a request attribute, and echo the canonicaltraceparentback on the response so a single user request can be reconstructed end-to-end across services from your log aggregator.TracingFieldenum carries the wiring names.
Problem Details
respondProblemDetails()โ RFC 9457 standardised error responses (application/problem+json) built from aProblemvalue object: typed standard fields (type,title,status,detail,instance) plus arbitrary extension members.ProblemFieldenum carries the field names.
Webhook signature verification
verifyWebhookSignature()โ verifies the simple-HMAC signature pattern shared by GitHub, Slack, Shopify, Twilio and SendGrid. Constant-time comparison, configurable algorithm / header prefix / encoding via theWebhookSignatureOptionenum.
Request defense
enforceMaxBodySize()/enforceTrustedHosts()โ pre-parsing guards that reject obviously-bad requests before the application has to handle them: oversized request bodies (declaredContent-Lengthor streamed) and Host-header attacks (allowlist with wildcard-subdomain support, port stripping, fail-open on an empty allowlist).
HTTP caching & conditional requests
buildCacheControl()โ compose aCache-Controlvalue from typedCacheDirectivenames (RFC 9111 / 5861 / 8246), catching themax_agevsmax-agetypo class that silently disables caching.isNotModified()/respondNotModified()โ evaluate RFC 9110 conditional requests (ETag/If-None-Matchweak comparison,If-Modified-Since) and emit a canonical304 Not Modifiedresponse.
Pagination
withPaginationHeaders()โ stamps the RFC 5988 / RFC 8288Linkheader (rel="first|prev|next|last") and the de-factoX-Total-Countheader from aPaginationLinksvalue object, keeping the response body pure data (GitHub-style API pagination).
Under the hood
- Pure PSR-7 โ no framework lock-in. Works with Slim, Laravel, Symfony HTTP Foundation (via PSR-7 bridge), Hyperf, RoadRunner, etc.
- Built on
oihana/php-httpprimitives (IP detection, content negotiation, etc.) andoihana/php-enumstyped HTTP constants (HttpHeader,HttpMethod,HttpStatusCode,AuthScheme, โฆ).
โ Running tests
Run all tests:
composer test
Measure coverage (requires Xdebug or PCOV):
composer coverage # text + Clover + HTML under build/coverage/ composer coverage:md # readable Markdown summary (build/coverage/COVERAGE.md)
See CONTRIBUTING.md for the full testing & coverage workflow.
๐ ๏ธ Generate the documentation
composer doc
๐งพ License
Licensed under the Mozilla Public License 2.0 (MPLโ2.0).
๐ค About the author
- Author: Marc ALCARAZ (aka eKameleon)
- Email:
marc@ooop.fr - Website:
https://www.ooop.fr
๐ Related packages
| Package | Description |
|---|---|
oihana/php-http |
Composable PHP HTTP primitives (client IP detection, signed URLs, cookies, content negotiation, โฆ) consumed by this library. |
oihana/php-enums |
Typed HTTP constants (HttpHeader, HttpMethod, HttpStatusCode, AuthScheme, โฆ). |
oihana/php-memcached |
Production-grade MemcachedRateLimitStore implementing this package's RateLimitStore interface. |
