File Coverage

blib/lib/Mojolicious/Sessions.pm
Criterion Covered Total %
statement 41 41 100.0
branch 17 22 77.2
condition 14 21 66.6
subroutine 8 8 100.0
pod 2 2 100.0
total 82 94 87.2


line stmt bran cond sub pod time code
1             package Mojolicious::Sessions;
2 50     50   378 use Mojo::Base -base;
  50         130  
  50         365  
3              
4 50     50   383 use Mojo::JSON;
  50         159  
  50         2216  
5 50     50   373 use Mojo::Util qw(b64_decode b64_encode);
  50         144  
  50         36237  
6              
7             has [qw(cookie_domain 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 187     187 1 415 my ($self, $c) = @_;
17              
18 187 100       535 return unless my $value = $c->signed_cookie($self->cookie_name);
19 31         125 $value =~ y/-/=/;
20 31 50       347 return unless my $session = $self->deserialize->(b64_decode $value);
21              
22             # "expiration" value is inherited
23 31   66     229 my $expiration = $session->{expiration} // $self->default_expiration;
24 31 50 66     160 return if !(my $expires = delete $session->{expires}) && $expiration;
25 31 50 66     193 return if defined $expires && $expires <= time;
26              
27 31         107 my $stash = $c->stash;
28 31 100       165 return unless $stash->{'mojo.active_session'} = keys %$session;
29 30         78 $stash->{'mojo.session'} = $session;
30 30 100       132 $session->{flash} = delete $session->{new_flash} if $session->{new_flash};
31             }
32              
33             sub store {
34 932     932 1 2242 my ($self, $c) = @_;
35              
36             # Make sure session was active
37 932         2273 my $stash = $c->stash;
38 932 100       3545 return unless my $session = $stash->{'mojo.session'};
39 100 50 66     550 return unless keys %$session || $stash->{'mojo.active_session'};
40              
41             # Don't reset flash for static files
42 45         93 my $old = delete $session->{flash};
43 45 50       125 $session->{new_flash} = $old if $stash->{'mojo.static'};
44 45 100       94 delete $session->{new_flash} unless keys %{$session->{new_flash}};
  45         200  
45              
46             # Generate "expires" value from "expiration" if necessary
47 45   66     285 my $expiration = $session->{expiration} // $self->default_expiration;
48 45         101 my $default = delete $session->{expires};
49 45 100 66     298 $session->{expires} = $default || time + $expiration if $expiration || $default;
      66        
50              
51 45         190 my $value = b64_encode $self->serialize->($session), '';
52 45         193 $value =~ y/=/-/;
53             my $options = {
54             domain => $self->cookie_domain,
55             expires => $session->{expires},
56 45         176 httponly => 1,
57             path => $self->cookie_path,
58             samesite => $self->samesite,
59             secure => $self->secure
60             };
61 45         177 $c->signed_cookie($self->cookie_name, $value, $options);
62             }
63              
64 31     31   347 sub _deserialize { Mojo::JSON::decode_json($_[0] =~ s/\}\KZ*$//r) }
65              
66             sub _serialize {
67 50     50   435 no warnings 'numeric';
  50         136  
  50         6564  
68 45     45   205 my $out = Mojo::JSON::encode_json($_[0]);
69 45         634 return $out . 'Z' x (1025 - length $out);
70             }
71              
72             1;
73              
74             =encoding utf8
75              
76             =head1 NAME
77              
78             Mojolicious::Sessions - Session manager based on signed cookies
79              
80             =head1 SYNOPSIS
81              
82             use Mojolicious::Sessions;
83              
84             my $sessions = Mojolicious::Sessions->new;
85             $sessions->cookie_name('myapp');
86             $sessions->default_expiration(86400);
87              
88             =head1 DESCRIPTION
89              
90             L manages sessions based on signed cookies for L. All data gets serialized with
91             L and stored Base64 encoded on the client-side, but is protected from unwanted changes with a HMAC-SHA256
92             signature.
93              
94             =head1 ATTRIBUTES
95              
96             L implements the following attributes.
97              
98             =head2 cookie_domain
99              
100             my $domain = $sessions->cookie_domain;
101             $sessions = $sessions->cookie_domain('.example.com');
102              
103             Domain for session cookies, not defined by default.
104              
105             =head2 cookie_name
106              
107             my $name = $sessions->cookie_name;
108             $sessions = $sessions->cookie_name('session');
109              
110             Name for session cookies, defaults to C.
111              
112             =head2 cookie_path
113              
114             my $path = $sessions->cookie_path;
115             $sessions = $sessions->cookie_path('/foo');
116              
117             Path for session cookies, defaults to C.
118              
119             =head2 default_expiration
120              
121             my $time = $sessions->default_expiration;
122             $sessions = $sessions->default_expiration(3600);
123              
124             Default time for sessions to expire in seconds from now, defaults to C<3600>. The expiration timeout gets refreshed for
125             every request. Setting the value to C<0> will allow sessions to persist until the browser window is closed, this can
126             have security implications though. For more control you can also use the C and C session values.
127              
128             # Expiration date in seconds from now (persists between requests)
129             $c->session(expiration => 604800);
130              
131             # Expiration date as absolute epoch time (only valid for one request)
132             $c->session(expires => time + 604800);
133              
134             # Delete whole session by setting an expiration date in the past
135             $c->session(expires => 1);
136              
137             =head2 deserialize
138              
139             my $cb = $sessions->deserialize;
140             $sessions = $sessions->deserialize(sub ($bytes) {...});
141              
142             A callback used to deserialize sessions, defaults to L.
143              
144             $sessions->deserialize(sub ($bytes) { return {} });
145              
146             =head2 samesite
147              
148             my $samesite = $sessions->samesite;
149             $sessions = $sessions->samesite('Strict');
150              
151             Set the SameSite value on all session cookies, defaults to C. Note that this attribute is B because
152             even though most commonly used browsers support the feature, there is no specification yet besides L
153             draft|https://tools.ietf.org/html/draft-west-first-party-cookies-07>.
154              
155             # Disable SameSite feature
156             $sessions->samesite(undef);
157              
158             =head2 secure
159              
160             my $bool = $sessions->secure;
161             $sessions = $sessions->secure($bool);
162              
163             Set the secure flag on all session cookies, so that browsers send them only over HTTPS connections.
164              
165             =head2 serialize
166              
167             my $cb = $sessions->serialize;
168             $sessions = $sessions->serialize(sub ($hash) {...});
169              
170             A callback used to serialize sessions, defaults to L.
171              
172             $sessions->serialize(sub ($hash) { return '' });
173              
174             =head1 METHODS
175              
176             L inherits all methods from L and implements the following new ones.
177              
178             =head2 load
179              
180             $sessions->load(Mojolicious::Controller->new);
181              
182             Load session data from signed cookie.
183              
184             =head2 store
185              
186             $sessions->store(Mojolicious::Controller->new);
187              
188             Store session data in signed cookie.
189              
190             =head1 SEE ALSO
191              
192             L, L, L.
193              
194             =cut