Skip to content

coinsbee/csrf

 
 

Repository files navigation

Automatic protection from Cross-Site Request Forgery.

This library handles CSRF protection automatically for you, including generating tokens, injecting them into all forms in the page and then verifying that a valid token is present whenever a POST request is received.


Usage: Protection in Three Steps

The CSRF library does two things:

  • Injects CSRF tokens into forms
  • Verifies POST requests to make sure they contain a valid token

Each is just a single method call, but you need to set up first.

Step 1: Set up

Start by creating the TokenStore. There are currently three implementations:

  • ArrayTokenStore - The most basic implementation that does not persist tokens between requests
  • SessionTokenStore - Persists tokens in PHP sessions (requires phpgt/session)
  • RedisTokenStore - Stores tokens in Redis with configurable expiration (requires ext-redis)

The easiest way to add CSRF protection is to use the Session or Redis implementation:

use Gt\Csrf\SessionTokenStore;
use Gt\Csrf\RedisTokenStore;

// Option 1: Using Session (requires phpgt/session)
// $session is an object-oriented representation of $_SESSION
// that implements the Gt\Session\SessionContainer Interface.
$tokenStore = new SessionTokenStore($session);

// Option 2: Using Redis (requires ext-redis)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$tokenStore = new RedisTokenStore($redis, 'myapp:csrf:', 3600); // 1 hour expiration

Step 2: Verify

Before running any other code (especially things that could affect data), you should check to make sure that there's a valid CSRF token in place if it's needed:

use Gt\Csrf\Exception\CSRFException;

if(this_is_a_post_request()) {
	try {
		$tokenStore->verify();
	}
	catch(CSRFException $e) {
// Stop processing this request and get out of there!
	}
}

If the request contains a POST and there is no valid CSRF token, a CSRFException will be thrown — so you should plan to catch it. Remember, if that happens, the request was fraudulent, so you shouldn't process it!

Step 3: Inject for Next Time

Finally, once you've finished processing your html code and it's ready to send back to the client, you should inject the CSRF tokens. If you don't, the request will fail to pass Step 2 when the page gets submitted!

use Gt\Csrf\HTMLDocumentProtector;

// The html can come in as anything accepted by Gt\Dom\HTMLDocument - here it's a
// plain string in a variable.
$html = "<html>...</html>";

// Now do the processing.
$protector = new HTMLDocumentProtector($html, $tokenStore);
$protector->protect();

// Output the HTML of the document - you will see the new fields have
// been automatically injected.
echo $protector->getHTMLDocument();

Using tokens of a different lengths

By default, 32 character tokens are generated. They use characters from the set [a-zA-Z0-9], meaning a 64-bit token which would take a brute-force attacker making 100,000 requests per second around 2.93 million years to guess. If this seems either excessive or inadequate you can change the token length using TokenStore::setTokenLength().

Special note about client-side requests

Note that if there are several forms on your page, a unique token will be generated and injected into each form. When a form is submitted using a client-side request (XMLHTTPRequest or Fetch, a.k.a. AJAX), the response will contain a new token that must be refreshed in the page ready for the next submission.

If you would prefer to have one token per page, shared across all forms, this can be configured by passing in the TOKEN_PER_PAGE parameter to the projectAndInject method: $page->protectAndInject(HTMLDocumentProtector::TOKEN_PER_PAGE);.

Storing one token per page will reduce the amount of server resources required, but concurrent client-side requests will fail, which is why one token per form is the default.

Alternatives to storing tokens on the session

The package includes several token store implementations:

  • ArrayTokenStore - Basic in-memory storage (not persistent)
  • SessionTokenStore - Uses PHP sessions for persistence
  • RedisTokenStore - Uses Redis for distributed token storage with automatic expiration

Redis Configuration

The RedisTokenStore provides several configuration options:

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$tokenStore = new RedisTokenStore(
    $redis,                    // Redis connection
    'myapp:csrf:',            // Key prefix (default: 'csrf:token:')
    3600,                     // Token expiration in seconds (default: 3600)
    1000                      // Max tokens (unused in Redis implementation)
);

// Additional Redis-specific methods
$tokenStore->clearAllTokens();    // Remove all tokens
$tokenStore->getTokenCount();     // Count active tokens
$tokenStore->getTokenExpiration(); // Get expiration time
$tokenStore->getKeyPrefix();      // Get Redis key prefix

You can also implement custom token stores by subclassing TokenStore and implementing the abstract methods.

About

Automatic protection from Cross-Site Request Forgery.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • PHP 100.0%