add SameSite support
[catagits/Catalyst-Plugin-Session-State-Cookie.git] / lib / Catalyst / Plugin / Session / State / Cookie.pm
CommitLineData
1a776a0c 1package Catalyst::Plugin::Session::State::Cookie;
679f4a58 2use Moose;
3use namespace::autoclean;
bf2bce67 4
679f4a58 5extends 'Catalyst::Plugin::Session::State';
bf2bce67 6
45c96680 7use MRO::Compat;
74586782 8use Catalyst::Utils ();
bf2bce67 9
d612a8fc 10our $VERSION = "0.17";
ea139a65 11
679f4a58 12has _deleted_session_id => ( is => 'rw' );
81eb8ebf 13
5e50008f 14sub setup_session {
20e33791 15 my $c = shift;
5e50008f 16
45c96680 17 $c->maybe::next::method(@_);
2bde9162 18
5aca1bdd 19 $c->_session_plugin_config->{cookie_name}
7022ec4c 20 ||= Catalyst::Utils::appprefix($c) . '_session';
5e50008f 21}
22
0ff18b66 23sub extend_session_id {
24 my ( $c, $sid, $expires ) = @_;
1a776a0c 25
2bde9162 26 if ( my $cookie = $c->get_session_cookie ) {
0ff18b66 27 $c->update_session_cookie( $c->make_session_cookie( $sid ) );
58730edc 28 }
db1cda22 29
45c96680 30 $c->maybe::next::method( $sid, $expires );
2bde9162 31}
32
33sub set_session_id {
34 my ( $c, $sid ) = @_;
35
36 $c->update_session_cookie( $c->make_session_cookie( $sid ) );
37
45c96680 38 return $c->maybe::next::method($sid);
db1cda22 39}
40
41sub update_session_cookie {
58730edc 42 my ( $c, $updated ) = @_;
ab649d6b 43
8bdcbb46 44 unless ( $c->cookie_is_rejecting( $updated ) ) {
5aca1bdd 45 my $cookie_name = $c->_session_plugin_config->{cookie_name};
8bdcbb46 46 $c->response->cookies->{$cookie_name} = $updated;
47 }
48}
49
50sub cookie_is_rejecting {
51 my ( $c, $cookie ) = @_;
ab649d6b 52
8bdcbb46 53 if ( $cookie->{path} ) {
91e4fe2d 54 return 1 if index '/'.$c->request->path, $cookie->{path};
8bdcbb46 55 }
ab649d6b 56
8bdcbb46 57 return 0;
db1cda22 58}
5e50008f 59
db1cda22 60sub make_session_cookie {
2bde9162 61 my ( $c, $sid, %attrs ) = @_;
58730edc 62
5aca1bdd 63 my $cfg = $c->_session_plugin_config;
58730edc 64 my $cookie = {
2bde9162 65 value => $sid,
58730edc 66 ( $cfg->{cookie_domain} ? ( domain => $cfg->{cookie_domain} ) : () ),
8bdcbb46 67 ( $cfg->{cookie_path} ? ( path => $cfg->{cookie_path} ) : () ),
df55e818 68 %attrs,
58730edc 69 };
70
2bde9162 71 unless ( exists $cookie->{expires} ) {
72 $cookie->{expires} = $c->calculate_session_cookie_expires();
73 }
1e986fd5 74
d2cf2047 75 #beware: we have to accept also the old syntax "cookie_secure = true"
76 my $sec = $cfg->{cookie_secure} || 0; # default = 0 (not set)
77 $cookie->{secure} = 1 unless ( ($sec==0) || ($sec==2) );
ab649d6b 78 $cookie->{secure} = 1 if ( ($sec==2) && $c->req->secure );
79
5aca1bdd 80 $cookie->{httponly} = $cfg->{cookie_httponly};
3c6b7451 81 $cookie->{httponly} = 1
5aca1bdd 82 unless defined $cookie->{httponly}; # default = 1 (set httponly)
fc4b9d6d 83
f877359e 84 $cookie->{samesite} = $cfg->{cookie_samesite};
85 $cookie->{samesite} = "Lax"
86 unless defined $cookie->{ samesite}; # default = Lax
87
1e986fd5 88 return $cookie;
89}
90
2bde9162 91sub calc_expiry { # compat
92 my $c = shift;
45c96680 93 $c->maybe::next::method( @_ ) || $c->calculate_session_cookie_expires( @_ );
2bde9162 94}
95
96sub calculate_session_cookie_expires {
97 my $c = shift;
5aca1bdd 98 my $cfg = $c->_session_plugin_config;
2bde9162 99
45c96680 100 my $value = $c->maybe::next::method(@_);
1e986fd5 101 return $value if $value;
2bde9162 102
58730edc 103 if ( exists $cfg->{cookie_expires} ) {
7022ec4c 104 if ( $cfg->{cookie_expires} > 0 ) {
1e986fd5 105 return time() + $cfg->{cookie_expires};
7022ec4c 106 }
107 else {
1e986fd5 108 return undef;
7022ec4c 109 }
58730edc 110 }
111 else {
2bde9162 112 return $c->session_expires;
58730edc 113 }
bf2bce67 114}
115
2bde9162 116sub get_session_cookie {
bf2bce67 117 my $c = shift;
1a776a0c 118
5aca1bdd 119 my $cookie_name = $c->_session_plugin_config->{cookie_name};
5e50008f 120
2bde9162 121 return $c->request->cookies->{$cookie_name};
122}
123
124sub get_session_id {
125 my $c = shift;
126
ab649d6b 127 if ( !$c->_deleted_session_id and my $cookie = $c->get_session_cookie ) {
bf2bce67 128 my $sid = $cookie->value;
bf2bce67 129 $c->log->debug(qq/Found sessionid "$sid" in cookie/) if $c->debug;
2bde9162 130 return $sid if $sid;
bf2bce67 131 }
bf2bce67 132
45c96680 133 $c->maybe::next::method(@_);
2bde9162 134}
135
136sub delete_session_id {
df55e818 137 my ( $c, $sid ) = @_;
ab649d6b 138
ea139a65 139 $c->_deleted_session_id(1); # to prevent get_session_id from returning it
df55e818 140
141 $c->update_session_cookie( $c->make_session_cookie( $sid, expires => 0 ) );
142
45c96680 143 $c->maybe::next::method($sid);
bf2bce67 144}
145
58b69507 1461;
1a776a0c 147__END__
bf2bce67 148
1a776a0c 149=head1 NAME
bf2bce67 150
75d3560d 151Catalyst::Plugin::Session::State::Cookie - Maintain session IDs using cookies.
bf2bce67 152
1a776a0c 153=head1 SYNOPSIS
bf2bce67 154
20e33791 155 use Catalyst qw/Session Session::State::Cookie Session::Store::Foo/;
bf2bce67 156
1a776a0c 157=head1 DESCRIPTION
bf2bce67 158
1a776a0c 159In order for L<Catalyst::Plugin::Session> to work the session ID needs to be
160stored on the client, and the session data needs to be stored on the server.
bf2bce67 161
1a776a0c 162This plugin stores the session ID on the client using the cookie mechanism.
57dbf608 163
724a6173 164=head1 METHODS
165
166=over 4
167
168=item make_session_cookie
169
170Returns a hash reference with the default values for new cookies.
171
172=item update_session_cookie $hash_ref
173
174Sets the cookie based on C<cookie_name> in the response object.
175
2cfb85de 176=item calc_expiry
177
178=item calculate_session_cookie_expires
179
180=item cookie_is_rejecting
181
182=item delete_session_id
183
184=item extend_session_id
185
186=item get_session_cookie
187
188=item get_session_id
189
190=item set_session_id
191
724a6173 192=back
193
1a776a0c 194=head1 EXTENDED METHODS
58c05d1a 195
57dbf608 196=over 4
197
1a776a0c 198=item prepare_cookies
57dbf608 199
1a776a0c 200Will restore if an appropriate cookie is found.
58c05d1a 201
d52e5079 202=item finalize_cookies
58c05d1a 203
1536d9aa 204Will set a cookie called C<session> if it doesn't exist or if its value is not
19c2baa1 205the current session id.
206
207=item setup_session
208
1536d9aa 209Will set the C<cookie_name> parameter to its default value if it isn't set.
58c05d1a 210
57dbf608 211=back
58c05d1a 212
5e50008f 213=head1 CONFIGURATION
214
215=over 4
216
217=item cookie_name
218
ae33e13f 219The name of the cookie to store (defaults to C<Catalyst::Utils::apprefix($c) . '_session'>).
5e50008f 220
41b4b15c 221=item cookie_domain
222
223The name of the domain to store in the cookie (defaults to current host)
224
7022ec4c 225=item cookie_expires
226
ab649d6b 227Number of seconds from now you want to elapse before cookie will expire.
228Set to 0 to create a session cookie, ie one which will die when the
7022ec4c 229user's browser is shut down.
230
fc4b9d6d 231=item cookie_secure
232
d2cf2047 233If this attribute B<set to 0> the cookie will not have the secure flag.
234
ab649d6b 235If this attribute B<set to 1> (or true for backward compatibility) - the cookie
e94e9e1c 236sent by the server to the client will get the secure flag that tells the browser
237to send this cookie back to the server only via HTTPS.
d2cf2047 238
e94e9e1c 239If this attribute B<set to 2> then the cookie will get the secure flag only if
ab649d6b 240the request that caused cookie generation was sent over https (this option is
e94e9e1c 241not good if you are mixing https and http in your application).
d2cf2047 242
e94e9e1c 243Default value is 0.
d2cf2047 244
245=item cookie_httponly
246
247If this attribute B<set to 0>, the cookie will not have HTTPOnly flag.
248
ab649d6b 249If this attribute B<set to 1>, the cookie will got HTTPOnly flag that should
d2cf2047 250prevent client side Javascript accessing the cookie value - this makes some
251sort of session hijacking attacks significantly harder. Unfortunately not all
ab649d6b 252browsers support this flag (MSIE 6 SP1+, Firefox 3.0.0.6+, Opera 9.5+); if
d2cf2047 253a browser is not aware of HTTPOnly the flag will be ignored.
254
255Default value is 1.
256
2b79283a 257Note1: Many people are confused by the name "HTTPOnly" - it B<does not mean>
ab649d6b 258that this cookie works only over HTTP and not over HTTPS.
d2cf2047 259
ef0cdc56 260Note2: This parameter requires Catalyst::Runtime 5.80005 otherwise is skipped.
fc4b9d6d 261
f877359e 262=item cookie_samesite
263
264This attribute configures the value of the
265L<SameSite|https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite>
266flag.
267
268If set to None, the cookie will be sent when making cross origin requests,
269including following links from other origins. This requires the
270L</cookie_secure> flag to be set.
271
272If set to Lax, the cookie will not be included when embedded in or fetched from
273other origins, but will be included when following cross origin links.
274
275If set to Strict, the cookie will not be included for any cross origin requests,
276including links from different origins.
277
278Default value is C<Lax>. This is the default modern browsers use.
279
280Note: This parameter requires Catalyst::Runtime 5.90125 otherwise is skipped.
281
8bdcbb46 282=item cookie_path
283
284The path of the request url where cookie should be baked.
285
5e50008f 286=back
287
d6bdceb5 288For example, you could stick this in MyApp.pm:
289
58b69507 290 __PACKAGE__->config( 'Plugin::Session' => {
291 cookie_domain => '.mydomain.com',
292 });
d6bdceb5 293
724a6173 294=head1 CAVEATS
db1cda22 295
296Sessions have to be created before the first write to be saved. For example:
297
58b69507 298 sub action : Local {
299 my ( $self, $c ) = @_;
300 $c->res->write("foo");
301 $c->session( ... );
302 ...
303 }
db1cda22 304
305Will cause a session ID to not be set, because by the time a session is
306actually created the headers have already been sent to the client.
307
bf2bce67 308=head1 SEE ALSO
309
1a776a0c 310L<Catalyst>, L<Catalyst::Plugin::Session>.
bf2bce67 311
47f47da5 312=head1 AUTHORS
bf2bce67 313
58b69507 314Yuval Kogman <nothingmuch@woobling.org>
8ae6d944 315
316=head1 CONTRIBUTORS
317
47f47da5 318This module is derived from L<Catalyst::Plugin::Session::FastMmap> code, and
319has been heavily modified since.
320
58b69507 321Andrew Ford
322
323Andy Grundman
324
325Christian Hansen
326
327Marcus Ramberg
328
329Jonathan Rockway <jrockway@cpan.org>
330
331Sebastian Riedel
332
333Florian Ragwitz
bf2bce67 334
335=head1 COPYRIGHT
336
d612a8fc 337Copyright (c) 2005 - 2009
338the Catalyst::Plugin::Session::State::Cookie L</AUTHORS> and L</CONTRIBUTORS>
339as listed above.
bab1512d 340
341=head1 LICENSE
342
bfeb5ca0 343This program is free software, you can redistribute it and/or modify it
344under the same terms as Perl itself.
bf2bce67 345
346=cut