1414use CodeIgniter \Cookie \Cookie ;
1515use CodeIgniter \HTTP \RequestInterface ;
1616use CodeIgniter \Security \Exceptions \SecurityException ;
17+ use CodeIgniter \Session \Session ;
1718use Config \App ;
1819use Config \Cookie as CookieConfig ;
1920use Config \Security as SecurityConfig ;
2728 */
2829class Security implements SecurityInterface
2930{
31+ public const CSRF_PROTECTION_COOKIE = 'cookie ' ;
32+ public const CSRF_PROTECTION_SESSION = 'session ' ;
33+
34+ /**
35+ * CSRF Protection Method
36+ *
37+ * Protection Method for Cross Site Request Forgery protection.
38+ *
39+ * @var string 'cookie' or 'session'
40+ */
41+ protected $ csrfProtection = self ::CSRF_PROTECTION_COOKIE ;
42+
3043 /**
3144 * CSRF Hash
3245 *
@@ -128,6 +141,13 @@ class Security implements SecurityInterface
128141 */
129142 private $ rawCookieName ;
130143
144+ /**
145+ * Session instance.
146+ *
147+ * @var Session
148+ */
149+ private $ session ;
150+
131151 /**
132152 * Constructor.
133153 *
@@ -141,11 +161,12 @@ public function __construct(App $config)
141161
142162 // Store CSRF-related configurations
143163 if ($ security instanceof SecurityConfig) {
144- $ this ->tokenName = $ security ->tokenName ?? $ this ->tokenName ;
145- $ this ->headerName = $ security ->headerName ?? $ this ->headerName ;
146- $ this ->regenerate = $ security ->regenerate ?? $ this ->regenerate ;
147- $ this ->rawCookieName = $ security ->cookieName ?? $ this ->rawCookieName ;
148- $ this ->expires = $ security ->expires ?? $ this ->expires ;
164+ $ this ->csrfProtection = $ security ->csrfProtection ?? $ this ->csrfProtection ;
165+ $ this ->tokenName = $ security ->tokenName ?? $ this ->tokenName ;
166+ $ this ->headerName = $ security ->headerName ?? $ this ->headerName ;
167+ $ this ->regenerate = $ security ->regenerate ?? $ this ->regenerate ;
168+ $ this ->rawCookieName = $ security ->cookieName ?? $ this ->rawCookieName ;
169+ $ this ->expires = $ security ->expires ?? $ this ->expires ;
149170 } else {
150171 // `Config/Security.php` is absence
151172 $ this ->tokenName = $ config ->CSRFTokenName ?? $ this ->tokenName ;
@@ -155,13 +176,28 @@ public function __construct(App $config)
155176 $ this ->expires = $ config ->CSRFExpire ?? $ this ->expires ;
156177 }
157178
158- $ this ->configureCookie ($ config );
179+ if ($ this ->isCSRFCookie ()) {
180+ $ this ->configureCookie ($ config );
181+ } else {
182+ // Session based CSRF protection
183+ $ this ->configureSession ();
184+ }
159185
160186 $ this ->request = Services::request ();
161187
162188 $ this ->generateHash ();
163189 }
164190
191+ private function isCSRFCookie (): bool
192+ {
193+ return $ this ->csrfProtection === self ::CSRF_PROTECTION_COOKIE ;
194+ }
195+
196+ private function configureSession (): void
197+ {
198+ $ this ->session = Services::session ();
199+ }
200+
165201 private function configureCookie (App $ config ): void
166202 {
167203 /** @var CookieConfig|null $cookie */
@@ -253,7 +289,12 @@ public function verify(RequestInterface $request)
253289
254290 if ($ this ->regenerate ) {
255291 $ this ->hash = null ;
256- unset($ _COOKIE [$ this ->cookieName ]);
292+ if ($ this ->isCSRFCookie ()) {
293+ unset($ _COOKIE [$ this ->cookieName ]);
294+ } else {
295+ // Session based CSRF protection
296+ $ this ->session ->remove ($ this ->tokenName );
297+ }
257298 }
258299
259300 $ this ->generateHash ();
@@ -409,13 +450,23 @@ protected function generateHash(): string
409450 // We don't necessarily want to regenerate it with
410451 // each page load since a page could contain embedded
411452 // sub-pages causing this feature to fail
412- if ($ this ->isHashInCookie ()) {
413- return $ this ->hash = $ _COOKIE [$ this ->cookieName ];
453+ if ($ this ->isCSRFCookie ()) {
454+ if ($ this ->isHashInCookie ()) {
455+ return $ this ->hash = $ _COOKIE [$ this ->cookieName ];
456+ }
457+ } elseif ($ this ->session ->has ($ this ->tokenName )) {
458+ // Session based CSRF protection
459+ return $ this ->hash = $ this ->session ->get ($ this ->tokenName );
414460 }
415461
416462 $ this ->hash = bin2hex (random_bytes (16 ));
417463
418- $ this ->saveHashInCookie ();
464+ if ($ this ->isCSRFCookie ()) {
465+ $ this ->saveHashInCookie ();
466+ } else {
467+ // Session based CSRF protection
468+ $ this ->saveHashInSession ();
469+ }
419470 }
420471
421472 return $ this ->hash ;
@@ -428,7 +479,7 @@ private function isHashInCookie(): bool
428479 && preg_match ('#^[0-9a-f]{32}$#iS ' , $ _COOKIE [$ this ->cookieName ]) === 1 ;
429480 }
430481
431- private function saveHashInCookie ()
482+ private function saveHashInCookie (): void
432483 {
433484 $ this ->cookie = new Cookie (
434485 $ this ->rawCookieName ,
@@ -467,4 +518,9 @@ protected function doSendCookie(): void
467518 {
468519 cookies ([$ this ->cookie ], false )->dispatch ();
469520 }
521+
522+ private function saveHashInSession (): void
523+ {
524+ $ this ->session ->set ($ this ->tokenName , $ this ->hash );
525+ }
470526}
0 commit comments