Writing an Auth plugin for SSL client certificates
Hello, TL;DR I'd like to write an authentication plugin that behaves like Dancer2::Plugin::Auth::Extensible but does NOT require a user's password but instead relies on the SSL variables SSL_CLIENT_VERIFY and SSL_CLIENT_S_DN_Email. Background: I have a Dancer2 application and currently using Dancer2::Plugin::Auth::Extensible to authenticate my users with username/password. I'm currently using two providers (realms), Config and Database. Config is mainly for testing purpose but I like the idea of multiple realms. I also need the concept of roles, so DPAE is just great and everything works like a charm. Now I want to switch to client certificate authentication. I have set up an Apache as a proxy to the plackup server. Apache handles the SSL stuff, sets the variables <Location /> RequestHeader set SSL_CLIENT_VERIFY "%{SSL_CLIENT_VERIFY}s" RequestHeader set SSL_CLIENT_S_DN_Email "%{SSL_CLIENT_S_DN_Email}s" </Location> and then redirects to my Dancer2 app (at 127.0.0.1:5000). Within my routes I can access those variables with my $client_verify = request->env->{'HTTP_SSL_CLIENT_VERIFY'} // ''; my $username = request->env->{'HTTP_SSL_CLIENT_S_DN_EMAIL'} // ''; This works. Apache sets HTTP_SSL_CLIENT_VERIFY to 'SUCCESS' if the authentication worked. What I now want to achieve is: IFF $client_verify eq 'SUCCESS' THEN let the user $username in. Don't show a /login page. Pick the user's details from the database (or whatever realm(s) is/are configured). Don't show a /logout page. In short: allow everything what Dancer2::Plugin::Auth::Extensible allows but without ever asking for a password or redirecting to a /login page if the two variables are set (and, of course, $username can be found in the DB). A very naive first approach was to use a "before" hook to set the "logged_in_user" value: hook before => sub { my $client_verify = request->env->{'HTTP_SSL_CLIENT_VERIFY'} // ''; my $username = request->env->{'HTTP_SSL_CLIENT_S_DN_EMAIL'} // ''; if ($client_verify eq 'SUCCESS' and $username) { session logged_in_user => $username; } }; This seems to work but looks very dirty to me. So I thought I'd better write a plugin that behaves like a Dancer2::Plugin::Auth::Extensible plugin but doesn't require a password and rather uses the two environment variables instead. Is Dancer2::Plugin::Auth::Extensible even the right place (base) for such a plugin? Cheers
Hi, I would not start with Dancer2::Plugin::Auth::Extensible - it has lots of stuff you do not need in your use case - but start with an empty plugin. It's really quite simple, you need just one function (beware, untested code!): package Dancer2::Plugin::Auth::SSLVerify; use strict; use warnings; use utf8; use Dancer2::Plugin; sub require_ssl_verified { my $dsl = shift; my $coderef = shift; return sub { my $app = $dsl->app; my $request = $app->request; my $session = $app->session; my $client_verify = $request->env->{'HTTP_SSL_CLIENT_VERIFY'} // ''; my $username = $request->env->{'HTTP_SSL_CLIENT_S_DN_EMAIL'} // ''; if ($client_verify eq 'SUCCESS' and $username) { $session->set( logged_in_user => $username ); return $coderef->($dsl); } else { return $dsl->redirect( '/not_authorized' ); # or something } }; } register 'require_ssl_verified' => \&require_ssl_verified; register_plugin; 1; Then, you can use this plugin in your route definitions: use Dancer2::Plugin::Auth::SSLVerify; get '/whatever' => require_ssl_verified sub { # your route logic here. }; Hope this helps. Regards, Lennart On 12-11-17 17:09, perlduck wrote:
Hello,
TL;DR
I'd like to write an authentication plugin that behaves like Dancer2::Plugin::Auth::Extensible but does NOT require a user's password but instead relies on the SSL variables SSL_CLIENT_VERIFY and SSL_CLIENT_S_DN_Email.
Background:
I have a Dancer2 application and currently using Dancer2::Plugin::Auth::Extensible to authenticate my users with username/password. I'm currently using two providers (realms), Config and Database. Config is mainly for testing purpose but I like the idea of multiple realms. I also need the concept of roles, so DPAE is just great and everything works like a charm.
Now I want to switch to client certificate authentication. I have set up an Apache as a proxy to the plackup server. Apache handles the SSL stuff, sets the variables
<Location /> RequestHeader set SSL_CLIENT_VERIFY "%{SSL_CLIENT_VERIFY}s" RequestHeader set SSL_CLIENT_S_DN_Email "%{SSL_CLIENT_S_DN_Email}s" </Location>
and then redirects to my Dancer2 app (at 127.0.0.1:5000).
Within my routes I can access those variables with
my $client_verify = request->env->{'HTTP_SSL_CLIENT_VERIFY'} // ''; my $username = request->env->{'HTTP_SSL_CLIENT_S_DN_EMAIL'} // '';
This works. Apache sets HTTP_SSL_CLIENT_VERIFY to 'SUCCESS' if the authentication worked. What I now want to achieve is:
IFF $client_verify eq 'SUCCESS' THEN let the user $username in.
Don't show a /login page. Pick the user's details from the database (or whatever realm(s) is/are configured). Don't show a /logout page.
In short: allow everything what Dancer2::Plugin::Auth::Extensible allows but without ever asking for a password or redirecting to a /login page if the two variables are set (and, of course, $username can be found in the DB).
A very naive first approach was to use a "before" hook to set the "logged_in_user" value:
hook before => sub { my $client_verify = request->env->{'HTTP_SSL_CLIENT_VERIFY'} // ''; my $username = request->env->{'HTTP_SSL_CLIENT_S_DN_EMAIL'} // ''; if ($client_verify eq 'SUCCESS' and $username) { session logged_in_user => $username; } };
This seems to work but looks very dirty to me. So I thought I'd better write a plugin that behaves like a Dancer2::Plugin::Auth::Extensible plugin but doesn't require a password and rather uses the two environment variables instead.
Is Dancer2::Plugin::Auth::Extensible even the right place (base) for such a plugin?
Cheers
_______________________________________________ dancer-users mailing list dancer-users@dancer.pm http://lists.preshweb.co.uk/mailman/listinfo/dancer-users
Hi Lennart, thank you, this looks promising. I will give that a try and see how I can combine this approach with the DPAE code because I like (and need) its concept of roles and other user details. The "require_ssl_verified" function at least needs to lookup the given user in a DB to see if he's not only authenticated but also a valid user for this very application. Regards Dirk Am 13.11.2017 14:42, schrieb Lennart Hengstmengel:
Hi,
I would not start with Dancer2::Plugin::Auth::Extensible - it has lots of stuff you do not need in your use case - but start with an empty plugin. It's really quite simple, you need just one function (beware, untested code!):
package Dancer2::Plugin::Auth::SSLVerify;
use strict; use warnings; use utf8;
use Dancer2::Plugin;
sub require_ssl_verified { my $dsl = shift; my $coderef = shift; return sub { my $app = $dsl->app; my $request = $app->request; my $session = $app->session;
my $client_verify = $request->env->{'HTTP_SSL_CLIENT_VERIFY'} // ''; my $username = $request->env->{'HTTP_SSL_CLIENT_S_DN_EMAIL'} // ''; if ($client_verify eq 'SUCCESS' and $username) { $session->set( logged_in_user => $username ); return $coderef->($dsl); } else { return $dsl->redirect( '/not_authorized' ); # or something } };
}
register 'require_ssl_verified' => \&require_ssl_verified; register_plugin;
1;
Then, you can use this plugin in your route definitions:
use Dancer2::Plugin::Auth::SSLVerify;
get '/whatever' => require_ssl_verified sub { # your route logic here.
};
Hope this helps.
Regards, Lennart
On 12-11-17 17:09, perlduck wrote:
Hello,
TL;DR
I'd like to write an authentication plugin that behaves like Dancer2::Plugin::Auth::Extensible but does NOT require a user's password but instead relies on the SSL variables SSL_CLIENT_VERIFY and SSL_CLIENT_S_DN_Email.
Background:
I have a Dancer2 application and currently using Dancer2::Plugin::Auth::Extensible to authenticate my users with username/password. I'm currently using two providers (realms), Config and Database. Config is mainly for testing purpose but I like the idea of multiple realms. I also need the concept of roles, so DPAE is just great and everything works like a charm.
Now I want to switch to client certificate authentication. I have set up an Apache as a proxy to the plackup server. Apache handles the SSL stuff, sets the variables
<Location /> RequestHeader set SSL_CLIENT_VERIFY "%{SSL_CLIENT_VERIFY}s" RequestHeader set SSL_CLIENT_S_DN_Email "%{SSL_CLIENT_S_DN_Email}s" </Location>
and then redirects to my Dancer2 app (at 127.0.0.1:5000).
Within my routes I can access those variables with
my $client_verify = request->env->{'HTTP_SSL_CLIENT_VERIFY'} // ''; my $username = request->env->{'HTTP_SSL_CLIENT_S_DN_EMAIL'} // '';
This works. Apache sets HTTP_SSL_CLIENT_VERIFY to 'SUCCESS' if the authentication worked. What I now want to achieve is:
IFF $client_verify eq 'SUCCESS' THEN let the user $username in.
Don't show a /login page. Pick the user's details from the database (or whatever realm(s) is/are configured). Don't show a /logout page.
In short: allow everything what Dancer2::Plugin::Auth::Extensible allows but without ever asking for a password or redirecting to a /login page if the two variables are set (and, of course, $username can be found in the DB).
A very naive first approach was to use a "before" hook to set the "logged_in_user" value:
hook before => sub { my $client_verify = request->env->{'HTTP_SSL_CLIENT_VERIFY'} // ''; my $username = request->env->{'HTTP_SSL_CLIENT_S_DN_EMAIL'} // ''; if ($client_verify eq 'SUCCESS' and $username) { session logged_in_user => $username; } };
This seems to work but looks very dirty to me. So I thought I'd better write a plugin that behaves like a Dancer2::Plugin::Auth::Extensible plugin but doesn't require a password and rather uses the two environment variables instead.
Is Dancer2::Plugin::Auth::Extensible even the right place (base) for such a plugin?
Cheers
_______________________________________________ dancer-users mailing list dancer-users@dancer.pm http://lists.preshweb.co.uk/mailman/listinfo/dancer-users
_______________________________________________ dancer-users mailing list dancer-users@dancer.pm http://lists.preshweb.co.uk/mailman/listinfo/dancer-users
On 11/12/2017 9:09 AM, perlduck wrote:
Is Dancer2::Plugin::Auth::Extensible even the right place (base) for such a plugin? I don't see why not. Aside from the potential security issues noted, see https://github.com/PerlDancer/Dancer2-Plugin-Auth-Extensible and create a new provider.
--john -- John J. McDermott, CPLP Learning and Performance Consultant jjm at jkintl.com 575/737-8556 Check out my blog posts at blog.learningtree.com Add an A for the Arts To STEM and get STEAM and a strong engine to move forward.
Am 13.11.2017 18:41, schrieb John McDermott, CPLP:
On 11/12/2017 9:09 AM, perlduck wrote:
Is Dancer2::Plugin::Auth::Extensible even the right place (base) for such a plugin? I don't see why not. Aside from the potential security issues noted, see https://github.com/PerlDancer/Dancer2-Plugin-Auth-Extensible and create a new provider.
--john
Well, as I see it, Dancer2::Plugin::Auth::Extensible requires all its providers to authenticate with uid/pwd. If a route "requires_login" and the "logged_in_user" isn't set in the session, then the Dancer2::Plugin::Auth::Extensible wants to redirect to the /login route. I already built my own Provider (based on Provider::Database) and overwrote (using "around") the "authenticate_user" method so it returns "true" when the uid is set in the environment. But still, DPAE shows the login page. When I press SUBMIT, then my "authenticate_user" method is called and returns true (ignoring the POST parameters). What I really want is: If a route "requires_login" and "logged_in_user" is not yet set, then watch out for the SSL environment variables (instead of redirecting to the login page). The more I explain, the more I think DPAE is the wrong place for my purpose. Kind of rubber-ducking. ;-)
participants (3)
-
John McDermott, CPLP -
Lennart Hengstmengel -
perlduck