version bump
[catagits/Catalyst-Plugin-Session.git] / lib / Catalyst / Plugin / Session.pm
CommitLineData
9e447f9d 1#!/usr/bin/perl
2
3package Catalyst::Plugin::Session;
4use base qw/Class::Accessor::Fast/;
5
6use strict;
7use warnings;
8
9use NEXT;
10use Catalyst::Exception ();
9a9252c2 11use Digest ();
12use overload ();
d44bc687 13use Object::Signature ();
9e447f9d 14
708dd5fd 15our $VERSION = "0.06";
37160715 16
ab634fee 17my @session_data_accessors; # used in delete_session
9e447f9d 18BEGIN {
4207ce8d 19 __PACKAGE__->mk_accessors(
ab634fee 20 "_session_delete_reason",
21 @session_data_accessors = qw/
4207ce8d 22 _sessionid
23 _session
24 _session_expires
25 _session_data_sig
4207ce8d 26 _flash
f4d79f85 27 _flash_keep_keys
28 _flash_key_hashes
1dbf4cd5 29 _tried_loading_session_id
30 _tried_loading_session_data
31 _tried_loading_session_expires
32 _tried_loading_flash_data
4207ce8d 33 /
34 );
9e447f9d 35}
36
37sub setup {
9a9252c2 38 my $c = shift;
39
40 $c->NEXT::setup(@_);
41
42 $c->check_session_plugin_requirements;
43 $c->setup_session;
44
45 return $c;
9e447f9d 46}
47
48sub check_session_plugin_requirements {
9a9252c2 49 my $c = shift;
9e447f9d 50
9a9252c2 51 unless ( $c->isa("Catalyst::Plugin::Session::State")
52 && $c->isa("Catalyst::Plugin::Session::Store") )
53 {
54 my $err =
55 ( "The Session plugin requires both Session::State "
56 . "and Session::Store plugins to be used as well." );
9e447f9d 57
9a9252c2 58 $c->log->fatal($err);
59 Catalyst::Exception->throw($err);
60 }
9e447f9d 61}
62
63sub setup_session {
9a9252c2 64 my $c = shift;
9e447f9d 65
9a9252c2 66 my $cfg = ( $c->config->{session} ||= {} );
9e447f9d 67
9a9252c2 68 %$cfg = (
69 expires => 7200,
70 verify_address => 1,
71 %$cfg,
72 );
9e447f9d 73
9a9252c2 74 $c->NEXT::setup_session();
9e447f9d 75}
76
19c130c2 77sub prepare_action {
78 my $c = shift;
79
4207ce8d 80 if ( $c->config->{session}{flash_to_stash}
e5b2372a 81 and $c->sessionid
4207ce8d 82 and my $flash_data = $c->flash )
83 {
19c130c2 84 @{ $c->stash }{ keys %$flash_data } = values %$flash_data;
85 }
86
87 $c->NEXT::prepare_action(@_);
88}
89
9e447f9d 90sub finalize {
9a9252c2 91 my $c = shift;
9e447f9d 92
1dbf4cd5 93 $c->_save_session_expires;
9b0fa2a6 94 $c->_save_session;
95 $c->_save_flash;
b92ce604 96 $c->_save_session_id;
9b0fa2a6 97
98 $c->NEXT::finalize(@_);
99}
100
b92ce604 101sub _save_session_id {
102 my $c = shift;
103}
104
1dbf4cd5 105sub _save_session_expires {
9b0fa2a6 106 my $c = shift;
4207ce8d 107
1dbf4cd5 108 if ( defined(my $expires = $c->_session_expires) ) {
109 my $sid = $c->sessionid;
110 $c->store_session_data( "expires:$sid" => $expires );
9e447f9d 111
1dbf4cd5 112 $c->_session_expires(undef);
113 $c->_tried_loading_session_expires(undef);
114 }
115}
6687905d 116
1dbf4cd5 117sub _save_session {
118 my $c = shift;
7a02371f 119
1dbf4cd5 120 if ( my $session_data = $c->_session ) {
d44bc687 121
1dbf4cd5 122 no warnings 'uninitialized';
123 if ( Object::Signature::signature($session_data) ne
124 $c->_session_data_sig )
125 {
126 $session_data->{__updated} = time();
127 my $sid = $c->sessionid;
128 $c->store_session_data( "session:$sid" => $session_data );
ea972e9a 129 }
1dbf4cd5 130
131 $c->_session(undef);
132 $c->_tried_loading_session_data(undef);
9a9252c2 133 }
9b0fa2a6 134}
9a9252c2 135
9b0fa2a6 136sub _save_flash {
137 my $c = shift;
138
1dbf4cd5 139 if ( my $flash_data = $c->_flash ) {
4207ce8d 140
1dbf4cd5 141 my $hashes = $c->_flash_key_hashes || {};
142 my $keep = $c->_flash_keep_keys || {};
143 foreach my $key ( keys %$hashes ) {
144 if ( !exists $keep->{$key} and Object::Signature::signature( \$flash_data->{$key} ) eq $hashes->{$key} ) {
145 delete $flash_data->{$key};
23fbca00 146 }
ea972e9a 147 }
1dbf4cd5 148
149 my $sid = $c->sessionid;
150
151 if (%$flash_data) {
152 $c->store_session_data( "flash:$sid", $flash_data );
153 }
154 else {
155 $c->delete_session_data("flash:$sid");
156 }
157
158 $c->_flash(undef);
159 $c->_tried_loading_flash_data(undef);
9b0fa2a6 160 }
9e447f9d 161}
162
e5b2372a 163sub _load_session_expires {
164 my $c = shift;
b92ce604 165 return $c->_session_expires if $c->_tried_loading_session_expires;
1dbf4cd5 166 $c->_tried_loading_session_expires(1);
e5b2372a 167
168 if ( my $sid = $c->sessionid ) {
169 my $expires = $c->get_session_data("expires:$sid") || 0;
170
171 if ( $expires >= time() ) {
172 return $c->extend_session_expires( $expires );
173 } else {
174 $c->delete_session( "session expired" );
175 return 0;
176 }
e5b2372a 177 }
1dbf4cd5 178
179 return;
e5b2372a 180}
181
b7acf64e 182sub _load_session {
183 my $c = shift;
b92ce604 184 return $c->_session if $c->_tried_loading_session_data;
1dbf4cd5 185 $c->_tried_loading_session_data(1);
b7acf64e 186
e5b2372a 187 if ( my $sid = $c->sessionid ) {
6687905d 188 if ( $c->session_expires ) { # > 0
0974ac06 189
2e412459 190 my $session_data = $c->get_session_data("session:$sid") || return;
6687905d 191 $c->_session($session_data);
3f182468 192
6687905d 193 no warnings 'uninitialized'; # ne __address
194 if ( $c->config->{session}{verify_address}
195 && $session_data->{__address} ne $c->request->address )
196 {
197 $c->log->warn(
198 "Deleting session $sid due to address mismatch ("
199 . $session_data->{__address} . " != "
47ca362e 200 . $c->request->address . ")"
6687905d 201 );
202 $c->delete_session("address mismatch");
203 return;
204 }
4207ce8d 205
6687905d 206 $c->log->debug(qq/Restored session "$sid"/) if $c->debug;
168d6819 207 $c->_session_data_sig( Object::Signature::signature($session_data) ) if $session_data;
6687905d 208 $c->_expire_session_keys;
4207ce8d 209
6687905d 210 return $session_data;
3f182468 211 }
9a9252c2 212 }
29d15411 213
4207ce8d 214 return;
b7acf64e 215}
9a9252c2 216
9b0fa2a6 217sub _load_flash {
218 my $c = shift;
b92ce604 219 return $c->_flash if $c->_tried_loading_flash_data;
1dbf4cd5 220 $c->_tried_loading_flash_data(1);
9b0fa2a6 221
e5b2372a 222 if ( my $sid = $c->sessionid ) {
4207ce8d 223 if ( my $flash_data = $c->_flash
224 || $c->_flash( $c->get_session_data("flash:$sid") ) )
225 {
f4d79f85 226 $c->_flash_key_hashes({ map { $_ => Object::Signature::signature( \$flash_data->{$_} ) } keys %$flash_data });
e5b2372a 227
9b0fa2a6 228 return $flash_data;
229 }
230 }
231
1dbf4cd5 232 return;
9b0fa2a6 233}
234
d44bc687 235sub _expire_session_keys {
b7acf64e 236 my ( $c, $data ) = @_;
237
238 my $now = time;
239
e5b2372a 240 my $expire_times = ( $data || $c->_session || {} )->{__expire_keys} || {};
241 foreach my $key ( grep { $expire_times->{$_} < $now } keys %$expire_times ) {
b7acf64e 242 delete $c->_session->{$key};
e5b2372a 243 delete $expire_times->{$key};
b7acf64e 244 }
9e447f9d 245}
246
247sub delete_session {
9a9252c2 248 my ( $c, $msg ) = @_;
9e447f9d 249
b92ce604 250 $c->log->debug("Deleting session") if $c->debug;
251
9a9252c2 252 # delete the session data
b92ce604 253 if ( my $sid = $c->sessionid ) {
254 $c->delete_session_data("${_}:${sid}") for qw/session expires flash/;
c6eaa93a 255 $c->delete_session_id($sid);
b92ce604 256 }
e5b2372a 257
9a9252c2 258 # reset the values in the context object
ab634fee 259 # see the BEGIN block
260 $c->$_(undef) for @session_data_accessors;
6687905d 261
29d15411 262 $c->_session_delete_reason($msg);
263}
264
265sub session_delete_reason {
266 my $c = shift;
267
e5b2372a 268 $c->session_is_valid; # check that it was loaded
29d15411 269
4207ce8d 270 $c->_session_delete_reason(@_);
9e447f9d 271}
272
6687905d 273sub session_expires {
e5b2372a 274 my $c = shift;
6687905d 275
e5b2372a 276 if ( defined( my $expires = $c->_session_expires ) ) {
277 return $expires;
1dbf4cd5 278 } elsif ( defined( $expires = $c->_load_session_expires ) ) {
279 $c->_session_expires($expires);
280 return $expires;
e5b2372a 281 } else {
1dbf4cd5 282 return 0;
e5b2372a 283 }
284}
6687905d 285
e5b2372a 286sub extend_session_expires {
287 my ( $c, $expires ) = @_;
e5b2372a 288 $c->_session_expires( my $updated = $c->calculate_extended_session_expires( $expires ) );
4c79feb6 289 $c->extend_session_id( $c->sessionid, $updated );
e5b2372a 290 return $updated;
291}
6687905d 292
e5b2372a 293sub calculate_initial_session_expires {
294 my $c = shift;
295 return ( time() + $c->config->{session}{expires} );
296}
297
298sub calculate_extended_session_expires {
299 my ( $c, $prev ) = @_;
300 $c->calculate_initial_session_expires;
301}
302
303sub reset_session_expires {
304 my ( $c, $sid ) = @_;
305 $c->_session_expires( my $exp = $c->calculate_initial_session_expires );
306 $exp;
6687905d 307}
308
0974ac06 309sub sessionid {
4207ce8d 310 my $c = shift;
e5b2372a 311
1dbf4cd5 312 return $c->_sessionid || $c->_load_sessionid;
313}
314
315sub _load_sessionid {
316 my $c = shift;
317 return if $c->_tried_loading_session_id;
318 $c->_tried_loading_session_id(1);
319
320 if ( defined( my $sid = $c->get_session_id ) ) {
321 if ( $c->validate_session_id($sid) ) {
322 $c->_sessionid( $sid );
323 return $sid;
324 } else {
325 my $err = "Tried to set invalid session ID '$sid'";
326 $c->log->error($err);
327 Catalyst::Exception->throw($err);
4207ce8d 328 }
329 }
1dbf4cd5 330
331 return;
e5b2372a 332}
333
334sub session_is_valid {
335 my $c = shift;
4207ce8d 336
b92ce604 337 # force a check for expiry, but also __address, etc
338 if ( $c->_load_session ) {
e5b2372a 339 return 1;
340 } else {
341 return;
342 }
0974ac06 343}
344
345sub validate_session_id {
4207ce8d 346 my ( $c, $sid ) = @_;
0974ac06 347
4207ce8d 348 $sid and $sid =~ /^[a-f\d]+$/i;
0974ac06 349}
350
9e447f9d 351sub session {
9a9252c2 352 my $c = shift;
9e447f9d 353
29d15411 354 $c->_session || $c->_load_session || do {
1dbf4cd5 355 $c->create_session_id_if_needed;
29d15411 356 $c->initialize_session_data;
4207ce8d 357 };
9e447f9d 358}
359
f4d79f85 360sub keep_flash {
361 my ( $c, @keys ) = @_;
2e412459 362 my $href = $c->_flash_keep_keys || $c->_flash_keep_keys({});
363 (@{$href}{@keys}) = ((undef) x @keys);
f4d79f85 364}
365
873f7011 366sub flash {
367 my $c = shift;
78476ce0 368 $c->_flash || $c->_load_flash || do {
1dbf4cd5 369 $c->create_session_id_if_needed;
78476ce0 370 $c->_flash( {} );
1dbf4cd5 371 }
873f7011 372}
373
b7acf64e 374sub session_expire_key {
375 my ( $c, %keys ) = @_;
376
377 my $now = time;
4207ce8d 378 @{ $c->session->{__expire_keys} }{ keys %keys } =
379 map { $now + $_ } values %keys;
b7acf64e 380}
381
9e447f9d 382sub initialize_session_data {
9a9252c2 383 my $c = shift;
9e447f9d 384
9a9252c2 385 my $now = time;
9e447f9d 386
4207ce8d 387 return $c->_session(
388 {
389 __created => $now,
390 __updated => $now,
391
392 (
393 $c->config->{session}{verify_address}
394 ? ( __address => $c->request->address )
395 : ()
396 ),
397 }
398 );
9e447f9d 399}
400
9e447f9d 401sub generate_session_id {
402 my $c = shift;
403
404 my $digest = $c->_find_digest();
405 $digest->add( $c->session_hash_seed() );
406 return $digest->hexdigest;
407}
408
1dbf4cd5 409sub create_session_id_if_needed {
78476ce0 410 my $c = shift;
1dbf4cd5 411 $c->create_session_id unless $c->sessionid;
412}
78476ce0 413
1dbf4cd5 414sub create_session_id {
415 my $c = shift;
416
e5b2372a 417 my $sid = $c->generate_session_id;
78476ce0 418
e5b2372a 419 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
78476ce0 420
e5b2372a 421 $c->_sessionid($sid);
422 $c->reset_session_expires;
423 $c->set_session_id($sid);
424
425 return $sid;
78476ce0 426}
427
9e447f9d 428my $counter;
9a9252c2 429
9e447f9d 430sub session_hash_seed {
9a9252c2 431 my $c = shift;
432
433 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
9e447f9d 434}
435
436my $usable;
9a9252c2 437
9e447f9d 438sub _find_digest () {
9a9252c2 439 unless ($usable) {
d44bc687 440 foreach my $alg (qw/SHA-1 SHA-256 MD5/) {
4207ce8d 441 if ( eval { Digest->new($alg) } ) {
5faaa4b0 442 $usable = $alg;
443 last;
444 }
7d139eeb 445 }
4207ce8d 446 Catalyst::Exception->throw(
9a9252c2 447 "Could not find a suitable Digest module. Please install "
4207ce8d 448 . "Digest::SHA1, Digest::SHA, or Digest::MD5" )
449 unless $usable;
9a9252c2 450 }
9e447f9d 451
452 return Digest->new($usable);
453}
454
99b2191e 455sub dump_these {
456 my $c = shift;
457
458 (
459 $c->NEXT::dump_these(),
460
461 $c->sessionid
462 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
463 : ()
464 );
465}
466
e5b2372a 467
468sub get_session_id { shift->NEXT::get_session_id(@_) }
469sub set_session_id { shift->NEXT::set_session_id(@_) }
470sub delete_session_id { shift->NEXT::delete_session_id(@_) }
4c79feb6 471sub extend_session_id { shift->NEXT::extend_session_id(@_) }
e5b2372a 472
9e447f9d 473__PACKAGE__;
474
475__END__
476
477=pod
478
479=head1 NAME
480
481Catalyst::Plugin::Session - Generic Session plugin - ties together server side
fb1a4ac3 482storage and client side state required to maintain session data.
9e447f9d 483
484=head1 SYNOPSIS
485
8f0b4c16 486 # To get sessions to "just work", all you need to do is use these plugins:
487
488 use Catalyst qw/
489 Session
490 Session::Store::FastMmap
491 Session::State::Cookie
492 /;
493
494 # you can replace Store::FastMmap with Store::File - both have sensible
495 # default configurations (see their docs for details)
496
497 # more complicated backends are available for other scenarios (DBI storage,
498 # etc)
499
500
501 # after you've loaded the plugins you can save session data
502 # For example, if you are writing a shopping cart, it could be implemented
503 # like this:
9e447f9d 504
229a5b53 505 sub add_item : Local {
506 my ( $self, $c ) = @_;
507
508 my $item_id = $c->req->param("item");
509
8f0b4c16 510 # $c->session is a hash ref, a bit like $c->stash
511 # the difference is that it' preserved across requests
229a5b53 512
513 push @{ $c->session->{items} }, $item_id;
514
515 $c->forward("MyView");
516 }
517
518 sub display_items : Local {
519 my ( $self, $c ) = @_;
520
521 # values in $c->session are restored
522 $c->stash->{items_to_display} =
8f0b4c16 523 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
229a5b53 524
525 $c->forward("MyView");
526 }
527
9e447f9d 528=head1 DESCRIPTION
529
530The Session plugin is the base of two related parts of functionality required
531for session management in web applications.
532
533The first part, the State, is getting the browser to repeat back a session key,
534so that the web application can identify the client and logically string
535several requests together into a session.
536
537The second part, the Store, deals with the actual storage of information about
538the client. This data is stored so that the it may be revived for every request
539made by the same client.
540
541This plugin links the two pieces together.
542
8f0b4c16 543=head1 RECCOMENDED BACKENDS
544
545=over 4
546
547=item Session::State::Cookie
548
549The only really sane way to do state is using cookies.
550
551=item Session::Store::File
552
553A portable backend, based on Cache::File.
554
555=item Session::Store::FastMmap
556
557A fast and flexible backend, based on Cache::FastMmap.
558
559=back
560
9e447f9d 561=head1 METHODS
562
563=over 4
564
565=item sessionid
566
567An accessor for the session ID value.
568
569=item session
570
571Returns a hash reference that might contain unserialized values from previous
572requests in the same session, and whose modified value will be saved for future
573requests.
574
575This method will automatically create a new session and session ID if none
576exists.
577
ab634fee 578=item session_expires
579
580=item session_expires $reset
581
582This method returns the time when the current session will expire, or 0 if
583there is no current session. If there is a session and it already expired, it
584will delete the session and return 0 as well.
585
586If the C<$reset> parameter is true, and there is a session ID the expiry time
587will be reset to the current time plus the time to live (see
588L</CONFIGURATION>). This is used when creating a new session.
589
07e714d2 590=item flash
591
592This is like Ruby on Rails' flash data structure. Think of it as a stash that
44ab6d1c 593lasts for longer than one request, letting you redirect instead of forward.
594
595The flash data will be cleaned up only on requests on which actually use
596$c->flash (thus allowing multiple redirections), and the policy is to delete
bf6bd311 597all the keys which haven't changed since the flash data was loaded at the end
598of every request.
07e714d2 599
600 sub moose : Local {
601 my ( $self, $c ) = @_;
602
603 $c->flash->{beans} = 10;
604 $c->response->redirect( $c->uri_for("foo") );
605 }
606
607 sub foo : Local {
608 my ( $self, $c ) = @_;
609
610 my $value = $c->flash->{beans};
611
612 # ...
613
614 $c->response->redirect( $c->uri_for("bar") );
615 }
616
617 sub bar : Local {
618 my ( $self, $c ) = @_;
619
620 if ( exists $c->flash->{beans} ) { # false
621
622 }
623 }
624
bf6bd311 625=item keep_flash @keys
626
627If you wawnt to keep a flash key for the next request too, even if it hasn't
628changed, call C<keep_flash> and pass in the keys as arguments.
629
fffeb18f 630=item delete_session REASON
631
632This method is used to invalidate a session. It takes an optional parameter
633which will be saved in C<session_delete_reason> if provided.
634
9e447f9d 635=item session_delete_reason
636
637This accessor contains a string with the reason a session was deleted. Possible
638values include:
639
640=over 4
641
642=item *
643
644C<address mismatch>
645
646=item *
647
648C<session expired>
649
650=back
651
b7acf64e 652=item session_expire_key $key, $ttl
653
654Mark a key to expire at a certain time (only useful when shorter than the
655expiry time for the whole session).
656
657For example:
658
659 __PACKAGE__->config->{session}{expires} = 1000000000000; # forever
660
661 # later
662
663 $c->session_expire_key( __user => 3600 );
664
665Will make the session data survive, but the user will still be logged out after
666an hour.
667
668Note that these values are not auto extended.
669
8f0b4c16 670=back
671
10c72079 672=head1 INTERNAL METHODS
8f0b4c16 673
674=over 4
675
9e447f9d 676=item setup
677
678This method is extended to also make calls to
679C<check_session_plugin_requirements> and C<setup_session>.
680
681=item check_session_plugin_requirements
682
683This method ensures that a State and a Store plugin are also in use by the
684application.
685
686=item setup_session
687
688This method populates C<< $c->config->{session} >> with the default values
689listed in L</CONFIGURATION>.
690
691=item prepare_action
692
68fd02ae 693This methoid is extended.
694
695It's only effect is if the (off by default) C<flash_to_stash> configuration
696parameter is on - then it will copy the contents of the flash to the stash at
697prepare time.
9e447f9d 698
699=item finalize
700
701This method is extended and will extend the expiry time, as well as persist the
702session data if a session exists.
703
9e447f9d 704=item initialize_session_data
705
706This method will initialize the internal structure of the session, and is
707called by the C<session> method if appropriate.
708
68fd02ae 709=item create_session_id
710
711Creates a new session id using C<generate_session_id> if there is no session ID
712yet.
713
ab634fee 714=item validate_session_id SID
715
716Make sure a session ID is of the right format.
717
718This currently ensures that the session ID string is any amount of case
719insensitive hexadecimal characters.
720
229a5b53 721=item generate_session_id
722
723This method will return a string that can be used as a session ID. It is
724supposed to be a reasonably random string with enough bits to prevent
725collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
726MD5 or SHA-256, depending on the availibility of these modules.
727
728=item session_hash_seed
729
730This method is actually rather internal to generate_session_id, but should be
731overridable in case you want to provide more random data.
732
733Currently it returns a concatenated string which contains:
734
735=over 4
736
737=item *
738
739A counter
740
741=item *
742
743The current time
744
745=item *
746
747One value from C<rand>.
748
749=item *
750
751The stringified value of a newly allocated hash reference
752
753=item *
754
755The stringified value of the Catalyst context object
756
757=back
758
759In the hopes that those combined values are entropic enough for most uses. If
760this is not the case you can replace C<session_hash_seed> with e.g.
761
762 sub session_hash_seed {
763 open my $fh, "<", "/dev/random";
764 read $fh, my $bytes, 20;
765 close $fh;
766 return $bytes;
767 }
768
769Or even more directly, replace C<generate_session_id>:
770
771 sub generate_session_id {
772 open my $fh, "<", "/dev/random";
773 read $fh, my $bytes, 20;
774 close $fh;
775 return unpack("H*", $bytes);
776 }
777
778Also have a look at L<Crypt::Random> and the various openssl bindings - these
779modules provide APIs for cryptographically secure random data.
780
99b2191e 781=item dump_these
782
783See L<Catalyst/dump_these> - ammends the session data structure to the list of
784dumped objects if session ID is defined.
785
9e447f9d 786=back
787
a92c8aeb 788=head1 USING SESSIONS DURING PREPARE
789
790The earliest point in time at which you may use the session data is after
791L<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
792
793State plugins must set $c->session ID before C<prepare_action>, and during
794C<prepare_action> L<Catalyst::Plugin::Session> will actually load the data from
795the store.
796
797 sub prepare_action {
798 my $c = shift;
799
800 # don't touch $c->session yet!
b1cd7d77 801
a92c8aeb 802 $c->NEXT::prepare_action( @_ );
803
804 $c->session; # this is OK
805 $c->sessionid; # this is also OK
806 }
807
9e447f9d 808=head1 CONFIGURATION
809
229a5b53 810 $c->config->{session} = {
811 expires => 1234,
812 };
9e447f9d 813
814All configuation parameters are provided in a hash reference under the
815C<session> key in the configuration hash.
816
817=over 4
818
819=item expires
820
821The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
822hours).
823
824=item verify_address
825
8c7e922c 826When true, C<<$c->request->address>> will be checked at prepare time. If it is
827not the same as the address that initiated the session, the session is deleted.
9e447f9d 828
68fd02ae 829=item flash_to_stash
830
831This option makes it easier to have actions behave the same whether they were
832forwarded to or redirected to. On prepare time it copies the contents of
833C<flash> (if any) to the stash.
834
9e447f9d 835=back
836
837=head1 SPECIAL KEYS
838
839The hash reference returned by C<< $c->session >> contains several keys which
840are automatically set:
841
842=over 4
843
844=item __expires
845
ab634fee 846This key no longer exists. Use C<session_expires> instead.
9e447f9d 847
848=item __updated
849
d44bc687 850The last time a session was saved to the store.
9e447f9d 851
852=item __created
853
854The time when the session was first created.
855
856=item __address
857
858The value of C<< $c->request->address >> at the time the session was created.
8c7e922c 859This value is only populated if C<verify_address> is true in the configuration.
9e447f9d 860
861=back
862
c80e9f04 863=head1 CAVEATS
864
a552e4b5 865=head2 Round the Robin Proxies
866
c80e9f04 867C<verify_address> could make your site inaccessible to users who are behind
868load balanced proxies. Some ISPs may give a different IP to each request by the
869same client due to this type of proxying. If addresses are verified these
870users' sessions cannot persist.
871
872To let these users access your site you can either disable address verification
873as a whole, or provide a checkbox in the login dialog that tells the server
874that it's OK for the address of the client to change. When the server sees that
875this box is checked it should delete the C<__address> sepcial key from the
876session hash when the hash is first created.
877
a552e4b5 878=head2 Race Conditions
879
880In this day and age where cleaning detergents and dutch football (not the
881american kind) teams roam the plains in great numbers, requests may happen
882simultaneously. This means that there is some risk of session data being
883overwritten, like this:
884
885=over 4
886
887=item 1.
888
889request a starts, request b starts, with the same session id
890
891=item 2.
892
893session data is loaded in request a
894
895=item 3.
896
897session data is loaded in request b
898
899=item 4.
900
901session data is changed in request a
902
903=item 5.
904
905request a finishes, session data is updated and written to store
906
907=item 6.
908
909request b finishes, session data is updated and written to store, overwriting
910changes by request a
911
912=back
913
914If this is a concern in your application, a soon to be developed locking
915solution is the only safe way to go. This will have a bigger overhead.
916
917For applications where any given user is only making one request at a time this
918plugin should be safe enough.
919
d45028d6 920=head1 AUTHORS
921
baa9db9c 922=over 4
923
924=item Andy Grundman
925
926=item Christian Hansen
927
928=item Yuval Kogman, C<nothingmuch@woobling.org> (current maintainer)
929
930=item Sebastian Riedel
931
932=back
933
934And countless other contributers from #catalyst. Thanks guys!
d45028d6 935
cc40ae4b 936=head1 COPYRIGHT & LICENSE
d45028d6 937
938 Copyright (c) 2005 the aforementioned authors. All rights
939 reserved. This program is free software; you can redistribute
940 it and/or modify it under the same terms as Perl itself.
941
9e447f9d 942=cut
943
944