default csrf filter and class in ci4 is not fully support for rest full api i mean when backend and front end are separate oky got problem with it so i decide to write new one which work very well
the problem when appear s when send multiple request at same time
it test with post man work
$csrf= new CsrfSecurity();
$csrf->$init();
this return string hash code call after login in and send to header x-csrf-token
each time it get csrf token in response object
$data['csrf']=hash;
hash need send to header x-csrf-token
again again.
here s my code
PHP Code:
<?php
namespace CSRF\Config;
use CodeIgniter\Config\BaseConfig;
class CsrfConfig extends BaseConfig
{
/**
* --------------------------------------------------------------------------
* CSRF Token Name
* --------------------------------------------------------------------------
*
* Token name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
public $tokenName = 'csrf_test_name';
/**
* --------------------------------------------------------------------------
* CSRF Header Name
* --------------------------------------------------------------------------
*
* Token name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
public $headerName = 'X-CSRF-TOKEN';
/**
* --------------------------------------------------------------------------
* CSRF Cookie Name
* --------------------------------------------------------------------------
*
* Cookie name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
public $cookieName = 'csrf_cookie_name';
/**
* --------------------------------------------------------------------------
* Ex CSRF Cookie Name
* --------------------------------------------------------------------------
*
* Cookie name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
public $exCookieName = 'ex_csrf_cookie_name';
/**
* --------------------------------------------------------------------------
* CSRF Expires
* --------------------------------------------------------------------------
*
* Expiration time for Cross Site Request Forgery protection cookie.
*
* Defaults to two hours (in seconds).
*
* @var integer
*/
public $expires = 7200;
/**
* --------------------------------------------------------------------------
* CSRF Regenerate
* --------------------------------------------------------------------------
*
* Regenerate CSRF Token on every request.
*
* @var boolean
*/
public $regenerate = true;
/**
* --------------------------------------------------------------------------
* CSRF SameSite
* --------------------------------------------------------------------------
*
* Setting for CSRF SameSite cookie token.
*
* Allowed values are: None - Lax - Strict - ''.
*
* Defaults to `Lax` as recommended in this link:
*
* @see https://portswigger.net/web-security/csrf/samesite-cookies
*
* @var string
*
* @deprecated
*/
public $samesite = 'Lax';
/**
* --------------------------------------------------------------------------
* Cookie Prefix
* --------------------------------------------------------------------------
*
* Set a cookie name prefix if you need to avoid collisions.
*
* @var string
*
* @deprecated use Config\Cookie::$prefix property instead.
*/
public $cookiePrefix = '';
}
PHP Code:
<?php
namespace CSRF\Filters;
use CoreAuth\Enums\FilterErrorType;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Config\Services;
use CSRF\Libraries\CsrfSecurity;
/**
* CSRF filter.
*
* This filter is not intended to be used from the command line.
*
* @codeCoverageIgnore
*/
class CsrfFilter implements FilterInterface
{
/**
* Do whatever processing this filter needs to do.
* By default it should not return anything during
* normal execution. However, when an abnormal state
* is found, it should return an instance of
* CodeIgniter\HTTP\Authenticate. If it does, script
* execution will end and that Response will be
* sent back to the client, allowing for error pages,
* redirects, etc.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param array|null $arguments
*
* @return mixed
* @throws \CodeIgniter\Security\Exceptions\SecurityException
*/
public function before(RequestInterface $request, $arguments = null)
{
if ($request->isCLI()) {
return;
}
// $ruleRoute = \CoreAuth\Config\Services::ruleRoute();
// if ($ruleRoute->ignoreRoute()) {
// return;
// }
$csrfSecurity = new CsrfSecurity();
if (!$csrfSecurity->verify($request)) {
return Services::response()->setJSON(['success' => false,
'type' => FilterErrorType::Csrf,
'error' => lang('Csrf.filter.csrf')])->setContentType('application/json')
->setStatusCode(Response::HTTP_UNAUTHORIZED, lang('Authenticate.filter.csrf'));
}
}
//--------------------------------------------------------------------
/**
* We don't have anything to do here.
*
* @param RequestInterface|\CodeIgniter\HTTP\IncomingRequest $request
* @param ResponseInterface|\CodeIgniter\HTTP\Response $response
* @param array|null $arguments
*
* @return mixed
*/
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
if ($request->isCLI()) {
return;
}
// $ruleRoute = \CoreAuth\Config\Services::ruleRoute();
// if ($ruleRoute->ignoreRoute()) {
// return;
// }
$csrfSecurity = new CsrfSecurity();
$data = json_decode($response->getJSON(), true);
if (!empty($data)) {
if ($csrfSecurity->refresh($request) == true) {
$data['csrf'] = $csrfSecurity->getHash();
}
$response->setJSON($data);
}
}
}
PHP Code:
<?php
/**
* This file is part of the CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CSRF\Interfaces;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\Security\Exceptions\SecurityException;
/**
* Expected behavior of a Security.
*/
interface CsrfInterface
{
/**
* CSRF Verify
*
* @param RequestInterface $request
*
* @return bool
*
* @throws SecurityException
*/
public function verify(RequestInterface $request): bool;
/**
* Returns the CSRF Hash.
*
* @return string|null
*/
public function getHash(): ?string;
/**
* Returns the CSRF Token Name.
*
* @return string
*/
public function getTokenName(): string;
/**
* Returns the CSRF Header Name.
*
* @return string
*/
public function getHeaderName(): string;
/**
* Returns the CSRF Cookie Name.
*
* @return string
*/
public function getCookieName(): string;
/**
* Check if CSRF cookie is expired.
*
* @return boolean
*
* @deprecated
*/
public function isExpired(): bool;
/**
* CSRF Verify
*
* @param RequestInterface $request
*
* @return $this|false
*
*/
public function refresh(RequestInterface $request): bool;
/**
* CSRF init
*
* @param RequestInterface $request
* @return string
*/
public function init(RequestInterface $request): string;
}
PHP Code:
<?php
namespace CSRF\Libraries;
use CodeIgniter\Cookie\Cookie;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\Security\Exceptions\SecurityException;
use CodeIgniter\Session\SessionInterface;
use Config\App;
use Config\Cookie as CookieConfig;
use Config\Security as SecurityConfig;
use CSRF\Config\CsrfConfig;
use CSRF\Interfaces\CsrfInterface;use PharIo\Manifest\Library;
/**
* Class Security
*
* Provides methods that help protect your site against
* Cross-Site Request Forgery attacks.
*/
class CsrfSecurity implements CsrfInterface
{
/**
*
* Holds the session instance
*/
protected SessionInterface $session;
/**
* CSRF Hash
*
* Random hash for Cross Site Request Forgery protection cookie
*
* @var string|null
*/
protected $hash = null;
/**
* CSRF Token Name
*
* Token name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
protected $tokenName = 'csrf_token_name';
/**
* CSRF Header Name
*
* Token name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
protected $headerName = 'X-CSRF-TOKEN';
/**
* The CSRF Cookie instance.
*
* @var Cookie
*/
protected $cookie;
/**
* CSRF Cookie Name
*
* Cookie name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
protected $cookieName = 'csrf_cookie_name';
/**
* EX CSRF Cookie Name
*
* Cookie name for Cross Site Request Forgery protection cookie.
*
* @var string
*/
protected $exCookieName = 'ex_csrf_cookie_name';
/**
* CSRF Expires
*
* Expiration time for Cross Site Request Forgery protection cookie.
*
* Defaults to two hours (in seconds).
*
* @var integer
*
* @deprecated
*/
protected $expires = 7200;
/**
* CSRF Regenerate
*
* Regenerate CSRF Token on every request.
*
* @var boolean
*/
protected $regenerate = true;
/**
* CSRF Redirect
*
* Redirect to previous page with error on failure.
*
* @var boolean
*/
/**
* CSRF SameSite
*
* Setting for CSRF SameSite cookie token.
*
* Allowed values are: None - Lax - Strict - ''.
*
* Defaults to `Lax` as recommended in this link:
*
* @see https://portswigger.net/web-security/csrf/samesite-cookies
*
* @var string
*
* @deprecated
*/
protected $samesite = Cookie::SAMESITE_LAX;
/**
* Constructor.
*
* Stores our configuration and fires off the init() method to setup
* initial state.
*
*
*/
public function __construct()
{
$config = new CsrfConfig();
/** @var SecurityConfig */
$this->session = \Codeigniter\Config\Services::session();
// Store CSRF-related configurations
$this->tokenName = $config->tokenName;
$this->headerName = $config->headerName;
$this->regenerate = $config->regenerate;
$rawCookieName = $config->cookieName;
/** @var CookieConfig */
$cookie = config('Cookie');
$cookiePrefix = $cookie->prefix ?? $config->cookiePrefix;
$this->cookieName = $cookiePrefix . $rawCookieName;
$expires = $security->expires ?? $config->expires ?? 7200;
Cookie::setDefaults($cookie);
$this->cookie = new Cookie($rawCookieName, $this->generateHash(), [
'expires' => $expires === 0 ? 0 : time() + $expires,
]);
}
/**
* CSRF Verify
*
* @param RequestInterface $request
*
* @return $this|false
*
* @throws SecurityException
*/
public function verify(RequestInterface $request): bool
{
$exCsrfCookie = null;
$csrfCookie = null;
$csrfHeader = null;
if ($this->session->has($this->cookieName)) {
$csrfCookie = $this->session->get($this->cookieName);
} else {
$csrfCookie = get_cookie($this->cookieName);
}
if ($this->session->has($this->exCookieName)) {
$exCsrfCookie = $this->session->get($this->exCookieName);
}
$csrfHeader = $request->getHeaderLine($this->headerName);
log_message('info', 'CSRF token verifying.');
return ($csrfHeader == $csrfCookie || $csrfHeader == $exCsrfCookie);
}
/**
* CSRF Generate
*
* @param RequestInterface $request
*
*
* @return array
*
*/
public function refresh(RequestInterface $request): bool
{
if ($this->session->has($this->exCookieName) &&
($request->getHeaderLine($this->headerName) ==
$this->session->get($this->exCookieName))) {
return false;
}
if ($this->session->has($this->cookieName)) {
$exCsrf = $this->session->get($this->cookieName);
$this->session->set($this->exCookieName, $exCsrf);
}
if ($this->regenerate) {
$this->hash = null;
unset($_COOKIE[$this->cookieName]);
}
$newToken = $this->generateHash();
$this->session->set($this->cookieName, $newToken);
$this->cookie = $this->cookie->withValue($newToken);
$this->sendCookie($request);
log_message('info', 'CSRF token generate');
return true;
}
/**
* Returns the CSRF Hash.
*
* @return string|null
*/
public function getHash(): ?string
{
return $this->hash;
}
/**
* Returns the CSRF Token Name.
*
* @return string
*/
public function getTokenName(): string
{
return $this->tokenName;
}
/**
* Returns the CSRF Header Name.
*
* @return string
*/
public function getHeaderName(): string
{
return $this->headerName;
}
/**
* Returns the CSRF Cookie Name.
*
* @return string
*/
public function getCookieName(): string
{
return $this->cookieName;
}
/**
* Check if CSRF cookie is expired.
*
* @return boolean
*
* @deprecated
*
* @codeCoverageIgnore
*/
public function isExpired(): bool
{
return $this->cookie->isExpired();
}
/**
* Generates the CSRF Hash.
*
* @return string
*/
protected function generateHash(): string
{
if (is_null($this->hash)) {
// If the cookie exists we will use its value.
// We don't necessarily want to regenerate it with
// each page load since a page could contain embedded
// sub-pages causing this feature to fail
if (isset($_COOKIE[$this->cookieName])
&& is_string($_COOKIE[$this->cookieName])
&& preg_match('#^[0-9a-f]{32}$#iS', $_COOKIE[$this->cookieName]) === 1
) {
return $this->hash = $_COOKIE[$this->cookieName];
}
$this->hash = bin2hex(random_bytes(16));
}
return $this->hash;
}
/**
* CSRF Send Cookie
*
* @param RequestInterface $request
*
* @return Security|false
*/
protected function sendCookie(RequestInterface $request)
{
if ($this->cookie->isSecure() && !$request->isSecure()) {
return false;
}
$this->doSendCookie();
log_message('info', 'CSRF cookie sent.');
return $this;
}
/**
* Actual dispatching of cookies.
* Extracted for this to be unit tested.
*
* @codeCoverageIgnore
*
* @return void
*/
protected function doSendCookie(): void
{
cookies([$this->cookie], false)->dispatch();
}
/**
* Returns the CSRF Token Name.
*
* @return string
*
* @deprecated Use `CodeIgniter\Security\Security::getTokenName()` instead of using this method.
*
* @codeCoverageIgnore
*/
public function getCSRFTokenName(): string
{
return $this->getTokenName();
}
/**
* init csrf
*
* @param RequestInterface $request
* @return string
*/
public function init(RequestInterface $request): string
{
unset($_COOKIE[$this->cookieName]);
$newToken = $this->generateHash();
$this->session->set($this->cookieName, $newToken);
$this->cookie = $this->cookie->withValue($newToken);
$this->sendCookie($request);
log_message('info', 'CSRF init');
return $newToken;
}
}
Enlightenment Is Freedom