File Coverage

blib/lib/Catalyst/Plugin/Session/Store/Cookie.pm
Criterion Covered Total %
statement 36 38 94.7
branch 4 6 66.6
condition 10 23 43.4
subroutine 7 9 77.7
pod 4 5 80.0
total 61 81 75.3


line stmt bran cond sub pod time code
1              
2             use Moose;
3 1     1   3215 use Session::Storage::Secure;
  1         2  
  1         5  
4 1     1   6782 use MRO::Compat;
  1         65610  
  1         31  
5 1     1   8 use Catalyst::Utils;
  1         2  
  1         18  
6 1     1   6  
  1         2  
  1         508  
7             extends 'Catalyst::Plugin::Session::Store';
8             with 'Catalyst::ClassData';
9              
10             our $VERSION = '0.004';
11              
12             __PACKAGE__->mk_classdata($_)
13             for qw/_secure_store _store_cookie_name _store_cookie_expires
14             _store_cookie_secure _store_cookie_httponly _store_cookie_samesite/;
15              
16             my ($self, $key) = @_;
17             $self->_needs_early_session_finalization(1);
18 22     22 1 271966 my $cookie = $self->req->cookie($self->_store_cookie_name);
19 22         67 $self->{__cookie_session_store_cache__} = defined($cookie) ?
20 22         5631 $self->_secure_store->decode($cookie->value) : {};
21 22 100       2194  
22             return $self->{__cookie_session_store_cache__}->{$key};
23             }
24 22         13762  
25             my ($self, $key, $data) = @_;
26              
27             $self->{__cookie_session_store_cache__} = +{
28 3     3 1 2262 %{$self->{__cookie_session_store_cache__}},
29             $key => $data};
30              
31 3         7 my $cookie = {
  3         14  
32             value => $self->_secure_store->encode($self->{__cookie_session_store_cache__}),
33             expires => $self->_store_cookie_expires,
34             };
35 3         17  
36             # copied from Catalyst::Plugin::Session::State::Cookie
37             my $sec = $self->_store_cookie_secure;
38             $cookie->{secure} = 1 unless ( ($sec==0) || ($sec==2) );
39             $cookie->{secure} = 1 if ( ($sec==2) && $self->req->secure );
40 3         8271 $cookie->{httponly} = $self->_store_cookie_httponly;
41 3 50 33     63 $cookie->{samesite} = $self->_store_cookie_samesite;
42 3 50 33     11  
43 3         11 return $self->res->cookies->{$self->_store_cookie_name} = $cookie;
44 3         59 }
45              
46 3         58 my ($self, $key) = @_;
47             delete $self->{__cookie_session_store_cache__}->{$key};
48             }
49              
50 0     0 1 0 # Docs say 'this may be used in the future', like 10 years ago...
51 0         0  
52             my $class = shift;
53             my $cfg = $class->_session_plugin_config;
54             $class->_store_cookie_name($cfg->{storage_cookie_name} || Catalyst::Utils::appprefix($class) . '_store');
55       0 1   $class->_store_cookie_expires($cfg->{storage_cookie_expires} || '+1d');
56             $class->_secure_store(
57             Session::Storage::Secure->new(
58 1     1 0 328077 secret_key => ($cfg->{storage_secret_key} ||
59 1         4 die "storage_secret_key' configuration param for 'Catalyst::Plugin::Session::Store::Cookie' is missing!"),
60 1   33     100 sereal_encoder_options => ($cfg->{sereal_encoder_options} || +{ snappy => 1, stringify_unknown => 1 }),
61 1   50     64 sereal_decoder_options => ($cfg->{sereal_decoder_options} || +{ validate_utf8 => 1 })
62             )
63             );
64             $class->_store_cookie_secure($cfg->{storage_cookie_secure} || 0);
65             $class->_store_cookie_httponly($cfg->{storage_cookie_httponly} || 1);
66             $class->_store_cookie_samesite($cfg->{storage_cookie_samesite} || 'Lax');
67 1   50     41  
      50        
      50        
68             return $class->maybe::next::method(@_);
69             }
70 1   50     5308  
71 1   50     30 __PACKAGE__->meta->make_immutable;
72 1   50     25  
73             =head1 NAME
74 1         33  
75             Catalyst::Plugin::Session::Store::Cookie - Store session data in the cookie
76              
77             =head1 SYNOPSIS
78              
79             package MyApp;
80              
81             use Catalyst qw/
82             Session
83             Session::State::Cookie
84             Session::Store::Cookie
85             /;
86              
87             __PACKAGE__->config(
88             'Plugin::Session' => {
89             storage_cookie_name => ...,
90             storage_cookie_expires => ...,
91             storage_secret_key => ...,
92             storage_cookie_secure => ...,
93             storage_cookie_httponly => ...,
94             storage_cookie_samesite => ...,
95             },
96             ## More configuration
97             );
98              
99             __PACKAGE__->setup;
100              
101             =head1 DESCRIPTION
102              
103             What's old is new again...
104              
105             Store session data in the client cookie, like in 1995. Handy when you don't
106             want to setup yet another storage system just for supporting sessions and
107             authentication. Can be very fast since you avoid the overhead of requesting and
108             deserializing session information from whatever you are using to store it.
109             Since Sessions in L<Catalyst> are global you can use this to reduce per request
110             overhead. On the other hand you may just use this for early prototying and
111             then move onto something else for production. I'm sure you'll do the right
112             thing ;)
113              
114             The downsides are that you can really only count on about 4Kb of storage space
115             on the cookie. Also, that cookie data becomes part of every request so that
116             will increase overhead on the request side of the network. In other words a big
117             cookie means more data over the wire (maybe you are paying by the byte...?)
118              
119             Also there are some questions as to the security of this approach. We encrypt
120             information with L<Session::Storage::Secure> so you should review that and the
121             notes that it includes. Using this without SSL/HTTPS is not recommended. Buyer
122             beware.
123              
124             In any case if all you are putting in the session is a user id and a few basic
125             things this will probably be totally fine and likely a lot more sane that using
126             something non persistant like memcached. On the other hand if you like to dump
127             a bunch of stuff into the user session, this will likely not work out.
128              
129             B<NOTE> Since we need to store all the session info in the cookie, the session
130             state will be set at ->finalize_headers stage (rather than at ->finalize_body
131             which is the default for session storage plugins). What this means is that if
132             you use the streaming or socket interfaces ($c->response->write, $c->response->write_fh
133             and $c->req->io_fh) your session state will get saved early. For example you
134             cannot do this:
135              
136             $c->res->write("some stuff");
137             $c->session->{key} = "value";
138              
139             That key 'key' will not be recalled when the session is recovered for the following
140             request. In general this should be an easy issue to work around, but you need
141             to be aware of it.
142              
143             =head1 CONFIGURATION
144              
145             This plugin supports the following configuration settings, which are stored as
146             a hash ref under the configuration key 'Plugin::Session::Store::Cookie'. See
147             L</SYNOPSIS> for example.
148              
149             =head2 storage_cookie_name
150              
151             The name of the cookie that stores your session data on the client. Defaults
152             to '${$myapp}_sstore' (where $myappp is the lowercased version of your application
153             subclass). You may wish something less obvious.
154              
155             =head2 storage_cookie_expires
156              
157             How long before the cookie that is storing the session info expires. defaults
158             to '+1d'. Lower is more secure but bigger hassle for your user. You choose the
159             right balance.
160              
161             =head2 storage_secret_key
162              
163             Used to fill the 'secret_key' initialization parameter for L<Session::Storage::Secure>.
164             Don't let this be something you can guess or something that escapes into the
165             wild...
166              
167             There is no default for this, you need to supply.
168              
169             =head2 storage_cookie_secure
170              
171             If this attribute B<set to 0> the cookie will not have the secure flag.
172              
173             If this attribute B<set to 1> the cookie sent by the server to the client
174             will get the secure flag that tells the browser to send this cookie back to
175             the server only via HTTPS.
176              
177             If this attribute B<set to 2> then the cookie will get the secure flag only if
178             the request that caused cookie generation was sent over https (this option is
179             not good if you are mixing https and http in your application).
180              
181             Default value is 0.
182              
183             =head2 storage_cookie_httponly
184              
185             If this attribute B<set to 0>, the cookie will not have HTTPOnly flag.
186              
187             If this attribute B<set to 1>, the cookie will got HTTPOnly flag that should
188             prevent client side Javascript accessing the cookie value - this makes some
189             sort of session hijacking attacks significantly harder. Unfortunately not all
190             browsers support this flag (MSIE 6 SP1+, Firefox 3.0.0.6+, Opera 9.5+); if
191             a browser is not aware of HTTPOnly the flag will be ignored.
192              
193             Default value is 1.
194              
195             Note1: Many people are confused by the name "HTTPOnly" - it B<does not mean>
196             that this cookie works only over HTTP and not over HTTPS.
197              
198             Note2: This parameter requires Catalyst::Runtime 5.80005 otherwise is skipped.
199              
200             =head2 storage_cookie_samesite
201              
202             This attribute configures the value of the
203             L<SameSite|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite>
204             flag.
205              
206             If set to None, the cookie will be sent when making cross origin requests,
207             including following links from other origins. This requires the
208             L</cookie_secure> flag to be set.
209              
210             If set to Lax, the cookie will not be included when embedded in or fetched from
211             other origins, but will be included when following cross origin links.
212              
213             If set to Strict, the cookie will not be included for any cross origin requests,
214             including links from different origins.
215              
216             Default value is C<Lax>. This is the default modern browsers use.
217              
218             Note: This parameter requires Catalyst::Runtime 5.90125 otherwise is skipped.
219              
220             =head2 sereal_decoder_options
221              
222             =head2 sereal_encoder_options
223              
224             This should be a hashref of options passed to init args of same name in
225             L<Session::Storage::Secure>. Defaults to:
226              
227             sereal_encoder_options => +{ snappy => 1, stringify_unknown => 1 },
228             sereal_decoder_options => +{ validate_utf8 => 1 },
229              
230             Please note the default B<allows> object serealization. You may wish to
231             not allow this for production setups.
232              
233             =head1 AUTHOR
234              
235             John Napiorkowski L<email:jjnapiork@cpan.org>
236             Alexander Hartmaier L<email:abraxxa@cpan.org>
237              
238             =head1 SEE ALSO
239              
240             L<Catalyst>, L<Catalyst::Plugin::Session>, L<Session::Storage::Secure>
241              
242             =head1 COPYRIGHT & LICENSE
243              
244             Copyright 2022, John Napiorkowski L<email:jjnapiork@cpan.org>
245              
246             This library is free software; you can redistribute it and/or modify it under
247             the same terms as Perl itself.
248              
249             =cut
250