|
|
| |
Catalyst::Plugin::Authentication::Internals(3) |
User Contributed Perl Documentation |
Catalyst::Plugin::Authentication::Internals(3) |
Catalyst::Plugin::Authentication::Internals - All about authentication Stores
and Credentials
Catalyst::Plugin::Authentication provides a standard authentication interface to
application developers using the Catalyst framework. It is designed to allow
application developers to use various methods of user storage and credential
verification. It is also designed to provide for minimal change to the
application when switching between different storage and credential
verification methods.
While Catalyst::Plugin::Authentication provides the interface to
the application developer, the actual work of verifying the credentials and
retrieving users is delegated to separate modules. These modules are called
Credentials and storage backends, or Stores, respectively. For
authentication to function there must be at least one credential and one
store. A pairing of a store and a credential is referred to as a
Realm. There may be any number of realms defined for an application,
though most applications will not require more than one or two.
The details of using this module can be found in the
Catalyst::Plugin::Authentication documentation.
What follows is an explanation of how the module functions
internally and what is required to implement a credential or a store.
There are two main entry points you need to be aware of when writing a store or
credential module. The first is initialization and the second is during the
actual call to the Catalyst application's authenticate method.
A simplified description of the authentication process
follows:
Initialization
Realm Setup - for each realm:
1) The Realm is instantiated using new() method
2) The Store is instantiated using new() method
3) The Credential Instantiated using new() method
4) Credential and Store objects tied to realm for use during
requests
Authentication
"$c->authenticate( $userinfo,
$realm )" called
1) Credential object retrieved for realm provided
2) Credential's authenticate() method called with authinfo
and realm object for current realm
The realm object and the authinfo hash are provided to
the credential object's authenticate call. In most cases the credential object
will attempt to retrieve a user using the realm's find_user() method,
which by default relays the call directly to the Store's find_user()
method. It will then usually compare the retrieved user's information with the
information provided in the $authinfo hash. This is
how the default 'Password' credential functions. If the credentials match, the
authenticate() method should return a user object.
3) User object stored in session
If the user object supports session storage, the
successfully authenticated user will be placed in session storage. This is
done by calling the realm object's persist_user() method. The
persist_user() routine by default calls the Store's
for_session() method, which should return serialized data (IE a
scalar). This serialized data is passed back to the store via the
from_session() method, so the data should contain enough information
for the store to recreate / reload the user.
Sessions - Per-Request operations
When any user-related activity occurs, and
$c->authenticate has not yet been called, the
Catalyst::Plugin::Authentication module will attempt to restore the persisted
user (normally from the session if one is available). There is only one step
in this process:
1) Store object's from_session() is called
The serialized data previously returned by the store's
for_session() method is provided to the from_session() method.
The from_session() method should return a valid user object.
Note that the for_session() is only called during the
original $c->authenticate() call, so if
changes are made to the user that need to be reflected in your session data,
you will want to call the
$c->persist_user() method - which will
perform the session storage process again (complete with call to
for_session()).
More detailed information about these processes is below.
When the authentication module is loaded, it reads it's configuration to
determine the realms to set up for the application and which realm is to be
the default. For each realm defined in the application's config,
Catalyst::Plugin::Authentication instantiates both a new credential object and
a new store object. See below for the details of how credentials and stores
are instantiated.
NOTE: The instances created will remain active throughout
the entire lifetime of the application, and so should be relatively
lightweight. Care should be taken to ensure that they do not grow, or retain
information per request, because they will be involved in each
authentication request and could therefore substantially hurt memory
consumption over time.
When "$c->authenticate()" is called from
within an application, the objects created in the initialization process come
into play. "$c->authenticate()" takes two
arguments. The first is a hash reference containing all the information
available about the user. This will be used to locate the user in the store
and verify the user's credentials. The second argument is the realm to
authenticate against. If the second argument is omitted, the default realm is
assumed.
The main authentication module then locates the credential and
store objects for the realm specified and calls the credential object's
"authenticate()" method. It provides three
arguments, first the application object, or $c, then
a reference to the store object, and finally the hashref provided in the
"$c->authenticate" call. The main
authentication module expects the return value to be a reference to a user
object upon successful authentication. If it receives anything aside from a
reference, it is considered to be an authentication failure. Upon success,
the returned user is marked as authenticated and the application can act
accordingly, using "$c->user" to access
the authenticated user, etc.
Astute readers will note that the main
Catalyst::Plugin::Authentication module does not interact with the store in
any way, save for passing a reference to it to the credential. This is
correct. The credential object is responsible for obtaining the user from
the provided store using information from the userinfo hashref and/or data
obtained during the credential verification process.
There are two parts to an authentication store, the store object and the user
object.
Writing a store is actually quite simple. There are only five methods that must
be implemented. They are:
new() - instantiates the store object
find_user() - locates a user using data contained in the hashref
for_session() - prepares a user to be stored in the session
from_session() - does any restoration required when obtaining a user from the session
user_supports() - provides information about what the user object supports
STORE METHODS
- new( $config, $app, $realm )
- The "new()" method is called only once,
during the setup process of Catalyst::Plugin::Authentication. The first
argument, $config, is a hash reference containing
the configuration information for the store module. The second argument is
a reference to the Catalyst application.
Note that when new() is called, Catalyst has not yet
loaded the various controller and model classes, nor is it definite that
other plugins have been loaded, so your new() method must not
rely on any of those being present. If any of this is required for your
store to function, you should defer that part of initialization until
the first method call.
The "new()" method should
return a blessed reference to your store object.
- find_user( $authinfo, $c )
- This is the workhorse of any authentication store. It's job is to take the
information provided to it via the $authinfo
hashref and locate the user that matches it. It should return a reference
to a user object. A return value of anything else is considered to mean no
user was found that matched the information provided.
How "find_user()"
accomplishes it's job is entirely up to you, the author, as is what
$authinfo is required to contain. Many stores
will simply use a username element in $authinfo
to locate the user, but more advanced functionality is possible and you
may bend the $authinfo to your needs. Be aware,
however, that both Credentials and Stores usually work with the same
$authinfo hash, so take care to avoid
overlapping element names.
Please note that this routine may be called numerous times in
various circumstances, and that a successful match for a user here does
NOT necessarily constitute successful authentication. Your store
class should never assume this and in most cases
$c should not be modified by your store
object.
- for_session( $c, $user )
- This method is responsible for preparing a user object for storage in the
session. It should return information that can be placed in the session
and later used to restore a user object (using the
"from_session()" method). It should
therefore ensure that whatever information provided can be used by the
"from_session()" method to locate the
unique user being saved. Note that there is no guarantee that the same
Catalyst instance will receive both the
"for_session()" and
"from_session()" calls. You should take
care to provide information that can be used to restore a user, regardless
of the current state of the application. A good rule of thumb is that if
"from_session()" can revive the user
with the given information even if the Catalyst application has just
started up, you are in good shape.
- from_session( $c, $frozenuser )
- This method is called whenever a user is being restored from the session.
$frozenuser contains the information that was
stored in the session for the user. This will under normal circumstances
be the exact data your store returned from the previous call to
"for_session()".
"from_session()" should return a valid
user object.
- user_supports( $feature, ... )
- This method allows credentials and other objects to inquire as to what the
underlying user object is capable of. This is pretty-well free-form and
the main purpose is to allow graceful integration with credentials and
applications that may provide advanced functionality based on whether the
underlying user object can do certain things. In most cases you will want
to pass this directly to the underlying user class'
"supports" method. Note that this is
used as a class method against the user class and therefore must be
able to function without an instantiated user object.
OPTIONAL STORE METHODS
If you want your store to be able to auto- create users, then you
can implement these methods:
auto_update_user( $authinfo,
$c, $res )
This method is called if the realm's auto_update_user setting is
true.
auto_create_user( $authinfo,
$c )
This method is called if the realm's auto_create_user setting is
true.
The user object is an important piece of your store module. It will be the part
of the system that the application developer will interact with most. As such,
the API for the user object is very rigid. All user objects MUST
inherit from Catalyst::Authentication::User.
USER METHODS
The routines required by the Catalyst::Plugin::Authentication
plugin are below. Note that of these, only get_object is strictly required,
as the Catalyst::Authentication::User base class contains reasonable
implementations of the rest. If you do choose to implement only the
"get_object()" routine, please read the
base class code and documentation so that you fully understand how the other
routines will be implemented for you.
Also, your user object can implement whatever additional methods
you require to provide the functionality you need. So long as the below are
implemented, and you don't overlap the base class' methods with incompatible
routines, you should experience no problems.
- id( )
- The "id()" method should return a unique
id (scalar) that can be used to retreive this user from the store. Often
this will be provided to the store's
"find_user()" routine as
"id => $user->id" so you should
ensure that your store's "find_user()"
can cope with that.
- supports( $feature, $subfeature ... )
- This method checks to see if the user class supports a particular feature.
It is implemented such that each argument provides a subfeature of the
previous argument. In other words, passing 'foo', 'bar' would return true
if the user supported the 'foo' feature, and the 'bar' feature of 'foo'.
This is implemented in Catalyst::Authentication::User, so if your class
inherits from that, you do not need to implement this and can instead
implement supported_features().
Note: If you want the authentication module to be able
to save your user in the session you must return true when presented
with the feature 'session'.
- supported_features( )
- This method should return a hashref of features supported by the user
class. This is for more flexible integration with some Credentials /
applications. It is not required that you support anything, and returning
"undef" is perfectly acceptable and in
most cases what you will do.
- get( $fieldname )
- This method should return the value of the field matching fieldname
provided, or undef if there is no field matching that fieldname. In most
cases this will access the underlying storage mechanism for the user data
and return the information. This is used as a standard method of accessing
an authenticated user's data, and MUST be implemented by all user objects.
Note: There is no equivalent 'set' method. Each user
class is likely to vary greatly in how data must be saved and it is
therefore impractical to try to provide a standard way of accomplishing
it. When an application developer needs to save data, they should obtain
the underlying object / data by calling get_object, and work with it
directly.
- get_object( )
- This method returns the underlying user object. If your user object is
backed by another object class, this method should return that underlying
object. This allows the application developer to obtain an editable
object. Generally speaking this will only be done by developers who know
what they are doing and require advanced functionality which is either
unforeseen or inconsistent across user classes. If your object is not
backed by another class, or you need to provide additional intermediate
functionality, it is perfectly reasonable to return
$self.
Compared to writing a store, writing a credential is very simple. There is only
one class to implement, and it consists of only two required routines. They
are:
new() - instantiates the credential object
authenticate() - performs the authentication and returns a user object
- new( $config, $app, $realm )
- Like the Store method of the same name, the
"new()" method is called only once,
during the setup process of Catalyst::Plugin::Authentication. The first
argument, $config, is a hash reference containing
the configuration information for the credential module. The second
argument is a reference to the Catalyst application.
$realm is the instantiated Realm object, which you
may use to access realm routines - such as find_user.
Again, when the credential's new() method is called,
Catalyst has not yet loaded the various controller and model
classes.
The new method should perform any necessary setup required and
instantiate your credential object. It should return your instantiated
credential.
- authenticate( $c, $realm, $authinfo )
- This is the workhorse of your credential. When
$c->authenticate() is called the
Catalyst::Plugin::Authentication module retrieves the realm object and
passes it, along with the $authinfo hash to your
credential's authenticate method. Your module should use the
$authinfo hash to obtain the user from the realm
passed, and then perform any credential verification steps necessary to
authenticate the user. This method should return the user object returned
by the authentication store if credential verification succeeded. It
should return undef on failure.
How your credential module performs the credential
verification is entirely up to you. In most cases, the credential will
retrieve a user from the store first (using the stores
find_user() method), and then validate the user's information.
However, this does not have to be the case.
It is perfectly acceptable for your credential to perform
other tasks prior to attempting to retrieve the user from the store. It
may also make sense for your credential to perform activities which help
to locate the user in question, for example, finding a user id based on
an encrypted token. In these scenarios, the
$authinfo hash passed to find_user() can
be different than that which is passed in to
$c->authenticate(). Once again this is
perfectly acceptable if it makes sense for your credential, though you
are strongly advised to note this behavior clearly in your credential's
documentation - as application authors are almost certainly expecting
the user to be found using the information provided to
$c->authenticate().
Look at the Catalyst::Authentication::Credential::Password
module source to see this in action. In order to avoid possible
mismatches between the encrypted and unencrypted passwords, the password
credential actually removes the provided password from the authinfo
array. It does this because, in many cases, the store's password field
will be encrypted in some way, and the password passed to
$c->authenticate is almost certainly in
plaintext.
NOTE: You should always assume that a store is going to use
all the information passed to it to locate the user in question. If
there are fields in the $authinfo hash that you
are sure are specific to your credential, you may want to consider
removing them before user retrieval. A better solution is to place those
arguments that are specific to your credential within their own subhash
named after your module.
The Catalyst::Authentication::Store::DBIx::Class module does
this in order to encapsulate arguments intended specifically for that
module. See the Catalyst::Authentication::Store::DBIx::Class::User
source for details.
Jay Kuri, "jayk@cpan.org"
Copyright (c) 2005 the aforementioned authors. All rights reserved. This program
is free software; you can redistribute it and/or modify it under the same
terms as Perl itself.
Visit the GSP FreeBSD Man Page Interface. Output converted with ManDoc. |