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()).
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. |
