├── CookieServiceProvider.php ├── LICENSE.md ├── Middleware ├── AddQueuedCookiesToResponse.php └── EncryptCookies.php ├── composer.json ├── CookieValuePrefix.php └── CookieJar.php /CookieServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('cookie', function ($app) { 17 | $config = $app->make('config')->get('session'); 18 | 19 | return (new CookieJar)->setDefaultPathAndDomain( 20 | $config['path'], $config['domain'], $config['secure'], $config['same_site'] ?? null 21 | ); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Taylor Otwell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Middleware/AddQueuedCookiesToResponse.php: -------------------------------------------------------------------------------- 1 | cookies = $cookies; 25 | } 26 | 27 | /** 28 | * Handle an incoming request. 29 | * 30 | * @param \Illuminate\Http\Request $request 31 | * @param \Closure $next 32 | * @return mixed 33 | */ 34 | public function handle($request, Closure $next) 35 | { 36 | $response = $next($request); 37 | 38 | foreach ($this->cookies->getQueuedCookies() as $cookie) { 39 | $response->headers->setCookie($cookie); 40 | } 41 | 42 | return $response; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "illuminate/cookie", 3 | "description": "The Illuminate Cookie package.", 4 | "license": "MIT", 5 | "homepage": "https://laravel.com", 6 | "support": { 7 | "issues": "https://github.com/laravel/framework/issues", 8 | "source": "https://github.com/laravel/framework" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Taylor Otwell", 13 | "email": "taylor@laravel.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.3", 18 | "ext-hash": "*", 19 | "illuminate/collections": "^13.0", 20 | "illuminate/contracts": "^13.0", 21 | "illuminate/macroable": "^13.0", 22 | "illuminate/support": "^13.0", 23 | "symfony/http-foundation": "^7.4.0|^8.0.0", 24 | "symfony/http-kernel": "^7.4.0|^8.0.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Illuminate\\Cookie\\": "" 29 | } 30 | }, 31 | "extra": { 32 | "branch-alias": { 33 | "dev-master": "13.0.x-dev" 34 | } 35 | }, 36 | "config": { 37 | "sort-packages": true 38 | }, 39 | "minimum-stability": "dev" 40 | } 41 | -------------------------------------------------------------------------------- /CookieValuePrefix.php: -------------------------------------------------------------------------------- 1 | getPathAndDomain($path, $domain, $secure, $sameSite); 67 | 68 | $time = ($minutes == 0) ? 0 : $this->availableAt($minutes * 60); 69 | 70 | return new Cookie($name, $value, $time, $path, $domain, $secure, $httpOnly, $raw, $sameSite); 71 | } 72 | 73 | /** 74 | * Create a cookie that lasts "forever" (400 days). 75 | * 76 | * @param string $name 77 | * @param string $value 78 | * @param string|null $path 79 | * @param string|null $domain 80 | * @param bool|null $secure 81 | * @param bool $httpOnly 82 | * @param bool $raw 83 | * @param string|null $sameSite 84 | * @return \Symfony\Component\HttpFoundation\Cookie 85 | */ 86 | public function forever($name, $value, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null) 87 | { 88 | return $this->make($name, $value, 576000, $path, $domain, $secure, $httpOnly, $raw, $sameSite); 89 | } 90 | 91 | /** 92 | * Expire the given cookie. 93 | * 94 | * @param string $name 95 | * @param string|null $path 96 | * @param string|null $domain 97 | * @return \Symfony\Component\HttpFoundation\Cookie 98 | */ 99 | public function forget($name, $path = null, $domain = null) 100 | { 101 | return $this->make($name, null, -2628000, $path, $domain); 102 | } 103 | 104 | /** 105 | * Determine if a cookie has been queued. 106 | * 107 | * @param string $key 108 | * @param string|null $path 109 | * @return bool 110 | */ 111 | public function hasQueued($key, $path = null) 112 | { 113 | return ! is_null($this->queued($key, null, $path)); 114 | } 115 | 116 | /** 117 | * Get a queued cookie instance. 118 | * 119 | * @param string $key 120 | * @param mixed $default 121 | * @param string|null $path 122 | * @return \Symfony\Component\HttpFoundation\Cookie|null 123 | */ 124 | public function queued($key, $default = null, $path = null) 125 | { 126 | $queued = Arr::get($this->queued, $key, $default); 127 | 128 | if ($path === null) { 129 | return Arr::last($queued, null, $default); 130 | } 131 | 132 | return Arr::get($queued, $path, $default); 133 | } 134 | 135 | /** 136 | * Queue a cookie to send with the next response. 137 | * 138 | * @param mixed ...$parameters 139 | * @return void 140 | */ 141 | public function queue(...$parameters) 142 | { 143 | if (isset($parameters[0]) && $parameters[0] instanceof Cookie) { 144 | $cookie = $parameters[0]; 145 | } else { 146 | $cookie = $this->make(...array_values($parameters)); 147 | } 148 | 149 | if (! isset($this->queued[$cookie->getName()])) { 150 | $this->queued[$cookie->getName()] = []; 151 | } 152 | 153 | $this->queued[$cookie->getName()][$cookie->getPath()] = $cookie; 154 | } 155 | 156 | /** 157 | * Queue a cookie to expire with the next response. 158 | * 159 | * @param string $name 160 | * @param string|null $path 161 | * @param string|null $domain 162 | * @return void 163 | */ 164 | public function expire($name, $path = null, $domain = null) 165 | { 166 | $this->queue($this->forget($name, $path, $domain)); 167 | } 168 | 169 | /** 170 | * Remove a cookie from the queue. 171 | * 172 | * @param string $name 173 | * @param string|null $path 174 | * @return void 175 | */ 176 | public function unqueue($name, $path = null) 177 | { 178 | if ($path === null) { 179 | unset($this->queued[$name]); 180 | 181 | return; 182 | } 183 | 184 | unset($this->queued[$name][$path]); 185 | 186 | if (empty($this->queued[$name])) { 187 | unset($this->queued[$name]); 188 | } 189 | } 190 | 191 | /** 192 | * Get the path and domain, or the default values. 193 | * 194 | * @param string $path 195 | * @param string|null $domain 196 | * @param bool|null $secure 197 | * @param string|null $sameSite 198 | * @return array 199 | */ 200 | protected function getPathAndDomain($path, $domain, $secure = null, $sameSite = null) 201 | { 202 | return [$path ?: $this->path, $domain ?: $this->domain, is_bool($secure) ? $secure : $this->secure, $sameSite ?: $this->sameSite]; 203 | } 204 | 205 | /** 206 | * Set the default path and domain for the jar. 207 | * 208 | * @param string $path 209 | * @param string|null $domain 210 | * @param bool|null $secure 211 | * @param string|null $sameSite 212 | * @return $this 213 | */ 214 | public function setDefaultPathAndDomain($path, $domain, $secure = false, $sameSite = null) 215 | { 216 | [$this->path, $this->domain, $this->secure, $this->sameSite] = [$path, $domain, $secure, $sameSite]; 217 | 218 | return $this; 219 | } 220 | 221 | /** 222 | * Get the cookies which have been queued for the next request. 223 | * 224 | * @return \Symfony\Component\HttpFoundation\Cookie[] 225 | */ 226 | public function getQueuedCookies() 227 | { 228 | return Arr::flatten($this->queued); 229 | } 230 | 231 | /** 232 | * Flush the cookies which have been queued for the next request. 233 | * 234 | * @return $this 235 | */ 236 | public function flushQueuedCookies() 237 | { 238 | $this->queued = []; 239 | 240 | return $this; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | protected $except = []; 29 | 30 | /** 31 | * The globally ignored cookies that should not be encrypted. 32 | * 33 | * @var array 34 | */ 35 | protected static $neverEncrypt = []; 36 | 37 | /** 38 | * Indicates if cookies should be serialized. 39 | * 40 | * @var bool 41 | */ 42 | protected static $serialize = false; 43 | 44 | /** 45 | * Create a new CookieGuard instance. 46 | * 47 | * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter 48 | */ 49 | public function __construct(EncrypterContract $encrypter) 50 | { 51 | $this->encrypter = $encrypter; 52 | } 53 | 54 | /** 55 | * Disable encryption for the given cookie name(s). 56 | * 57 | * @param string|array $name 58 | * @return void 59 | */ 60 | public function disableFor($name) 61 | { 62 | $this->except = array_merge($this->except, (array) $name); 63 | } 64 | 65 | /** 66 | * Handle an incoming request. 67 | * 68 | * @param \Illuminate\Http\Request $request 69 | * @param \Closure $next 70 | * @return \Symfony\Component\HttpFoundation\Response 71 | */ 72 | public function handle($request, Closure $next) 73 | { 74 | return $this->encrypt($next($this->decrypt($request))); 75 | } 76 | 77 | /** 78 | * Decrypt the cookies on the request. 79 | * 80 | * @param \Symfony\Component\HttpFoundation\Request $request 81 | * @return \Symfony\Component\HttpFoundation\Request 82 | */ 83 | protected function decrypt(Request $request) 84 | { 85 | foreach ($request->cookies as $key => $cookie) { 86 | if ($this->isDisabled($key)) { 87 | continue; 88 | } 89 | 90 | try { 91 | $value = $this->decryptCookie($key, $cookie); 92 | 93 | $request->cookies->set($key, $this->validateValue($key, $value)); 94 | } catch (DecryptException) { 95 | $request->cookies->set($key, null); 96 | } 97 | } 98 | 99 | return $request; 100 | } 101 | 102 | /** 103 | * Validate and remove the cookie value prefix from the value. 104 | * 105 | * @param string $key 106 | * @param string $value 107 | * @return string|array|null 108 | */ 109 | protected function validateValue(string $key, $value) 110 | { 111 | return is_array($value) 112 | ? $this->validateArray($key, $value) 113 | : CookieValuePrefix::validate($key, $value, $this->encrypter->getAllKeys()); 114 | } 115 | 116 | /** 117 | * Validate and remove the cookie value prefix from all values of an array. 118 | * 119 | * @param string $key 120 | * @param array $value 121 | * @return array 122 | */ 123 | protected function validateArray(string $key, array $value) 124 | { 125 | $validated = []; 126 | 127 | foreach ($value as $index => $subValue) { 128 | $validated[$index] = $this->validateValue("{$key}[{$index}]", $subValue); 129 | } 130 | 131 | return $validated; 132 | } 133 | 134 | /** 135 | * Decrypt the given cookie and return the value. 136 | * 137 | * @param string $name 138 | * @param string|array $cookie 139 | * @return string|array 140 | */ 141 | protected function decryptCookie($name, $cookie) 142 | { 143 | return is_array($cookie) 144 | ? $this->decryptArray($cookie) 145 | : $this->encrypter->decrypt($cookie, static::serialized($name)); 146 | } 147 | 148 | /** 149 | * Decrypt an array based cookie. 150 | * 151 | * @param array $cookie 152 | * @return array 153 | */ 154 | protected function decryptArray(array $cookie) 155 | { 156 | $decrypted = []; 157 | 158 | foreach ($cookie as $key => $value) { 159 | if (is_string($value)) { 160 | $decrypted[$key] = $this->encrypter->decrypt($value, static::serialized($key)); 161 | } 162 | 163 | if (is_array($value)) { 164 | $decrypted[$key] = $this->decryptArray($value); 165 | } 166 | } 167 | 168 | return $decrypted; 169 | } 170 | 171 | /** 172 | * Encrypt the cookies on an outgoing response. 173 | * 174 | * @param \Symfony\Component\HttpFoundation\Response $response 175 | * @return \Symfony\Component\HttpFoundation\Response 176 | */ 177 | protected function encrypt(Response $response) 178 | { 179 | foreach ($response->headers->getCookies() as $cookie) { 180 | if ($this->isDisabled($cookie->getName())) { 181 | continue; 182 | } 183 | 184 | $response->headers->setCookie($this->duplicate( 185 | $cookie, 186 | $this->encrypter->encrypt( 187 | CookieValuePrefix::create($cookie->getName(), $this->encrypter->getKey()).$cookie->getValue(), 188 | static::serialized($cookie->getName()) 189 | ) 190 | )); 191 | } 192 | 193 | return $response; 194 | } 195 | 196 | /** 197 | * Duplicate a cookie with a new value. 198 | * 199 | * @param \Symfony\Component\HttpFoundation\Cookie $cookie 200 | * @param mixed $value 201 | * @return \Symfony\Component\HttpFoundation\Cookie 202 | */ 203 | protected function duplicate(Cookie $cookie, $value) 204 | { 205 | return $cookie->withValue($value); 206 | } 207 | 208 | /** 209 | * Determine whether encryption has been disabled for the given cookie. 210 | * 211 | * @param string $name 212 | * @return bool 213 | */ 214 | public function isDisabled($name) 215 | { 216 | return in_array($name, array_merge($this->except, static::$neverEncrypt)); 217 | } 218 | 219 | /** 220 | * Indicate that the given cookies should never be encrypted. 221 | * 222 | * @param array|string $cookies 223 | * @return void 224 | */ 225 | public static function except($cookies) 226 | { 227 | static::$neverEncrypt = array_values(array_unique( 228 | array_merge(static::$neverEncrypt, Arr::wrap($cookies)) 229 | )); 230 | } 231 | 232 | /** 233 | * Determine if the cookie contents should be serialized. 234 | * 235 | * @param string $name 236 | * @return bool 237 | */ 238 | public static function serialized($name) 239 | { 240 | return static::$serialize; 241 | } 242 | 243 | /** 244 | * Flush the middleware's global state. 245 | * 246 | * @return void 247 | */ 248 | public static function flushState() 249 | { 250 | static::$neverEncrypt = []; 251 | 252 | static::$serialize = false; 253 | } 254 | } 255 | --------------------------------------------------------------------------------