Apache-AuthCookie

 view release on metacpan or  search on metacpan

lib/Apache/AuthCookie.pm  view on Meta::CPAN

=back

By using AuthCookie versus Apache's built-in AuthBasic you can design
your own authentication system.  There are several benefits.

=over 4

=item 1.

The client doesn't *have* to pass the user credentials on every
subsequent access.  If you're using passwords, this means that the
password can be sent on the first request only, and subsequent
requests don't need to send this (potentially sensitive) information.
This is known as "ticket-based" authentication.

=item 2.

When you determine that the client should stop using the
credentials/session key, the server can tell the client to delete the
cookie.  Letting users "log out" is a notoriously impossible-to-solve
problem of AuthBasic.

lib/Apache/AuthCookie.pm  view on Meta::CPAN


 (-----------------------)     +---------------------------------+
 ( Request a protected   )     | AuthCookie sets custom error    |
 ( page, but user hasn't )---->| document and returns            |
 ( authenticated (no     )     | FORBIDDEN. Apache abandons      |      
 ( session key cookie)   )     | current request and creates sub |      
 (-----------------------)     | request for the error document. |<-+
                               | Error document is a script that |  |
                               | generates a form where the user |  |
                 return        | enters authentication           |  |
          ^------------------->| credentials (login & password). |  |
         / \      False        +---------------------------------+  |
        /   \                                   |                   |
       /     \                                  |                   |
      /       \                                 V                   |
     /         \               +---------------------------------+  |
    /   Pass    \              | User's client submits this form |  |
   /   user's    \             | to the LOGIN URL, which calls   |  |
   | credentials |<------------| AuthCookie->login().            |  |
   \     to      /             +---------------------------------+  |
    \authen_cred/                                                   |

lib/Apache/AuthCookie.pm  view on Meta::CPAN

                                              V
         (---------------------)              ^
         ( Request a protected )              |
         ( page, user has a    )--------------+
         ( session key cookie  )
         (---------------------)


 *  The session key that the client gets can be anything you want.  For
    example, encrypted information about the user, a hash of the
    username and password (similar in function to Digest
    authentication), or the user name and password in plain text
    (similar in function to HTTP Basic authentication).

    The only requirement is that the authen_ses_key function that you
    create must be able to determine if this session_key is valid and
    map it back to the originally authenticated user ID.

=head1 METHODS

=head2 authen_cred($r, @credentials)

lib/Apache/AuthCookie.pm  view on Meta::CPAN

=over 4

=item 1.

The ACTION of the form must be /LOGIN (or whatever you defined in your
server configuration as handled by the ->login() method - see example
in the SYNOPSIS section).

=item 2.

The various user input fields (username, passwords, etc.) must be
named 'credential_0', 'credential_1', etc. on the form.  These will
get passed to your authen_cred() method.

=item 3.

You must define a form field called 'destination' that tells
AuthCookie where to redirect the request after successfully logging
in.  Typically this value is obtained from C<$r-E<gt>prev-E<gt>uri>.
See the login.pl script in t/eg/.

=back

In addition, you might want your login page to be able to tell why
the user is being asked to log in.  In other words, if the user sent
bad credentials, then it might be useful to display an error message
saying that the given username or password are invalid.  Also, it
might be useful to determine the difference between a user that sent
an invalid auth cookie, and a user that sent no auth cookie at all.  To
cope with these situations, B<AuthCookie> will set
C<$r-E<gt>subprocess_env('AuthCookieReason')> to one of the following values.

=over 4

=item I<no_cookie>

The user presented no cookie at all.  Typically this means the user is

lib/Apache/AuthCookie.pm  view on Meta::CPAN

This will make it so that AuthCookie will decode your C<requires> directives
using the configured character set.  You really only need to do this if you
have used non-ascii characters in any of your C<requires> directives in
httpd.conf.  e.g.:

 requires user programmør

=head1 ABOUT SESSION KEYS

Unlike the sample AuthCookieHandler, you have you verify the user's
login and password in C<authen_cred()>, then you do something
like:

    my $date = localtime;
    my $ses_key = MD5->hexhash(join(';', $date, $PID, $PAC));

save C<$ses_key> along with the user's login, and return C<$ses_key>.

Now C<authen_ses_key()> looks up the C<$ses_key> passed to it and
returns the saved login.  I use Oracle to store the session key and
retrieve it later, see the ToDo section below for some other ideas.

lib/Apache2/AuthCookie.pm  view on Meta::CPAN

=back

By using AuthCookie versus Apache's built-in AuthBasic you can design
your own authentication system.  There are several benefits.

=over 4

=item 1.

The client doesn't *have* to pass the user credentials on every
subsequent access.  If you're using passwords, this means that the
password can be sent on the first request only, and subsequent
requests don't need to send this (potentially sensitive) information.
This is known as "ticket-based" authentication.

=item 2.

When you determine that the client should stop using the
credentials/session key, the server can tell the client to delete the
cookie.  Letting users "log out" is a notoriously impossible-to-solve
problem of AuthBasic.

lib/Apache2/AuthCookie.pm  view on Meta::CPAN


 (-----------------------)     +---------------------------------+
 ( Request a protected   )     | AuthCookie sets custom error    |
 ( page, but user hasn't )---->| document and returns            |
 ( authenticated (no     )     | HTTP_FORBIDDEN. Apache abandons |      
 ( session key cookie)   )     | current request and creates sub |      
 (-----------------------)     | request for the error document. |<-+
                               | Error document is a script that |  |
                               | generates a form where the user |  |
                 return        | enters authentication           |  |
          ^------------------->| credentials (login & password). |  |
         / \      False        +---------------------------------+  |
        /   \                                   |                   |
       /     \                                  |                   |
      /       \                                 V                   |
     /         \               +---------------------------------+  |
    /   Pass    \              | User's client submits this form |  |
   /   user's    \             | to the LOGIN URL, which calls   |  |
   | credentials |<------------| AuthCookie->login().            |  |
   \     to      /             +---------------------------------+  |
    \authen_cred/                                                   |

lib/Apache2/AuthCookie.pm  view on Meta::CPAN

                                              V
         (---------------------)              ^
         ( Request a protected )              |
         ( page, user has a    )--------------+
         ( session key cookie  )
         (---------------------)


 *  The session key that the client gets can be anything you want.  For
    example, encrypted information about the user, a hash of the
    username and password (similar in function to Digest
    authentication), or the user name and password in plain text
    (similar in function to HTTP Basic authentication).

    The only requirement is that the authen_ses_key function that you
    create must be able to determine if this session_key is valid and
    map it back to the originally authenticated user ID.

=head1 METHODS

=head2 authorize(): int

lib/Apache2/AuthCookie.pm  view on Meta::CPAN

=over 4

=item 1.

The ACTION of the form must be /LOGIN (or whatever you defined in your
server configuration as handled by the ->login() method - see example
in the SYNOPSIS section).

=item 2.

The various user input fields (username, passwords, etc.) must be
named 'credential_0', 'credential_1', etc. on the form.  These will
get passed to your authen_cred() method.

=item 3.

You must define a form field called 'destination' that tells
AuthCookie where to redirect the request after successfully logging
in.  Typically this value is obtained from C<$r-E<gt>prev-E<gt>uri>.
See the login.pl script in t/eg/.

=back

In addition, you might want your login page to be able to tell why
the user is being asked to log in.  In other words, if the user sent
bad credentials, then it might be useful to display an error message
saying that the given username or password are invalid.  Also, it
might be useful to determine the difference between a user that sent
an invalid auth cookie, and a user that sent no auth cookie at all.  To
cope with these situations, B<AuthCookie> will set
C<$r-E<gt>subprocess_env('AuthCookieReason')> to one of the following values.

=over 4

=item I<no_cookie>

The user presented no cookie at all.  Typically this means the user is

lib/Apache2/AuthCookie.pm  view on Meta::CPAN

C<$r-E<gt>auth_type-E<gt>logout($r);>.

Note that if you don't necessarily trust your users, you can't count
on cookie deletion for logging out.  You'll have to expire some
server-side login information too.  AuthCookie doesn't do this for
you, you have to handle it yourself.

=head1 ABOUT SESSION KEYS

Unlike the sample AuthCookieHandler, you have you verify the user's
login and password in C<authen_cred()>, then you do something
like:

    my $date = localtime;
    my $ses_key = MD5->hexhash(join(';', $date, $PID, $PAC));

save C<$ses_key> along with the user's login, and return C<$ses_key>.

Now C<authen_ses_key()> looks up the C<$ses_key> passed to it and
returns the saved login.  I use Oracle to store the session key and
retrieve it later, see the ToDo section below for some other ideas.

lib/Apache2_4/AuthCookie.pm  view on Meta::CPAN

=back

By using AuthCookie versus Apache's built-in AuthBasic you can design your own
authentication system.  There are several benefits.

=over 4

=item 1.

The client doesn't *have* to pass the user credentials on every subsequent
access.  If you're using passwords, this means that the password can be sent on
the first request only, and subsequent requests don't need to send this
(potentially sensitive) information.  This is known as "ticket-based"
authentication.

=item 2.

When you determine that the client should stop using the credentials/session
key, the server can tell the client to delete the cookie.  Letting users "log
out" is a notoriously impossible-to-solve problem of AuthBasic.

lib/Apache2_4/AuthCookie.pm  view on Meta::CPAN


 (-----------------------)     +---------------------------------+
 ( Request a protected   )     | AuthCookie sets custom error    |
 ( page, but user hasn't )---->| document and returns            |
 ( authenticated (no     )     | HTTP_FORBIDDEN. Apache abandons |      
 ( session key cookie)   )     | current request and creates sub |      
 (-----------------------)     | request for the error document. |<-+
                               | Error document is a script that |  |
                               | generates a form where the user |  |
                 return        | enters authentication           |  |
          ^------------------->| credentials (login & password). |  |
         / \      False        +---------------------------------+  |
        /   \                                   |                   |
       /     \                                  |                   |
      /       \                                 V                   |
     /         \               +---------------------------------+  |
    /   Pass    \              | User's client submits this form |  |
   /   user's    \             | to the LOGIN URL, which calls   |  |
   | credentials |<------------| AuthCookie->login().            |  |
   \     to      /             +---------------------------------+  |
    \authen_cred/                                                   |

lib/Apache2_4/AuthCookie.pm  view on Meta::CPAN

                                              V
         (---------------------)              ^
         ( Request a protected )              |
         ( page, user has a    )--------------+
         ( session key cookie  )
         (---------------------)


 *  The session key that the client gets can be anything you want.  For
    example, encrypted information about the user, a hash of the
    username and password (similar in function to Digest
    authentication), or the user name and password in plain text
    (similar in function to HTTP Basic authentication).

    The only requirement is that the authen_ses_key function that you
    create must be able to determine if this session_key is valid and
    map it back to the originally authenticated user ID.

=head1 METHODS

=head2 authen_cred()

lib/Apache2_4/AuthCookie.pm  view on Meta::CPAN

=over 4

=item 1.

The ACTION of the form must be /LOGIN (or whatever you defined in your
server configuration as handled by the C<-E<gt>login()> method - see example in
the SYNOPSIS section).

=item 2.

The various user input fields (username, passwords, etc.) must be named
'credential_0', 'credential_1', etc. on the form.  These will get passed to
your C<authen_cred()> method.

=item 3.

You must define a form field called 'destination' that tells AuthCookie where
to redirect the request after successfully logging in.  Typically this value is
obtained from C<$r-E<gt>prev-E<gt>uri>.  See the login.pl script in t/eg/.

=back

In addition, you might want your login page to be able to tell why the user is
being asked to log in.  In other words, if the user sent bad credentials, then
it might be useful to display an error message saying that the given username
or password are invalid.  Also, it might be useful to determine the difference
between a user that sent an invalid auth cookie, and a user that sent no auth
cookie at all.  To cope with these situations, B<AuthCookie> will set
C<$r-E<gt>subprocess_env('AuthCookieReason')> to one of the following values.

=over 4

=item I<no_cookie>

The user presented no cookie at all.  Typically this means the user is
trying to log in for the first time.

lib/Apache2_4/AuthCookie.pm  view on Meta::CPAN

C<$r-E<gt>auth_type-E<gt>logout($r);>.

Note that if you don't necessarily trust your users, you can't count on cookie
deletion for logging out.  You'll have to expire some server-side login
information too.  AuthCookie doesn't do this for you, you have to handle it
yourself.

=head1 ABOUT SESSION KEYS

Unlike the sample AuthCookieHandler, you have you verify the user's login and
password in C<authen_cred()>, then you do something like:

    my $date = localtime;
    my $ses_key = Digest::SHA::sha256_hex(join(';', $date, $PID, $PAC));

save C<$ses_key> along with the user's login, and return C<$ses_key>.

Now C<authen_ses_key()> looks up the C<$ses_key> passed to it and returns the
saved login.  I use a database to store the session key and retrieve it later.

=head1 FREQUENTLY ASKED QUESTIONS

t/Skeleton/AuthCookieHandler.pm  view on Meta::CPAN

    # Here I'm just using setting the session
    # key to the credentials and delaying authentication.
    #
    # Similar to HTTP Basic Authentication, only not base 64 encoded
    join(":", @creds);
}

sub authen_ses_key ($$$) {
    my $self = shift;
    my $r = shift;
    my($user, $password) = split(/:/, shift, 2);

    # Authenticate use here...
    return $user;
}

1;

t/htdocs/docs/login.pl  view on Meta::CPAN

    $form .= "<!-- creds: @{$creds} -->\n";
}

$form .= <<HERE;
<FORM METHOD="POST" ACTION="/LOGIN">
<TABLE WIDTH=60% ALIGN=CENTER VALIGN=CENTER>
<TR><TD ALIGN=CENTER>
<H1>This is a secure document</H1>
</TD></TR>
<TR><TD ALIGN=LEFT>
<P>Failure reason: '$reason'.  Please enter your login and password to authenticate.</P>
</TD>
<TR><TD>
<INPUT TYPE=hidden NAME=destination VALUE="$uri">

</TD></TR>
<TR><TD>
<TABLE ALIGN=CENTER>
<TR>
<TD ALIGN=RIGHT><B>Login:</B></TD>
<TD><INPUT TYPE="text" NAME="credential_0" SIZE=10 MAXLENGTH=10></TD>
</TR>
<TR>
<TD ALIGN=RIGHT><B>Password:</B></TD>
<TD><INPUT TYPE="password" NAME="credential_1" SIZE=8 MAXLENGTH=8></TD>
</TR>
<TR>
<TD COLSPAN=2 ALIGN=CENTER><INPUT TYPE="submit" VALUE="Continue"></TD>
</TR></TABLE>
</TD></TR></TABLE>
</FORM>
</BODY>
</HTML>
HERE

t/lib/Sample/Apache/AuthCookieHandler.pm  view on Meta::CPAN

    return join ':', map { uri_escape_utf8($_) } @creds;
}

sub authen_ses_key ($$$) {
    my ($self, $r, $ses_key) = @_;

    # NOTE: uri_escape_utf8() was used to encode this so we have to decode
    # using UTF-8.  We don't rely on $self->encoding($r) here because if an
    # encoding other than UTF-8 is configured in t/conf/extra.conf.in, then the
    # wrong encoding gets used here.
    my($user, $password) =
        map { decode('UTF-8', uri_unescape($_)) }
        split /:/, $ses_key, 2;

    if ($user eq 'programmer' && $password eq 'Hero') {
        return $user;
    }
    elsif ($user eq 'some-user') {
        return $user;
    }
    elsif ($user eq '0') {
        return $user;
    }
    elsif ($user eq '程序员') { # programmer in chinese, at least according to google translate
        return $user;

t/lib/Sample/Apache2/AuthCookieHandler.pm  view on Meta::CPAN

    # This would really authenticate the credentials 
    # and return the session key.
    # Here I'm just using setting the session
    # key to the escaped credentials and delaying authentication.
    return join ':', map { uri_escape_utf8($_) } @creds;
}

sub authen_ses_key ($$$) {
    my ($self, $r, $cookie) = @_;

    my ($user, $password) =
        map { decode('UTF-8', uri_unescape($_)) }
        split /:/, $cookie, 2;

    $r->server->log_error("authen_ses_key entry");

    $r->server->log_error("user=$user pass=$password cookie=$cookie");

    if ($user eq 'programmer' && $password eq 'Hero') {
        return $user;
    }
    elsif ($user eq 'some-user') {
        return $user;
    }
    elsif ($user eq '0') {
        return $user;
    }
    elsif ($user eq '程序员') { # programmer in chinese, at least according to google translate
        return $user;

t/lib/Sample/Apache2/AuthCookieHandler.pm  view on Meta::CPAN

<HEAD>
<TITLE>Enter Login and Password</TITLE>
</HEAD>
<BODY onLoad="document.forms[0].credential_0.focus();">
<FORM METHOD="POST" ACTION="/LOGIN">
<TABLE WIDTH=60% ALIGN=CENTER VALIGN=CENTER>
<TR><TD ALIGN=CENTER>
<H1>This is a secure document</H1>
</TD></TR>
<TR><TD ALIGN=LEFT>
<P>Failure reason: '$reason'.  Please enter your login and password to authenticate.</P>
</TD>
<TR><TD>
<INPUT TYPE=hidden NAME=destination VALUE="$uri">

</TD></TR>
<TR><TD>
<TABLE ALIGN=CENTER>
<TR>
<TD ALIGN=RIGHT><B>Login:</B></TD>
<TD><INPUT TYPE="text" NAME="credential_0" SIZE=10 MAXLENGTH=10></TD>
</TR>
<TR>
<TD ALIGN=RIGHT><B>Password:</B></TD>
<TD><INPUT TYPE="password" NAME="credential_1" SIZE=8 MAXLENGTH=8></TD>
</TR>
<TR>
<TD COLSPAN=2 ALIGN=CENTER><INPUT TYPE="submit" VALUE="Continue"></TD>
</TR></TABLE>
</TD></TR></TABLE>
</FORM>
</BODY>
</HTML>
HERE

t/real.t  view on Meta::CPAN

    like($r->content, qr/Failure reason: 'bad_credentials'/,
         'invalid credentials');
};

subtest 'AuthAny' => sub {
    plan tests => 3;

    my $r = POST('/LOGIN', [
        destination  => '/docs/authany/get_me.html',
        credential_0 => 'some-user',
        credential_1 => 'mypassword'
    ]);

    is($r->header('Location'), '/docs/authany/get_me.html',
       'Location header is correct');

    is($r->header('Set-Cookie'), 
       'Sample::AuthCookieHandler_WhatEver=some-user:mypassword; path=/',
       'Set-Cookie header is correct');

    is($r->code, 302, 'redirect code is correct');
};

# should fail because all requirements are not met
subtest 'AuthAll' => sub {
    plan tests => 3;

    my $r = GET(
        '/docs/authall/get_me.html',
        Cookie => 'Sample::AuthCookieHandler_WhatEver=some-user:mypassword'
    );

    is($r->code(), 403, 'unauthorized if requirements are not met');

    # should pass, ALL requirements are met
    $r = GET(
        '/docs/authall/get_me.html',
        Cookie => 'Sample::AuthCookieHandler_WhatEver=programmer:Hero'
    );

t/real.t  view on Meta::CPAN

    like($data, qr#"/docs/protected/get_me\.html\?foo=bar"#,
         'input query string exists in desintation');
};

# should succeed (any requirement is met)
subtest 'AuthAny' => sub {
    plan tests => 3;

    my $r = GET(
        '/docs/authany/get_me.html',
        Cookie => 'Sample::AuthCookieHandler_WhatEver=some-user:mypassword'
    );

    like($r->content, qr/Congratulations, you got past AuthCookie/,
         'AuthAny access allowed');

    # any requirement, username=0 works.
    $r = GET(
        '/docs/authany/get_me.html',
        Cookie => 'Sample::AuthCookieHandler_WhatEver=0:mypassword'
    );

    like($r->content, qr/Congratulations, you got past AuthCookie/,
         'username=0 access allowed');

    # no AuthAny requirements met
    $r = GET(
        '/docs/authany/get_me.html',
        Cookie => 'Sample::AuthCookieHandler_WhatEver=nouser:mypassword'
    );

    is($r->code, 403, 'AuthAny forbidden');
};

# local authz provider test for 2.4 (works same as authany on older versions)
subtest 'Authz Provider' => sub {
    plan tests => 1;

    my $r = GET(

t/real.t  view on Meta::CPAN

         'myuser=programmer access allowed');
};

# login with username=0 works
subtest 'login with username=0' => sub {
    plan tests => 2;

    my $r = POST('/LOGIN', [
        destination  => '/docs/authany/get_me.html',
        credential_0 => '0',
        credential_1 => 'mypassword'
    ]);

    is($r->code, 302, 'username=0 login produces redirect');
    is($r->header('Location'), '/docs/authany/get_me.html',
       'redirect header exists, and contains expected url');
};

subtest 'parameter encoding' => sub {
    plan tests => 5;

t/real.t  view on Meta::CPAN

subtest 'XSS: no embedded script' => sub {
    plan tests => 1;

    my $r = POST('/LOGIN', [
        destination => q{"><script>alert('123')</script>}
    ]);

    ok index($r->content, q{<script>alert('123')</script>}) == -1;
};

subtest 'preserve / in password' => sub {
    plan tests => 1;

    my $r = POST('/LOGIN', [
        destination  => '/docs/protected/get_me.html',
        credential_0 => 'fail',
        credential_1 => 'one/two'
    ]);

    like($r->content, qr/creds: fail one\/two/,
         'read form data handles "/" conversion with encoded +');



( run in 0.997 second using v1.01-cache-2.11-cpan-49f99fa48dc )