7 Authentication means ensuring the user is who they claim to be.
11 Once a user's identity is known and authenticated, we can decide what they are
16 The process of authenticating the user is, almost always, asking the user to
17 prove they know a secret that only the real user would know.
19 Ways of proving you know a secret are:
23 =item cleartext password
25 Just tell the secret to the other side to prove you know it.
27 =item challange/response
29 Prove you know the secret without revealing it, and without the possibility of
30 an attacker gaining access even if they get a copy of the data sent from the
37 The server sends some random data, unique for each request
41 The client combines the random data with the secret, and digests that
45 The server combines the data with the password as well, and makes sure the
46 digest is the same as what the user sent.
50 Can possibly be implemented optionally using javascript on the client side if
53 =item public key cryptography
63 =item Role based classification
65 Each user plays in several roles. Restricted actions can then check that the
66 user belongs to a certain role (for example, for the 'delete' action you might
67 want to ensure that a user is an 'editor').
69 Since users are allowed to be in an arbitrary amount of roles the logic is
74 Cascading on top of roles, and more tightly coupled with catalyst is the
75 possibility of access control lists in two namespaces:
81 The perl package namespace
89 For example, you could say that the controller MyApp::C::AdminPanel requires
92 This should resemble the way apache can provide access control for
93 C<< <Location ...> >> and C<< <Directory ...> >>.
97 =head1 ASPECTS OF IMPLEMENTATION
101 Authentication needs to take care of two things:
107 Storage of the user data
111 Authentication of credentials against storage
115 Ideally the two are decoupled, with the following call chain relationship:
117 When application asks the authentication plugin to verify credentials, the
118 credential verification plugin asks storage retrieve the user based on the user
119 ID (the credential plugin knows what part of the credential is a user ID).
121 A storage must return an object representing the user, whose API is irrelevant
124 It is up to the user to synchronize the user info storage with the credential
125 plugin, so that the value retrieved from storage is the value the credential
128 For example, the user/password credential plugin will look something like this:
131 my ( $c, %opts ) = @_;
132 my $user = $opts{user} || $c->auth_get_user( %opts ); # note that
133 # credential plugin methods should accept a user object if they are
136 if ( $user->password eq $opts->{password} ) {
141 A situation where login/password authentication would have to be changed is if
142 the credential system uses /etc/passwd storage, for example. Since /etc/passwd
143 contains hashes of passwords, we can't compare the password directly. The
144 credential system must know to take care of this.
146 The login/password plugin should probably be a bit more lax, making checks on
147 $user to see what the user supports:
149 if (my $hash = eval { $user->hashed_password }) {
150 my $digest = Digest->new( $user->hash_algorithm );
152 $digest->digest eq $hash; # yes, this is binay, not hex or whatever
153 } elsif (my $crypt = eval { $user->crypted_password }) {
154 return crypt( $password, $crypt ) eq $crypt;
155 } elsif (my $clear = eval { $user->password }) {
156 return $passwd eq $clear;
158 Catalyst::Exception->throw("password storage scheme possibilities exhausted");
161 Or less optimistically
163 if ( $user->supports(password => "hashed") ) {
164 my $hash = $user->hashed_password;
168 C<supports> in the user base class should look like this:
171 my ( $self, @path ) = @_;
173 my $cursor = $self->_supports; # this is a nested hash
176 ref $cursor or return undef;
177 $cursor = $cursor->{ shift @path };
180 return $cursor && !ref($cursor);
183 Which just goes through a nested hash, populated by the implementation class.
185 The structure of this hash should be converged by the community, and should
186 look something like this for a plugin that supports everything I can think of:
200 kerberos => 1, # maybe this needs to be deeper? i dunno
204 In the event that the storage backend implies a certain format, the credential
205 verification and storage plugins should ship together, for convenience.
207 =head3 Testing for authentication
209 Once authenticated the authentication plugin sets the C<user> accessor in the
210 context object to contain the user object.
212 =head3 Stores are just models
214 To make things more flexible, stores are really just models.
216 To make things easy a "default" auth storage can be [automatically] selected
217 for the user for the more common situation of one auth driver per app:
220 my ( $c, %opts ) = @_;
222 ( $opts->{store} || $c->find_auth_store )->auth_get_user( %opts );
225 sub find_auth_store {
228 $c->config->{auth}{default_store} || ...;
231 Ofcourse, %opts should not be just delegated blindly in the production
232 versions. Perhaps even L<Params::Validate> is in order.
234 =head3 Integration with Catalyst::Plugin::Session
236 If a session plugin is loaded (C<< $c->isa("Catalyst::Plugin::Session) >>), it
237 should also store the user ID in C<< $c->session->{user} >> unless
238 C<< $c->config->{authentication}{use_session} >> is a false value (true by
243 Authorization uses the same storage backend as authentication - that's why they
246 Like the credential verification part, the authorization system simply assumes
247 that the necesseary information is in the object representing the user.
249 The API required should be strictly documented, in order to minimize confusion.
251 For example, the role authorization plugin may require a C<roles> method:
253 use Quantum::Superpositions qw/all any/;
255 sub authz_check_roles {
256 my ( $c, @required_roles ) = @_;
258 all(@required_roles) == $c->user->roles;
261 Which must be arranged by the authentication storage plugin's user class.
263 In order to facilitate this all storage plugins, either trivial ones, should
264 use factory methods for generating the user object. This allows the user to
265 override the method, replacing the user class with their own, extended class
266 that provides the necessary glue.
268 =head1 DESIRED PLUGINS
270 =head2 Credential Verification
276 Requires a C<password> method from the user, checks for equality
280 Requires a C<crypted_passwd> method from the user, checks for equality after crypt
282 =item challenge response
284 pretends to be a login/password compatible plugin, and will fall back to it,
285 unless the client can use javascript to create a digest of the password and a
286 seed instead of sending the actual password.
290 Send 401 status code, read back authentication headers
294 a passive authentication plugin that checks whether the user logged in using
295 the engine's backend (e.g. apache) and provides a handle object representing
296 that user that is not tied to any storage.
304 User table, contains arbitrary fields and relationships.
306 This should simply provide the interface for storage plugins based on a table
311 FIXME I'm not quite sure how LDAP looks from an OO perspective.
315 Implies credential verification for the various types of passwords htpasswd
318 Can use extra info field for additional interface glue. For example:
322 split(",", $self->info);
327 A subset of htpasswd, can use comment field for extended info like htpasswd.
329 =item perl hash of objects
331 a hash of user object keyed by user ID can prove very convenient for rapid
344 A complex interface that layers on roles and automatically checks role
345 membership for certain areas of an application based on a perl data structure.
351 Avoid new formats - there are enough standard formats out there that we can
352 use. No need for XML, YAML, etc authentication support in the core of these plugins.
354 These plugins can be supplemented by ::Simple namespaces that have the extra sugar.
356 This is one of the reasons Authentication::Simple sucked so bad - it's file
357 parser was half baked, it was very inefficient, and about 10x as many lines of
358 code it could have been if the user simply had to put a perl hash of arrays in
366 =item Look into L<Apache::AuthCookie>
368 < kgftr|konobi> nothingmuch: Apache::AuthCookie has a really nice way of
369 dealing with multiple types of auth (like roles, groups, etc)
376 This tree proposes a list the classes in the authentication/authorization
377 plugins, as well as their methods.
379 It's purpose is to help synchronize the authen/authz efforts, and is not to be
380 considered a formal spec.
382 * Authentication (umbrella plugin)
383 - set_authenticated( $user ) # set $c->user, and $c->session->{user_id} = $user->id
384 - logout # delete $c->user, delete $c->session->{user_id}
385 - user # currently logged in user, if any
386 - prepare (extended) # checks session to see if a user should be logged in
388 - id (the value that can be used to get the user with)
390 method name, or hierarchy
391 The methods listed under a hierarchy mean the methods you can call
392 if $user->supports( qw/password clear/ ) then you can $user->password safely
405 the storage plugin is a thin wrapper around a store model, that
406 provides a notion of a default store for easy dwimming.
407 it's two methods are just dump delegations to $c->config->{authentication}{store}
409 - user_supports( $id )
411 generates an object, puts it in $c->{authetnciation}{store}
412 this object inherits the model class but is not really a model,
413 instead it delegates to the real DBIC model under it
414 * ... # each store impl has a dual life as a plugin
416 provides the actual storage model glue. For example, in DBIC get_user
417 will do a retrieve on the user table.
418 - get_user( $id ) # $id is username, or other credential a user might type in
420 like calling ->supports on the user, but applies to all users
421 if one user supports X but another doesn't, the global check will say they both don't
423 A nested hash in $c->config->{authentication}{entries}
425 user_id => $user_obj || $hash_ref
427 Hash ref will auto instantiate into a user obj that provides a default
428 implementation of supports and some accessors, based on the keys
431 - roles # has_many rel
433 - crypted_password # column
434 - hashed_password # column
435 - hash_algorithm # column
437 * UNIX (Unix::PasswdFile, Unix::GroupFile)
438 provides simple role based authentication
439 implies the Password credential checker
447 - groups/roles # aliases
449 uses group password to temporary add a group/role (newgrp)
450 stores the added groups in the session data
451 * Htpasswd (Apache::Htpasswd)
452 has optional role based authentication in the info field
453 ($c->config->{authentication}{htpasswd}{role_delimiter} defaults to ',')
454 implies the Password credential checker
456 - password # htpasswd -p
457 - crypted_password # htpasswd -d
458 - hashed_password # htpasswd -s, htpasswd -m
460 * Credential (plugin)
461 all $user args can be either user objects, or user ids that will be queried from
462 $c->config->{authentication}{store}
464 - login( $user, $password )
465 tries hashed password, crypted password, and password based on caps of user obj
466 * CRAM (requires session)
467 challange is the session ID
468 response is hash( challange + ( password | crypted_password | hashed_password ) )
469 possibly backed by javascript on the client side
470 - authenticate_cram( $user, $response )
471 hashes $c->sessionid and password variant based on config
472 * SRP (probably useless, but at least take a look - SSL uses it)
473 possibly backed by javascript on the client side
474 * HTTP Auth (like Catalyst::Plugin::Authentication::CDBI::Basic does)
475 subclasses Credential::Password, and just calls login automatically from prepare
476 takes a value to $c->forward to in case the user isn't authenticated, to generate 401
478 logs in a user without a store, by checking apache's authentication data
480 all of these are Password subclasses
486 * Store/Credential hybrid
488 storage dependent... querying the database is tied with credential checking
502 * SASL (Authen::SASL)
503 * Authorization (plugin)
505 checks a user's membership in roles
506 uses has_roles( @roles ) if the user class has that method (assumed to be more efficient)
507 uses roles and compares on it's own otherwise
508 - check_user_roles( [ $user ], @roles )
509 returns boolean, whether $c->user (or supplied user) has specified roles
510 - assert_user_roles( [ $user ] @roles )
511 throws an exception if $c->user (or supplied user) doesn't have the specified roles
513 allows to add ACL rules to sections of the catalyst app
514 ACLs apply to perl namespace (MyApp::C::Foo "contains" MyApp::C::Foo::Bar, etc)
515 or they can apply to URL space (/foo contains /bar)
517 takes a value to $c->forward to in case the user isn't authorized (error page)
518 - restrict_access ( $arg_like_forward_takes, @roles ) # roles to allow
519 - deny_access ( $arg_like_forward_takes, @roles ) # roles to disallow
521 * Login Form (plugin)
522 this is the plugin that will be used for CRAM stuff in javascript
523 http://pajhome.org.uk/crypt/ - bsd license hashes in js
524 http://pajhome.org.uk/crypt/md5/chaplogin.html
525 http://perl-md5-login.sourceforge.net/
526 it's just for convenience
527 - login_form( [ $store ] )
528 uses user_supports and isa checks on credentials to create a form
529 that makes sense for the store provided if no store is provided uses
530 $c->config->{authenticion}{store}
532 using the same info about stores/credentials, will pick up the data
533 used to generate a form, and find out what form data to use to login
535 if $c->config->{authentication}{auto_login} is on will call process_login automatically