I'm learning PHP, and I'm curious if there are elegant solutions to designing an authentication interface for a website.
Basically, I will have three types of users: Regular Users, Admins, and "Power users".
each type of user will have its own actions.
Here is a brief sketch of my current code:
# Regular User = Non-Authenticated
interface IRegularUser {
# Will return an object of a class that implements this interface
public static function loginUser ($name, $pass);
}
# Authenticated User
class AuthUser extends RegularUser {
public function logoutUser();
}
interface IAdmin {..}
interface IPowerUser {...}
class User implements IRegularUser {}
class Admin extends AuthUser implements IAdmin {}
class PowerUser extends AuthUser implements IPowerUser {}
In the script that checks for correct login, I would write something similar to this:
$user = new User();
$user = $user->loginUser($_POST['username'], $_POST['password']);
if (get_class($user) == get_class(new Admin()))
# Redirect to admin_home.html
else if (get_class($user) == get_class(new PowerUser()))
# Redirect to power_user_home.html
I am trying to use the OOP features of PHP, in order to establish some constraints between the entities in my application (Ex: Only by acquiring an User object, you can then login, and acquire an object of type Admin, or PowerUser).
I feel that my authentication system is kind of clunky, and I would like to know if there is a common design pattern for a webapp authentication system.
Actually, its not that easy as it seems at first glance. There are 3 different aspects when dealing with users, that have roles. The "system" you're looking for would consists of these components:
Query adapter
Its responsible for finding records match against some adapter. This can query against almost any type, be it MySQL table, XML file or Mongo DB collection. All your query adapters would implement this interface:
interface QueryAdapter
{
public function recordValid($username, $password);
}
Access Control List (ACL/RBAC)
It must be separated layer, that deals with roles and their permissions. Typically
$roleManager = new RoleManager();
$roleManager->register(array(
'admin' => 'write, edit, read, delete',
'user' => 'view',
'moderator' => 'write, edit, read'
));
$currentUser = get_that_from_session();
if ($roleManager->isAllowed($currentUser, 'write')) {
// Allow writing
} else {
echo 'You are not allowed to write';
}
Storage adapter
That is responsible for storing passwords and tokens inside either $_SESSION or $_COOKIE. If user clicked on Remember me, then you would store that info inside $_COOKIE, otherwise in $_SESSION.
Typical workflow
To "connect" them all together, you would write it like this : (Keep in mind, this is extremely simplified version of it)
<?php
$queryAdapter = new Query_Adapter_MySQL($pdo); // or $mongo, whatever
if ($queryAdapter->rowExists($_POST['username'], $_POST['password'])) {
$role = $queryAdapter->getRole(); // Let's assume that its "user"
$roleManager = new RoleManager();
$roleManager->register(array(
$role => 'read'
));
if (isset($_POST['rememberMe'])){
$storageAdapter = new StorageAdapter_Cookie();
} else {
$storageAdapter = new StorageAdapten_Session();
}
$storageAdapter->write(array(
'role' => $role,
'passwordHash' => $passwordHash,
'token' => $token
));
}
To check if that user logged in, and has a right to do something, you would simply query an adapter to sees that, like,
<?php
if ($storageAdapter->isLoggedIn()){
if ($roleManager->hasRight($storageAdapter->get('role'), 'read')){
// Allowed to read content
}
}
What would you gain with this approach?
Clear separation of responsibilities (This adheres to Single-Responsibility Principle)
Improved code readability
Adhering to Dependency Injection (thus making unit-testing possible)
You can easily switch from MySQL to another storage, without affecting the rest of your code. You would simply inject an instance of the adapter you're going to use.
Is that it?
Yes. You can also take a look at how Zend Framework implements this
Related
I am making a social website using Zend. The site allows users to become friends and access each other's profiles and blogs. I also want users to have control over their privacy, which can take parameters "Friends Only" and "Public". I looked at Zend_Acl but it seems to be only able to to handle single user's accessibility not users have relationship. Any ideas about the best way to do this?
For your purposes, if you use Zend_Acl, you should look at assertions.
Given the complex nature of the relationships between users in your applications, most of the access rules you will query seem very dynamic so they will largely rely on assertions that can use more complex logic to determine accessibility.
You should be able to accomplish what you want using Zend_Acl though.
You may set up an ACL rule like this:
$acl->allow('user', 'profile', 'view', new My_Acl_Assertion_UsersAreFriends());
The ACL assertion itself:
<?php
class My_Acl_Assertion_UsersAreFriends implements Zend_Acl_Assert_Interface
{
public function assert(Zend_Acl $acl,
Zend_Acl_Role_Interface $role = null,
Zend_Acl_Resource_Interface $resource = null,
$privilege = null)
{
return $this->_usersAreFriends();
}
protected function _usersAreFriends()
{
// get UserID of current logged in user
// assumes Zend_Auth has stored a User object of the logged in user
$user = Zend_Auth::getInstance()->getStorage();
$userId = $user->getId();
// get the ID of the user profile they are trying to view
// assume you can pull it from the URL
// or your controller or a plugin can set this value another way
$userToView = $this->getRequest()->getParam('id', null);
// call your function that checks the database for the friendship
$usersAreFriends = usersAreFriends($userId, $userToView);
return $usersAreFriends;
}
}
Now with this assertion in place, the access will be denied if the 2 user IDs are not friends.
Check it like:
if ($acl->isAllowed('user', 'profile', 'view')) {
// This will use the UsersAreFriends assertion
// they can view profile
} else {
// sorry, friend this person to view their profile
}
Hope that helps.
I need a solution where authenticated users are allowed access to certain Controllers/Actions based not on their user type :ie. admin or normal user (although I may add this using standard ACL later) but according to the current status of their user.
For example :
Have they been a member of the site for more than 1 week?
Have they filled in their profile fully?
Actually, now that I think about it, kind of like they have on this site with their priviledges and badges.
For dynamic condition-based tests like you are describing, you can use dynamic assertions in your Zend_Acl rules.
For example:
class My_Acl_IsProfileComplete implements Zend_Acl_Assert_Interface
{
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function assert(Zend_Acl $acl,
Zend_Acl_Role_Interface $role = null,
Zend_Acl_Resource_Interface $resource = null,
$privilege = null)
{
// check the user's profile
if (null === $this->user){
return false;
}
return $this->user->isProfileComplete(); // for example
}
}
Then when defining your Acl object:
$user = Zend_Auth::getInstance()->getIdentity();
$assertion = new My_Acl_Assertion_IsProfileComplete($user);
$acl->allow($role, $resource, $privilege, $assertion);
Of course, some of the details depend upon the specifics of what you need to check and what you can use in your depend upon what you store in your Zend_Auth::setIdentity() call - only a user Id, a full user object, etc. And the roles, resources, and privileges are completely app-specific. But hopefully this gives the idea.
Also, since the assertion object requires a user object at instantiation, this dynamic rule cannot be added at Bootstrap. But, you can create the core Acl instance with static rules during bootstrap and then register a front controller plugin (to run at preDispatch(), say) that adds the dynamic assertion. This way, the Acl is fully populated by the time you get to your controllers where presumably you would be checking them.
Just thinking out loud.
I set up a Zend_Acl and Zend_Auth scheme where user is authenticated using Zend_Auth_Adapter_Ldap and stored in session. I use a controller plugin to check if $auth->hasIdentity() and $acl->isAllowed() to display login form if needed.
What I want to do is to add login cookies (my implementation of best practices), and API keys in addition to the session check in Zend_Auth. I also need to switch the role to 'owner', on content created by the user.
My concerns:
Login cookie should only be used as fallback if regular session auth fails, and thus the session should be authenticated
API keys should be used as fallback if both login cookie and session cookie fails
I don't want to store the password anywhere, it should only reside in LDAP
I need persistent storage of the identity, as looking it up in LDAP is not possible without full username and password
The role is dependent both on LDAP group membership (which needs to be persistently stored), and if the identity should be considered owner of the content (meaning it's changing in between requests, unless admin)
What's a good pattern / approach to solve this using Zend Framework MVC and Zend_Auth + Zend_Acl ?
you can create your own adapter/storage classes, with implementing Zend_Auth_Adpater_Interface and Zend_Auth_Storage_Interface
In these classes, you can re-use original adapters (like LDAP) or storages, and only write the code that implements your auth rules.
for example, using multiple sources for the Zend_Auth_Adapter :
<?php
class My_Auth_Adapter implements Zend_Auth_Adapter_Interface
{
private $ldapAdapter;
private $cookieAdapter;
private $apiKeyAdapter;
public function __construct($ldapAdapter, $cookieAdapter, $apiKeyAdapter) {
{
$this->ldapAdapter = $ldapAdapter;
$this->cookieAdapter = $cookieAdapter;
$this->apyKeyAdapter = $apiKeyAdapter;
}
public function authenticate()
{
if ($this->ldapAdapter->authenticate()) {
//return the Zend_Auth_Restult
} elseif ($this->cookieAdapter->authenticate() {
//return the result
} elseif ($this->apiKeyAdapter->authenticate() {
//return the result
} else {
//Create and return a Zend_Auth_Result which prevents logging in
}
}
}
I am not sure to understand your login rules, but the concept remains the same for the Storage class :
<?php
class My_Auth_Storage implements Zend_Auth_Storage_Interface
private $sessionStorage;
private $cookieStorage;
private $apiStorage;
public function read()
{
if (!$this->sessionStorage->isEmpty())
{
return $this->sessionStorage->read();
} elseif (!$this->cookieStorage->isEmpty())
{
return $this->cookieStorage->read();
} //And so one, do not forget to implement all the interface's methods
With this implementation, you can have multiple credential sources, and multiple session storage engines (cookie, session, db, or whatever you want to use).
For your acl concerns, you can fetch the LDAP group in you controller plugin and store it wherever you need, after authentication. You can then use a second plugin that checks ACLs on each request.
I have a CMS built on the Zend Framework. It uses Zend_Auth for "CMS User" authentication. CMS users have roles and permissions that are enforced with Zend_Acl. I am now trying to create "Site Users" for things like an online store. For simplicity sake I would like to use a separate instance of Zend_Auth for site users. Zend_Auth is written as a singleton, so I'm not sure how to accomplish this.
Reasons I don't want to accomplish this by roles:
Pollution of the CMS Users with Site Users (visitors)
A Site User could accidentally get elevated permissions
The users are more accurately defined as different types than different roles
The two user types are stored in separate databases/tables
One user of each type could be signed in simultaneously
Different types of information are needed for the two user types
Refactoring that would need to take place on existing code
In that case, you want to create your own 'Auth' class to extend and remove the 'singleton' design pattern that exists in Zend_Auth
This is by no means complete, but you can create an instance and pass it a 'namespace'. The rest of Zend_Auth's public methods should be fine for you.
<?php
class My_Auth extends Zend_Auth
{
public function __construct($namespace) {
$this->setStorage(new Zend_Auth_Storage_Session($namespace));
// do other stuff
}
static function getInstance() {
throw new Zend_Auth_Exception('I do not support getInstance');
}
}
Then where you want to use it, $auth = new My_Auth('CMSUser'); or $auth = new My_Auth('SiteUser');
class App_Auth
{
const DEFAULT_NS = 'default';
protected static $instance = array();
protected function __clone(){}
protected function __construct() {}
static function getInstance($namespace = self::DEFAULT_NS) {
if(!isset(self::$instance[$namespace]) || is_null(self::$instance[$namespace])) {
self::$instance[$namespace] = Zend_Auth::getInstance();
self::$instance[$namespace]->setStorage(new Zend_Auth_Storage_Session($namespace));
}
return self::$instance[$namespace];
}
}
Try this one , just will need to use App_Auth instead of Zend_Auth everywhere, or App_auth on admin's area, Zend_Auth on front
that is my suggestion :
i think you are in case that you should calculate ACL , recourses , roles dynamically ,
example {md5(siteuser or cmsuser + module + controller)= random number for each roles }
and a simple plugin would this role is allowed to this recourse
or you can build like unix permission style but i guess this idea need alot of testing
one day i will build one like it in ZF :)
i hope my idea helps you
You're mixing problems. (not that I didn't when I first faced id)
Zend_Auth answers the question "is that user who he claims to be"? What you can do is to add some more info to your persistence object. Easiest option is to add one more column into your DB and add it to result.
i did up a minimalistic Command Pattern example in PHP after reading up about it. i have a few questions ...
i'll like to know if what i did is right? or maybe too minimal, thus reducing the point of the command pattern
interface ICommand {
function execute($params);
}
class LoginCommand implements ICommand {
function execute($params) {
echo "Logging in : $params[user] / $params[pass] <br />";
$user = array($params["user"], $params["pass"]);
// faked users data
$users = array(
array("user1", "pass1"),
array("user2", "pass2")
);
if (in_array($user, $users)) {
return true;
} else {
return false;
}
}
}
$loginCommand = new LoginCommand();
// $tries simulate multiple user postbacks with various inputs
$tries = array(
array("user" => "user1", "pass" => "pass1"),
array("user" => "user2", "pass" => "pass1"),
array("user" => "user2", "pass" => "PaSs2")
);
foreach ($tries as $params) {
echo $loginCommand->execute($params) ? " - Login succeeded!" : " - Login FAILED!";
echo " <br />";
}
i am wondering if there is any difference from simply putting this LoginCommand into a simple function say in the Users class?
if LoginCommand is better fit for a class, won't it be better if it were a static class so i can simply call LoginCommand::execute() vs needing to instanciate an object 1st?
The point of the Command Pattern is being able to isolate distinct functionality into an object (the command), so it can be reused across multiple other objects (the commanders). Usually, the Commander also passes a Receiver to the Command, e.g. an object that the command is targeted at. For instance:
$car = new Car;
echo $car->getStatus(); // Dirty as Hell
$carWash = new CarWash;
$carWash->addProgramme('standard',
new CarSimpleWashCommand,
new CarDryCommand,
new CarWaxCommand);
$carWash->wash();
echo $car->getStatus(); // Washed, Dry and Waxed
In the above example, CarWash is the Commander. The Car is the Receiver and the programme are the actual Commands. Of course I could have had a method doStandardWash() in CarWash and made each command a method in CarWash, but that is less extensible. I would have to add a new method and command whenever I wanted to add new programmes. With the command pattern, I can simply pass in new Commands (think Callback) and create new combinations easily:
$carWash->addProgramme('motorwash',
new CarSimpleWashCommand,
new CarMotorWashCommand,
new CarDryCommand,
new CarWaxCommand);
Of course, you could use PHP's closures or functors for this too, but let's stick to OOP for this example. Another thing where the Commands come in handy, is when you have more than one Commander that needs the Command functionality, e.g.
$dude = new Dude;
$dude->assignTask('washMyCarPlease', new CarSimpleWashCommand);
$dude->do('washMyCarPlease', new Car);
If we had hardcoded the washing logic into the CarWash, we would now have to duplicate all code in the Dude. And since a Dude can do many things (because he is human), the list of tasks he can do, will result in a terrible long class.
Often, the Commander itself is also a Command, so you can create a Composite of Commands and stack them into a tree. Commands often provide an Undo method as well.
Now, looking back at your LoginCommand, I'd say it doesn't make much sense to do it this way. You have no Command object (it's the global scope) and your Command has no Receiver. Instead it returns to the Commander (which makes the global scope the Receiver). So your Command does not really operate on the Receiver. It is also unlikely, that you will need the abstraction into an Command, when doing the login is only ever done in one place. In this case, I'd agree the LoginCommand is better placed into an Authentication adapter, maybe with a Strategy pattern:
interface IAuthAdapter { public function authenticate($username, $password); }
class DbAuth implements IAuthAdapter { /* authenticate against database */ }
class MockAuth implements IAuthAdapter { /* for UnitTesting */ }
$service = new AuthService();
$service->setAdapter(new DbAuth);
if( $service->authenticate('JohnDoe', 'thx1183') ) {
echo 'Successfully Logged in';
};
You could do it somewhat more Command-like:
$service = new LoginCommander;
$service->setAdapter(new DbAuth);
$service->authenticate(new User('JohnDoe', 'thx1138'));
if($user->isAuthenticated()) { /* ... */}
You could add the authenticate method to the User of course, but then you would have to set the Database adapter to the User in order to do the authentication, e.g.
$user = new User('JohnDoe', 'thx1138', new DbAuth);
if ( $user->authenticate() ) { /* ... */ }
That would be possible too, but personally, I don't see why a User should have an Authentication adapter. It doesn't sound like something a user should have. A user has the Credentials required by an Authentication adapter, but the not the adapter itself. Passing the adapter to the user's authenticate method would be an option though:
$user = new User('JohnDoe', 'thx1138');
if ( $user->authenticateAgainst($someAuthAdapter) ) { /* ... */ }
Then again, if you are using ActiveRecord, then your user will know about the database anyway and then you could simply dump all the above and write the entire authenticatation code into the user.
As you can see, it boils down to how you are setting up your application. And that brings us to to the most important point: Design Patterns offer solutions to common problems and they let us allow to speak about these without having to define tons of terms first. That's cool, but often, you will have to modify the patterns to make them solve your concrete problem. You can spend hours theorizing about architecture and which patterns to use and you wont have written a single code. Don't think too much about if a pattern is 100% true to the suggested definition. Make sure your problem is solved.