File Coverage

blib/lib/Mojolicious/Sessions.pm
Criterion Covered Total %
statement 39 39 100.0
branch 20 26 76.9
condition 15 21 71.4
subroutine 7 7 100.0
pod 2 2 100.0
total 83 95 87.3


line stmt bran cond sub pod time code
1             package Mojolicious::Sessions;
2 54     54   493 use Mojo::Base -base;
  54         157  
  54         483  
3              
4 54     54   486 use Mojo::JSON;
  54         142  
  54         3659  
5 54     54   376 use Mojo::Util qw(b64_decode b64_encode);
  54         211  
  54         59002  
6              
7             has [qw(cookie_domain encrypted secure)];
8             has cookie_name => 'mojolicious';
9             has cookie_path => '/';
10             has default_expiration => 3600;
11             has deserialize => sub { \&_deserialize };
12             has samesite => 'Lax';
13             has serialize => sub { \&_serialize };
14              
15             sub load {
16 211     211 1 524 my ($self, $c) = @_;
17              
18 211 50       811 my $method = $self->encrypted ? 'encrypted_cookie' : 'signed_cookie';
19 211 100       814 return unless my $value = $c->$method($self->cookie_name);
20 41         128 $value =~ y/-/=/;
21 41 50       338 return unless my $session = $self->deserialize->(b64_decode $value);
22              
23             # "expiration" value is inherited
24 41   66     345 my $expiration = $session->{expiration} // $self->default_expiration;
25 41 50 66     209 return if !(my $expires = delete $session->{expires}) && $expiration;
26 41 50 66     230 return if defined $expires && $expires <= time;
27              
28 41         157 my $stash = $c->stash;
29 41 100       235 return unless $stash->{'mojo.active_session'} = keys %$session;
30 36         120 $stash->{'mojo.session'} = $session;
31 36 100       187 $session->{flash} = delete $session->{new_flash} if $session->{new_flash};
32             }
33              
34             sub store {
35 1010     1010 1 3038 my ($self, $c) = @_;
36              
37             # Make sure session was active
38 1010         3384 my $stash = $c->stash;
39 1010 100       4688 return unless my $session = $stash->{'mojo.session'};
40 119 100 100     776 return unless keys %$session || $stash->{'mojo.active_session'};
41              
42             # Don't reset flash for static files
43 51         132 my $old = delete $session->{flash};
44 51 50       155 $session->{new_flash} = $old if $stash->{'mojo.static'};
45 51 100       92 delete $session->{new_flash} unless keys %{$session->{new_flash}};
  51         278  
46              
47             # Generate "expires" value from "expiration" if necessary
48 51   66     322 my $expiration = $session->{expiration} // $self->default_expiration;
49 51         140 my $default = delete $session->{expires};
50 51 100 66     425 $session->{expires} = $default || time + $expiration if $expiration || $default;
      66        
51              
52 51         199 my $value = b64_encode $self->serialize->($session), '';
53 51         158 $value =~ y/=/-/;
54             my $options = {
55             domain => $self->cookie_domain,
56             expires => $session->{expires},
57 51         206 httponly => 1,
58             path => $self->cookie_path,
59             samesite => $self->samesite,
60             secure => $self->secure
61             };
62 51 50       210 my $method = $self->encrypted ? 'encrypted_cookie' : 'signed_cookie';
63 51         184 $c->$method($self->cookie_name, $value, $options);
64             }
65              
66             # DEPRECATED! (Remove once old sessions with padding are no longer a concern)
67 41     41   539 sub _deserialize { Mojo::JSON::decode_json($_[0] =~ s/\}\KZ*$//r) }
68              
69 51     51   292 sub _serialize { Mojo::JSON::encode_json($_[0]) }
70              
71             1;
72              
73             =encoding utf8
74              
75             =head1 NAME
76              
77             Mojolicious::Sessions - Session manager based on signed cookies
78              
79             =head1 SYNOPSIS
80              
81             use Mojolicious::Sessions;
82              
83             my $sessions = Mojolicious::Sessions->new;
84             $sessions->cookie_name('myapp');
85             $sessions->default_expiration(86400);
86              
87             =head1 DESCRIPTION
88              
89             L manages sessions based on signed cookies for L. All data gets serialized with
90             L and stored Base64 encoded on the client-side, but is protected from unwanted changes with a HMAC-SHA256
91             signature.
92              
93             =head1 ATTRIBUTES
94              
95             L implements the following attributes.
96              
97             =head2 cookie_domain
98              
99             my $domain = $sessions->cookie_domain;
100             $sessions = $sessions->cookie_domain('.example.com');
101              
102             Domain for session cookies, not defined by default.
103              
104             =head2 cookie_name
105              
106             my $name = $sessions->cookie_name;
107             $sessions = $sessions->cookie_name('session');
108              
109             Name for session cookies, defaults to C.
110              
111             =head2 cookie_path
112              
113             my $path = $sessions->cookie_path;
114             $sessions = $sessions->cookie_path('/foo');
115              
116             Path for session cookies, defaults to C.
117              
118             =head2 default_expiration
119              
120             my $time = $sessions->default_expiration;
121             $sessions = $sessions->default_expiration(3600);
122              
123             Default time for sessions to expire in seconds from now, defaults to C<3600>. The expiration timeout gets refreshed for
124             every request. Setting the value to C<0> will allow sessions to persist until the browser window is closed, this can
125             have security implications though. For more control you can also use the C and C session values.
126              
127             # Expiration date in seconds from now (persists between requests)
128             $c->session(expiration => 604800);
129              
130             # Expiration date as absolute epoch time (only valid for one request)
131             $c->session(expires => time + 604800);
132              
133             # Delete whole session by setting an expiration date in the past
134             $c->session(expires => 1);
135              
136             =head2 deserialize
137              
138             my $cb = $sessions->deserialize;
139             $sessions = $sessions->deserialize(sub ($bytes) {...});
140              
141             A callback used to deserialize sessions, defaults to L.
142              
143             $sessions->deserialize(sub ($bytes) { return {} });
144              
145             =head2 encrypted
146              
147             my $bool = $sessions->encrypted;
148             $sessions = $sessions->encrypted($bool);
149              
150             Use encrypted session cookies instead of merely cryptographically signed ones.
151              
152             =head2 samesite
153              
154             my $samesite = $sessions->samesite;
155             $sessions = $sessions->samesite('Strict');
156              
157             Set the SameSite value on all session cookies, defaults to C.
158              
159             # Disable SameSite feature
160             $sessions->samesite(undef);
161              
162             =head2 secure
163              
164             my $bool = $sessions->secure;
165             $sessions = $sessions->secure($bool);
166              
167             Set the secure flag on all session cookies, so that browsers send them only over HTTPS connections.
168              
169             =head2 serialize
170              
171             my $cb = $sessions->serialize;
172             $sessions = $sessions->serialize(sub ($hash) {...});
173              
174             A callback used to serialize sessions, defaults to L.
175              
176             $sessions->serialize(sub ($hash) { return '' });
177              
178             =head1 METHODS
179              
180             L inherits all methods from L and implements the following new ones.
181              
182             =head2 load
183              
184             $sessions->load(Mojolicious::Controller->new);
185              
186             Load session data from signed cookie.
187              
188             =head2 store
189              
190             $sessions->store(Mojolicious::Controller->new);
191              
192             Store session data in signed cookie.
193              
194             =head1 SEE ALSO
195              
196             L, L, L.
197              
198             =cut