NAME
Cookie - Cookie Object with Encryption or Signature
SYNOPSIS
use Cookie;
my $c = Cookie->new(
name => 'my-cookie',
domain => 'example.com',
value => 'sid1234567',
path => '/',
expires => '+10D',
# or alternatively
maxage => 864000
# to make it exclusively accessible by regular http request and not javascript
http_only => 1,
same_site => 'Lax',
# should it be used under ssl only?
secure => 1,
);
# make the cookie expired
# Sets the expiration datetime to Thu, 01 Jan 1970 09:00:00 GMT
$c->elapse;
# Get cookie as an hash reference
my $hash = $c->as_hash;
print $c->as_string, "\n";
# or
print "$c\n";
# If expires is set, we can use its underlying DateTime object
my $now = DateTime->now;
if( $c->expires && $c->expires > $now )
{
# ok, we're good
}
# Unset expiration, effectively transforming it into a session cookie
$c->expires( undef );
print "Is session cookie? ", $c->is_session ? 'yes' : 'no', "\n";
$c->match_host( 'www.example.com' );
# Set max-age (in seconds) that takes precedence over expiration
$c->max_age( 86400 );
# Make it expired to tell the http client to remove it:
$c->max_age(0) # or $c->max_age(-1)
# Unset max-age
$c->max_age( undef );
print "Is it same? ", $c->same_as( $other ) ? 'yes' : 'no', "\n";
# Conveniently set port, path and domain in one go, but not the secure flag
$c->uri( 'https://www.example.com:8080/some/where' );
# Create encrypted cookie
# You can generate a key or type one as long as it meets the size requirement
use Bytes::Random::Secure ();
my $c = Cookie->new(
name => 'my-cookie',
domain => 'example.com',
value => 'sid1234567',
path => '/',
expires => '+10D',
# or alternatively
maxage => 864000
# to make it exclusively accessible by regular http request and not ajax
http_only => 1,
same_site => 'Lax',
# should it be used under ssl only?
secure => 1,
# Encryption parameters
key => Bytes::Random::Secure::random_bytes(32),
algo => 'AES',
encrypt => 1,
);
print( "My encrypted cookie: $c\n" );
# Sign cookie only
my $c = Cookie->new(
name => 'my-cookie',
domain => 'example.com',
value => 'sid1234567',
path => '/',
expires => '+10D',
# or alternatively
maxage => 864000
# to make it exclusively accessible by regular http request and not ajax
http_only => 1,
same_site => 'Lax',
# should it be used under ssl only?
secure => 1,
# Encryption parameters
# No size constraint for signature, but obviously the longer the better
key => Bytes::Random::Secure::random_bytes(32),
sign => 1,
);
print( "My signed cookie: $c\n" );
VERSION
v0.3.0
DESCRIPTION
This is a powerful and versatile package to create and represent a cookie compliant with the latest standard as set by rfc6265. This can be used as a standalone module, or can be managed as part of the cookie jar Cookie::Jar
The object is overloaded and will call "as_string" upon stringification and can also be used in comparison with other cookie object:
if( $cookie1 eq $cookie2 )
{
# do something
}
This module does not die upon error, but instead returns undef
and sets an error, so you should always check the return value of a method.
See also the Cookie::Jar package to manage server and client side handling of cookies:
use Cookie::Jar;
# Possibly passing the cookie repository the Apache2::RequestRec object
my $jar = Cookie::Jar->new( $r );
my $c = $jar->make(
name => 'my_cookie',
value => 'some value',
domain => 'example.org',
path => '/',
secure => 1,
http_only => 1,
) || die( $jar->error );
# Set it in the server response C<Set-Cookie> header:
$jar->set( $c ) || die( $jar->error );
METHODS
new
Provided with an hash or hash reference of parameters, and this initiates a new cookie object and return it. Each of the following parameters has a corresponding method.
debug
Optional. If set with a positive integer, this will activate verbose debugging message
name
String.
See also "name"
value
String.
See also "value"
comment
String.
See also "comment"
commentURL
URI string or object.
See also "commentURL"
discard
Boolean.
See also "discard"
domain
String.
See also "domain"
expires
Datetime str | DateTime object | integer
See also "expires"
http_only
Boolean,
See also "http_only"
implicit
Boolean.
See also "implicit"
max_age
Integer.
See also "max_age"
path
String.
See also "path"
port
Integer.
See also "port"
same_site
String.
See also "same_site"
secure
Boolean.
See also "secure"
version
Integer.
See also "version"
Other extra parameters not directly related to the cookie standard:
accessed_on
Datetime.
See also "accessed_on"
algo
String.
See also "algo"
created_on
Datetime.
See also "created_on"
encrypt
Boolean.
See also "encrypt"
key
String.
See also "key"
sign
Boolean.
See also "sign"
accessed_on
Set or get the datetime of the cookie object last accessed.
According to rfc6265, section 5.3.12.3, when deciding which cookies to remove, for those who have equal removal priority:
"If two cookies have the same removal priority, the user agent MUST evict the cookie with the earliest last-access date first."
algo
This set or get the the algorithm used to encrypt the cookie value.
It can be any of AES, Anubis, Blowfish, CAST5, Camellia, DES, DES_EDE, KASUMI, Khazad, MULTI2, Noekeon, RC2, RC5, RC6, SAFERP, SAFER_K128, SAFER_K64, SAFER_SK128, SAFER_SK64, SEED, Skipjack, Twofish, XTEA, IDEA, Serpent or simply any <NAME> for which there exists Crypt::Cipher::<NAME>
See also Stackoverflow on the choice of encryption algorithm
By default, the algorithm is set to AES
If the algorithm set is unsupported, this method returns an error
It returns the current value as a scalar object
apply
Provided with an hash ore hash reference of cookie parameter, and this will apply them to each of their equivalent method.
$c->apply(
expires => 'now',
secure => 1,
http_only => 1,
);
In the example above, this will call methods "expires", "secure" and "http_only" passing them the relevant values.
It returns the current object.
as_hash
Returns an hash reference of the cookie value.
The hash reference returned will contain the following keys: name
value
comment
commentURL
domain
expires
http_only
implicit
max_age
path
port
same_site
secure
version
as_string
Returns a string representation of the object.
my $cookie_string = $cookie->as_string;
# or
my $cookie_string = "$cookie";
my-cookie="sid1234567"; Domain=example.com; Path=/; Expires=Mon, 09 Jan 2020 12:17:30 GMT; Secure; HttpOnly
If encryption is enabled with "encrypt", the cookie value will be encrypted using the key provided with "key" and the Initialisation Vector. If the latter was not provided, it will be generated automatically. The resulting encrypted value is then encoded in base64 and escaped. For example:
my $cookie_value = "toc_ok=1";
my $key = Bytes::Random::Secure::random_bytes(32);
# result:
# session=PyJTlRJniAYVJJF6%2FswuPw%3D%3D; Path=/; SameSite=Lax; Secure; HttpOnly
If cookie signature is enabled for integrity protection with "sign", an sha256 hmac will be generated using the key provided with "key" and the resulting hash appended to the cookie value separated by a dot. For example:
my $cookie_value = "toc_ok=1";
my $key = "hard to guess key";
# I2M4/rh/TiNV5RZDSBJkhLblBvrN5k9448G6w/gp/jg=
my $signature = Crypt::Mac::HMAC::hmac_b64( $key, $cookie_value );
# result: toc_ok=1.I2M4/rh/TiNV5RZDSBJkhLblBvrN5k9448G6w/gp/jg=
# ultimately the cookie value sent will be:
# toc_ok%3D1.I2M4%2Frh%2FTiNV5RZDSBJkhLblBvrN5k9448G6w%2Fgp%2Fjg%3D
The returned value is cached so the next time, it simply return the cached version and not re-process it. You can reset it by calling "reset".
comment
$cookie->comment( 'Some comment' );
my $comment = $cookie->comment;
Sets or gets the optional comment for this cookie. This was used in version 2 of cookies but has since been deprecated.
Returns a Module::Generic::Scalar object.
commentURL
$cookie->commentURL( 'https://example.com/some/where.html' );
my $comment = $cookie->commentURL;
Sets or gets the optional comment URL for this cookie. This was used in version 2 of cookies but has since been deprecated.
Returns an URI object.
created_on
Set or get the datetime of the cookie object created. This value is primarily used by Cookie::Jar, as per the rfc6265, when setting the http request header Cookie
to differentiate two cookies that share the same domain and path. The cookie that has their creation datetime earlier are set first:
"Among cookies that have equal-length path fields, cookies with earlier creation-times are listed before cookies with later creation-times." (rfc6265, section 5.4.2)
decrypt
This returns the cookie decrypted value. If it used on a non-encrypted cookie, this would return undef
and set an error
It takes an optional hash or hash reference of parameters:
- algo string
-
The algorithm to use for encryption. Defaults to the value set with "algo". See this method for more information on acceptable values.
- iv string
-
The Initialisation Vector used for encryption and decryption. Default to the value set with "initialisation_vector"
- key string
-
The encryption key. Defaults to the value set with "key"
discard
Boolean. Set or get this value to true to flag this cookie to be discarded, whatever that means to you the user. This is not a standard protocol property.
This method is used in "save_as_lwp" in Cookie::Jar and "save_as_netscape" in Cookie::Jar with the option skip_discard
It returns the current value as a Module::Generic::Boolean object.
domain
$cookie->domain( 'example.com' );
my $dom = $cookie->domain;
Sets or gets the domain for this cookie.
Returns the current value as a Module::Generic::Scalar object.
Note that you can also call it using the alias method host
elapse
Set the expires
value for this cookie to 0
, which, in turn, will set it to Thu, 01 Jan 1970 09:00:00 GMT
When sent to the http client, this will have the effect of removing the cookie.
See rfc6265 for more information.
encrypt
Set or get the boolean value. If true, the this will tell "as_string" to encrypt the cookie value.
To use this feature, an encryption key must be set and the module Crypt::Cipher must be installed.
You can read more about the differences between sign and encryption at Stackoverflow
expires
Sets or gets the expiration date and time for this cookie.
The value provided can be one of:
- A date compliant with rfc7231
-
For example:
01 Nov 2021 08:42:17 GMT
- unix timestamp.
-
For example:
1631099228
- variable time.
-
For example:
30s
(30 seconds),5m
(5 minutes),12h
(12 hours),30D
(30 days),2M
(2 months),1Y
(1 year)However, this is not sprintf, so you cannot combine them, thus you cannot do this:
5m1D
now
-
Special keyword
- In last resort, the value provided will be parsed using "_parse_timestamp" in Module::Generic. If parsing fails, it will return
undef
and set an error.
Ultimately, a DateTime will be derived from those values, or undef
will be returned and an error will be set.
The DateTime object will be set with a formatter to allow a stringification that is compliant with rfc6265.
And you can use "max_age" alternatively.
See also https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Date
Note that a cookie without an expiration datetime is referred as a session cookie
, so setting the cookie expiration change a cookie from being a session cookie to being a more permanent cookie.
As documented, if expiration is "unspecified, the cookie becomes a session cookie. A session finishes when the client shuts down, after which the session cookie is removed."
fields
Returns an array object of cookie fields available. This is essentially used by "apply"
host
Alias for "domain"
host_only
This is an alias for "implicit". It has been added to comply with the language of rfc6265, section 5.3.6
If the domain attribute was not provided by the server for this cookie, then: "set the cookie's host-only-flag to true." and "set the cookie's domain to the canonicalized request-host"
Returns the current value as a Module::Generic::Boolean object (that is stringifyable).
http_only
Sets or gets the boolean for httpOnly
Returns a Module::Generic::Boolean object.
httponly
Alias for "http_only"
implicit
This boolean is set to true if the domain was not initially set and has been derived from the current host.
Returns a Module::Generic::Boolean object.
initialisation_vector
Set or get the Initialisation Vector used for cookie encryption. If you do not provide one, it will be automatically generated. If you want to provide your own, make sure the size meets the encryption algorithm size requirement.
To find the right size for the Initialisation Vector, for example for algorithm AES
, you could do:
perl -MCrypt::Cipher::AES -lE 'say Crypt::Cipher::AES->blocksize'
which would yield 16
is_expired
Returns true if this cookie has an expiration datetime set and it has expired, i.e. the expiration datetime is in the past. Otherwise, it returns false.
Return value is in the form of a Module::Generic::Boolean object that stringifies to 1 or 0;
is_persistent
Boolean. This returns true if the cookie sent from the server is not a session cookie, i.e. it has an "expires" value set.
is_session
Returns true if this is a session cookie, i.e. it has no expiration datetime nor any "max_age" set, otherwise, it returns false.
Return value is in the form of a Module::Generic::Boolean object that stringifies to 1 or 0;
is_tainted
Sets or gets the boolean value. This is a legacy method of old cookie module, but not used anymore.
Returns a Module::Generic::Boolean object.
is_valid
This takes an optional hash or hash reference of parameters.
It returns true if the cookie was signed and the signature is valid, or false otherwise.
If an error occurred, this method returns undef
and sets an error instead, so check the return value.
my $rv = $c->is_valid;
die( $c->error ) if( !defined( $rv ) );
print( "Cookie is valid? ", $rv ? 'yes' : 'no', "\n" );
Return value is in the form of a Module::Generic::Boolean object that stringifies to 1 or 0;
Possible parameters are:
- key string
-
The encryption key to use to sign and verify the cookie signature. Defaults to the value set with "key"
iv
This is an alias for "initialisation_vector"
key
Set or get the encryption key used to encrypt the cookie value. This is used when "encrypt" or "sign" are set to true.
When used for cookie encryption, make sure the key size is big enough to satisfy the encryption algorithm requirement, which you can check with, say for AES
:
perl -MCrypt::Cipher::AES -lE 'say Crypt::Cipher::AES->keysize'
In this case, it will yield 32
. Replace above AES
, byt whatever algorithm you have chosen.
perl -MCrypt::Cipher::Blowfish -lE 'say Crypt::Cipher::Blowfish->keysize'
would yield 56
for Blowfish
You can use "random_bytes" in Bytes::Random::Secure to generate a random key:
# will generate a 32 bytes-long key
my $key = Bytes::Random::Secure::random_bytes(32);
match_host
Provided with an host name and this returns true if this cookie domain either is a perfect match or if the "implicit" flag is on and the cookie domain is a subset of the host provided.
Otherwise this returns false.
max_age
Sets or gets the integer value for Max-Age
This value should be an integer representing the number of seconds until this cookie expires.
As per the rfc6265, Max-Age
takes precedence over Expires
when set, so if you set this, any value set with "expires" will be discarded.
Returns a Module::Generic::Number object.
maxage
Alias for "max_age"
name
Sets or gets the cookie name.
As per the Mozilla documentation, a cookie name cannot contain any of the following charadcters:
\(\)\<\>\@\,\;\:\\\"\/\[\]\?\=\{\}
Returns a Module::Generic::Scalar object.
path
Sets or gets the path.
Returns a Module::Generic::Scalar object.
port
Sets or gets the port number.
Returns a Module::Generic::Number object.
reset
Set the reset flag to true, which will force "as_string" to recompute the string value of the cookie.
same_as
Provided with another object and this returns true if it has the same property values, false otherwise.
This is used in overloaded object comparison, such as:
print( "Same cookie\n" ) if( $c1 eq $c2 );
# or
print( "Same cookie\n" ) if( $c1 == $c2 );
same_site
Sets or gets the boolean value for Same-Site
.
The proper values should be Relaxed
, Strict
or None
, but this module does not enforce the value you set. Setting a proper value is your responsibility.
See Mozilla documentation for more information.
If set to None
, secure should be set to true.
See rfc 6265 for more information.
Returns a Module::Generic::Scalar object.
samesite
Alias for "same_site".
secure
Sets or gets the boolean value for Secure
.
Returns a Module::Generic::Boolean object.
sign
Set or get the boolean value. If true, then the cookie value will be signed. The way this works, is that "hmac_b64" in Crypt::Mac::HMAC will create a SHA256
encrypted digest using the encryption key you provided with "key" and attach the signature to the cookie value separated by a dot. For example:
my $cookie_value = "toc_ok=1";
my $key = "hard to guess key";
my $signature = Crypt::Mac::HMAC::hmac_b64( $key, $cookie_value );
# signature is I2M4/rh/TiNV5RZDSBJkhLblBvrN5k9448G6w/gp/jg=
# cookie resulting value before uri encoding:
# toc_ok%3D1.I2M4/rh/TiNV5RZDSBJkhLblBvrN5k9448G6w/gp/jg=
So, you need to have the module Crypt::Mac installed to be able to use this feature.
Signature are used to ensure data integrity protection for content that are not secret.
For more secret content, use "encrypt".
You can read more about the difference between sign and encryption at Stackoverflow
strict
Boolean. Should this API be strict about the cookie names? When true, this will reject cookie names with invalid characters.
Cookie name can contain only US ASCII characters and exclude any separators such as ( ) < > @ , ; : \ " / [ ] ? = { }
uri
If a value is provided, it will be transformed into a URI object, and its port
, path
and host
components will be used to set the values for "port", "path" and "domain" respectively.
Otherwise, with no value provided, this will form an URI object based on the cookie secure flag, domain
, port
, and path
$c->uri( 'https://www.example.com:8080/some/where?q=find+me' );
# sets host to www.example.com, port to 8080 and path to /some/where
my $uri = $c->uri;
# get an uri based on cookie properties value, such as:
# https://www.example.com:8080/some/where
value
Sets or gets the value for this cookie.
Returns a Module::Generic::Scalar object.
version
Sets or gets the cookie version. This was used in version 2 of the cookie standard, but has since been deprecated by rfc6265.
Returns a Module::Generic::Number object.
_header_datetime
Given a DateTime object, or by default will instantiate a new one, and this will set its formatter to DateTime::Format::Strptime with the appropriate format to ensure the stringification produces a rfc6265 compliant datetime string.
TO_JSON
This method is used so that if the cookie object is part of some data encoded into json, this will convert the cookie data properly to be used by JSON
SIGNED COOKIES
As shown in the "SYNOPSIS" you can sign cookies effortlessly. This package has taken all the hassle of doing it for you.
To use this feature you need to have installed Crypt::Mode::CBC which is part of CryptX
The methods available to use for cookie integrity protection are: "key", "sign" to enable cookie signature, "is_valid" to check if the signature is valid.
Cookie signature is performed by CryptX, which is an XS module, and thus very fast.
ENCRYPTED COOKIES
As shown in the "SYNOPSIS" you can encrypt cookies effortlessly. This package has taken all the hassle of doing it for you.
To use this feature you need to have installed Crypt::Mode::CBC which is part of CryptX
The methods available to use for cookie encryption are: "algo" to set the desired algorithm, "key", "encrypt" to enable encryption, "decrypt" to decrypt the cookie value, and optionally "initialisation_vector".
Cookie encryption is performed by CryptX, which is an XS module, and thus very fast.
INSTALLATION
As usual, to install this module, you can do:
perl Makefile.PL
make
make test
sudo make install
If you have Apache/modperl2 installed, this will also prepare the Makefile and run test under modperl.
The Makefile.PL tries hard to find your Apache configuration, but you can give it a hand by specifying some command line parameters. See Apache::TestMM for available parameters or you can type on the command line:
perl -MApache::TestConfig -le 'Apache::TestConfig::usage()'
For example:
perl Makefile.PL -apxs /usr/bin/apxs -port 1234
# which will also set the path to httpd_conf, otherwise
perl Makefile.PL -httpd_conf /etc/apache2/apache2.conf
# then
make
make test
sudo make install
See also modperl testing documentation
But, if for some reason, you do not want to perform the mod_perl tests, you can use NO_MOD_PERL=1
when calling perl Makefile.PL
, such as:
NO_MOD_PERL=1 perl Makefile.PL
make
make test
sudo make install
AUTHOR
Jacques Deguest <jack@deguest.jp>
SEE ALSO
Cookie::Jar, Apache2::Cookies, APR::Request::Cookie
Latest tentative version of the cookie standard
COPYRIGHT & LICENSE
Copyright (c) 2019-2021 DEGUEST Pte. Ltd.
You can use, copy, modify and redistribute this package and associated files under the same terms as Perl itself.