Skip to content

Commit a087518

Browse files
authored
Merge pull request #5201 from kenjis/add-csrf-session
Add Session based CSRF Protection
2 parents f43f46b + 9851344 commit a087518

6 files changed

Lines changed: 324 additions & 14 deletions

File tree

app/Config/Security.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@
66

77
class Security extends BaseConfig
88
{
9+
/**
10+
* --------------------------------------------------------------------------
11+
* CSRF Protection Method
12+
* --------------------------------------------------------------------------
13+
*
14+
* Protection Method for Cross Site Request Forgery protection.
15+
*
16+
* @var string 'cookie' or 'session'
17+
*/
18+
public $csrfProtection = 'cookie';
19+
920
/**
1021
* --------------------------------------------------------------------------
1122
* CSRF Token Name

depfile.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ ruleset:
193193
- HTTP
194194
Security:
195195
- Cookie
196+
- Session
196197
- HTTP
197198
Session:
198199
- Cookie

env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
# SECURITY
111111
#--------------------------------------------------------------------
112112

113+
# security.csrfProtection = 'cookie'
113114
# security.tokenName = 'csrf_token_name'
114115
# security.headerName = 'X-CSRF-TOKEN'
115116
# security.cookieName = 'csrf_cookie_name'

system/Security/Security.php

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use CodeIgniter\Cookie\Cookie;
1515
use CodeIgniter\HTTP\RequestInterface;
1616
use CodeIgniter\Security\Exceptions\SecurityException;
17+
use CodeIgniter\Session\Session;
1718
use Config\App;
1819
use Config\Cookie as CookieConfig;
1920
use Config\Security as SecurityConfig;
@@ -27,6 +28,18 @@
2728
*/
2829
class 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

Comments
 (0)