You are on page 1of 20

Copyright IBM Corporation 2013 Trademarks

Sentry 2 and PHP, Part 1: Authentication and access control for


PHP
Page 1 of 20
Sentry 2 and PHP, Part 1: Authentication and access
control for PHP
Vikram Vaswani
Founder
Melonfire
01 October 2013
Authentication and access control are critical to keeping your web application secure. Sentry
2 is a framework-agnostic authentication and authorization system written in PHP. It provides
built-in methods for many common authentication and authorization tasks, allowing you to
efficiently and securely develop public-facing PHP web applications.
View more content in this series
Introduction
When creating a new web application, there are some bits of code you probably find yourself
routinely implementing: a login form, a registration form, a password reset workflow, or a user
account manager. Because these workflows are now fairly standard, 99% of the time you're going
to be writing the same code with minor modifications. So, why not encapsulate it in a set of classes
to save yourself some time?
That's where Sentry 2 comes in. Developed by Cartalyst and licensed under BSD-3, Sentry 2 is a
"framework agnostic authentication and authorization system" written in PHP. It provides a set of
classes that simplify the task of adding standard authentication and access control flows to a PHP
application without compromising on security.
This two-part article series (see Part 2) introduces you to the Sentry 2 API, showing you how to
integrate and use it with your PHP web application. It includes examples of setting up a registration
form with email activation; implementing password reset workflows; searching for user accounts
using various filters; and creating, editing, and deleting user accounts. So come on in, and let's get
started!
Installation
I'll assume throughout this article that you're familiar with HTML and SQL, and that you have a
working Apache/PHP/MySQL development environment. I'll also assume that you know the basics
of working with classes and objects in PHP, as the Sentry 2 package and example code in this
article make use of these concepts.
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 2 of 20
Sentry 2 can be used either as part of a PHP framework (FuelPHP, CodeIgniter or Laravel
are supported by default) or as a stand-alone package that you can integrate into your own
application, whether framework-based or not. To make the examples in this article easier to
understand, I'll use procedural PHP scripts and the Composer auto-loader. However, in a real-
world application, you'd probably be better off using a PHP framework and loading the Sentry 2
classes using your framework's class loader.
To use Sentry 2 with a PHP application, you need to first download and install the package and
its dependencies. The easiest way to do this is with Composer, which you'll need to download
and install if you don't already have it (see Resources for a link). After Composer is in place,
create a working directory (which I'll refer to as $PROJECT for convenience), and then create a
$PROJECT/composer.json file with the information in Listing 1.
Listing 1. Creating $PROJECT/composer.json file
{
"name": "myapp/sentry",
"minimum-stability": "dev",
"require": {
"cartalyst/sentry": "2.0.*",
"illuminate/database": "4.0.*",
"ircmaxell/password-compat": "1.0.*"
}
}
Next, run Composer, as shown in Listing 2. It will begin downloading the Sentry 2 package and
dependencies and setting up its auto-loader.
Listing 2. Running Composer
shell> php composer.phar self-update
shell> php composer.phar install
At the end of this process, your $PROJECT/vendor/ directory should look something like Figure 1.
Figure 1. Directory structure after Composer update
The next step is to set up the database tables to store user, group, and permission information.
Sentry 2 ships with an SQL file containing the necessary commands to set up these tables. You'll
find it in $PROJECT/vendor/cartalyst/sentry/schema/mysql.sql. To use it, first create an empty
MySQL database:
mysql> CREATE DATABASE appdata;
Then, import the table schema into MySQL using a command like the following command.
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 3 of 20
shell> mysql -D appdata -u user -p < mysql.sql
Check that the tables were successfully created using a SHOW TABLES command.
mysql> SHOW TABLES;
You should see output like that shown in Figure 2.
Figure 2. Database tables used by Sentry 2
IBM Security Identity Manager
You can integrate the Sentry 2 user registry with other user registries in your enterprise with
an IBM Security Identity Manager adapter. You can find out more about IBM Security Identity
Manager and other products from IBM Security Systems in the "When Millions Need Access"
white paper.
Most of the examples in this article use the 'users' table. Use the DESC command to see the
structure of this table.
mysql> DESC users;
Figure 3 illustrates the structure of this table.
Figure 3. Structure of Sentry 2 users table
To get things rolling and make it easier to work with the examples that follow, you should also
insert a single user record, as shown in Listing 3.
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 4 of 20
Listing 3. Inserting a single user record
INSERT INTO `users` (`id`, `email`, `password`, `permissions`, `activated`,
`activation_code`, `activated_at`, `last_login`, `persist_code`,
`reset_password_code`, `first_name`, `last_name`, `created_at`, `updated_at`)
VALUES
(1, 'vikram@example.com', '$2y$10$GbX.pVNcGdlDfyDudmE.U.PF7c/
ZajWhKLI9VZ23Ut.mXbQteDBZG', NULL, 1, NULL, NULL, NULL, NULL, NULL,
'Example', 'User', '2013-09-12 11:28:31', '2013-09-12 11:28:31');
Note that the SQL query in Listing 3 creates a user record with an encrypted password. In the
Sentry 2 system, each user must have a unique email address.
Getting started with Sentry 2
The quickest way to learn how Sentry 2 works is with an example. So, I'll dive right into the code
with something simple: user authentication. Consider Listing 4, which illustrates the basic process.
Listing 4. User authentication
<?php
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
);
// set login credentials
$credentials = array(
'email' => 'vikram@example.com',
'password' => 'guessme',
);
// authenticate
try {
$currentUser = Cartalyst\Sentry\Facades\Native\Sentry::
authenticate($credentials, false);
echo 'Logged in as ' . $currentUser->getLogin();
} catch (Exception $e) {
echo 'Authentication error: ' . $e->getMessage();
}
?>
Listing 4 begins by loading the Composer auto-loader script, which takes care of pulling in Sentry
2 components as needed. Then, the Sentry facade's setupDatabaseResolver() method is used
to connect Sentry 2 with the MySQL database set up earlier by passing it a PDO object with a DSN
and database credentials. These preliminary steps are common to every usage of Sentry 2.
After the database connection is initialized, the authenticate() method is used to actually
perform authentication. This method accepts an array containing the user's email address and
password and checks this information against the information in the database. If the credentials
match, a new User object is created to represent the currently logged-in user, and the user
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 5 of 20
information is also stored in the session. User object methods, like getLogin(), getPermissions(),
isActivated() and more, can now be used to retrieve specific information about the user.
Logging in and out
With this information at hand, it's a quick job to adapt Listing 4 to create a login/logout framework
for your web application. Listing 5 shows a traditional login form.
Listing 5. Login form
<html>
<head></head>
<body>
<h1>Login</h2>
<form action="login.php" method="post">
Username: <input type="text" name="username" /> <br/>
Password: <input type="password" name="password" /> <br/>
<input type="submit" name="submit" value="Log In" />
</form>
</body>
</html>
Input entered into this form can be submitted to Listing 6, which uses Sentry 2 to validate
credentials and authenticate the user.
Listing 6. User authentication and login
<?php
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
);
// check for form submission
if (isset($_POST['submit'])) {
try {
// validate input
$username = filter_var($_POST['username'], FILTER_SANITIZE_EMAIL);
$password = strip_tags(trim($_POST['password']));
// set login credentials
$credentials = array(
'email' => $username,
'password' => $password,
);
// authenticate
$currentUser = Cartalyst\Sentry\Facades\Native\Sentry::
authenticate($credentials, false);
echo 'Logged in as ' . $currentUser->getLogin();
} catch (Exception $e) {
echo 'Authentication error: ' . $e->getMessage();
}
}
?>
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 6 of 20
Typically, if a user is already logged in, you'll want to display this information when the user
visits any of your application pages, rather than redisplaying the login form. Because Sentry 2
automatically stores the User object in the session after authentication, it's possible to check for
its presence and conditionally display either the login form or the user's logged-in status. Consider
Listing 7, which merges Listing 5 and Listing 6 and adds the necessary logic.
Listing 7. User authentication with conditional login form display
<?php
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// if form submitted
if (isset($_POST['submit'])) {
try {
// validate input
$username = filter_var($_POST['username'], FILTER_SANITIZE_EMAIL);
$password = strip_tags(trim($_POST['password']));
// set login credentials
$credentials = array(
'email' => $username,
'password' => $password,
);
// authenticate
// if authentication fails, capture failure message
Cartalyst\Sentry\Facades\Native\Sentry::authenticate($credentials, false);
} catch (Exception $e) {
$failMessage = $e->getMessage();
}
}
// check if user logged in
if (Cartalyst\Sentry\Facades\Native\Sentry::check()) {
$currentUser = Cartalyst\Sentry\Facades\Native\Sentry::getUser();
}
?>
<html>
<head></head>
<body>
<?php if (isset($currentUser)): ?>
Logged in as <?php echo $currentUser->getLogin(); ?>
<a href="logout.php">[Log out]</a>
<?php else: ?>
<h1>Log In</h1>
<div><?php echo (isset($failMessage)) ?
$failMessage : null; ?></div>
<form action="login.php" method="post">
Username: <input type="text" name="username" /> <br/>
Password: <input type="password" name="password" /> <br/>
<input type="submit" name="submit" value="Log In" />
</form>
<?php endif; ?>
</body>
</html>
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 7 of 20
Listing 7 uses two additional Sentry 2 methods: check() is a helper method to check if the user is
logged in, and getUser() returns the User object representing the currently logged-in user. Using
these methods, it's quite easy to display appropriate information to the user based on current
authentication status.
The final piece is logging out. This is very simple, just call the logout() method to destroy the User
object in the session and log the user out. Listing 8 has example code.
Listing 8. User de-authentication and logout
<?php
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// log user out
Cartalyst\Sentry\Facades\Native\Sentry::logout();
?>
Securing password resets
If a user forgets his or her password, they'll need a way to reset it. The typical process here is to
generate and send a unique code to the user's email address. The user then visits the website,
passes back the code to confirm the password reset request, and submits a new password.
Sentry 2 comes with various methods that simplify this process:
The getResetPasswordCode() method generates a unique password reset code for a user.
The checkResetPasswordCode() method validates a password reset code submitted by a user.
The attemptResetPassword() attempts to reset a user's password to a new value by validating
the accompanying password reset code.
Let's see this in action. To begin, create a reset password form, which asks the user to enter his or
her email address (Listing 9).
Listing 9. Password reset request form
<html>
<head></head>
<body>
<h1>Reset Password</h2>
<form action="reset-request.php" method="post">
Email address: <br/>
<input type="text" name="email" /> <br/>
<input type="submit" name="submit" value="Submit Request" />
</form>
</body>
</html>
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 8 of 20
The email address submitted by the user goes to a PHP script that initializes the Sentry 2
package, verifies the format of the address supplied, and then attempts to find the corresponding
user record (Listing 10).
Listing 10. Password reset code generation
<?php
if (isset($_POST['submit'])) {
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// validate input and find user record
// send reset code by email to user
try {
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$user = Cartalyst\Sentry\Facades\Native\Sentry::findUserByCredentials(array(
'email' => $email
));
$code = $user->getResetPasswordCode();
$subject = 'Your password reset code';
$message = 'Code: ' . $code;
$headers = 'From: webmaster@example.com';
if (!mail($email, $subject, $message, $headers)) {
throw new Exception('Email could not be sent.');
}
echo 'Password reset code sent.';
exit;
} catch (Exception $e) {
echo $e->getMessage();
exit;
}
}
?>
Listing 10 introduces the findUserByCredentials() method, which searches the user database for
an account matching the supplied credentials and returns the corresponding User object if a match
is found. The User object's getResetPasswordCode() method is then used to generate a unique
reset code for the user, and the mail() function is then used to send this code to the user's email
address.
After the user receives the password reset code, he or she needs to revisit your web application,
use the supplied code to confirm the request, and provide a new password. Listing 11 is an
example of the form the user might see in this case.
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 9 of 20
Listing 11. Password reset form
<html>
<head></head>
<body>
<h1>Reset Password</h2>
<form action="reset-password.php" method="post">
Email address: <br/>
<input type="text" name="email" /> <br/>
Reset code: <br/>
<input type="text" name="code" /> <br/>
New password: <br/>
<input type="password" name="password" /> <br/>
New password (repeat): <br/>
<input type="password" name="password-repeat" /> <br/>
<input type="submit" name="submit" value="Change Password" />
</form>
</body>
</html>
When this form is submitted, the processing script needs to check the reset code supplied by the
user against the code recorded in the user database, using the email address as key. Assuming a
match, the user's password needs to be updated to the new value supplied in the form. Listing 12
demonstrates the necessary business logic using Sentry 2's built-in methods.
Listing 12. Password reset confirmation
<?php
if (isset($_POST['code']) && $_POST['email']) {
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// find user by email address
// attempt password reset
try {
$code = strip_tags($_POST['code']);
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$password = htmlentities($_POST['password']);
$password_repeat = htmlentities($_POST['password-repeat']);
if ($password != $password_repeat) {
throw new Exception ('Passwords do not match.');
}
$user = Cartalyst\Sentry\Facades\Native\Sentry::findUserByCredentials(array(
'email' => $email
));
if ($user->checkResetPasswordCode($code)) {
if ($user->attemptResetPassword($code, $password)) {
echo 'Password successfully reset.';
exit;
} else {
throw new Exception('User password could not be reset.');
}
} else {
throw new Exception('User password could not be reset.');
}
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 10 of 20
} catch (Exception $e) {
echo $e->getMessage();
exit;
}
}
?>
Listing 12 begins by setting up the Sentry 2 object and its database connection. It then checks the
input variables submitted with the request, sanitizing them, checking the email address format,
and verifying that the two passwords supplied are identical. If all these tests pass, the script uses
the findUserByCredentials() method to produce a User object corresponding to the supplied
email address, then invokes the object's checkResetPasswordCode() to check the accuracy of the
supplied code.
If the code provided by the user matches what is recorded in the database, the password reset
request may be considered a genuine one. In this case, the User object's attemptResetPassword()
method is passed the reset code and the new password, and this in turn updates the password
recorded in the user database.
It's important to note from these code listings that although the mechanics of form generation, input
validation, and code transmission do need to be manually coded, the hardest partsgenerating
the reset code, checking the reset code, and actually resetting the passwordare all encapsulated
within Sentry 2 methods, which are simply invoked as needed. This built-in function reduces the
total amount of effort involved in implementing a standard password reset workflow.
Securing user registrations
The previous examples have demonstrated how to authenticate existing users. However, in most
web applications you will typically also need to provide an interface to create new users. Here
too, there are two options: you might wants users to self-register their accounts or you might want
administrators to create their accountsor you might want both.
Sentry 2 provides methods that enable secure implementation of these options. The common
element here is the createUser() method, which accepts an array of key-value pairs and creates
and persists a User object representing this information to the database. Listing 13 provides a
quick example of what it looks like.
Listing 13. User account creation
<?php
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// create user record
try {
$user = Cartalyst\Sentry\Facades\Native\Sentry::createUser(array(
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 11 of 20
'email' => 'test@example.com',
'password' => 'guessme',
'first_name' => 'Test',
'last_name' => 'User',
'activated' => true,
));
} catch (Exception $e) {
echo $e->getMessage();
}
?>
In particular, note the 'activated' key in the array passed to the createUser() method. When
set to false, this key saves the user record to the database but disables the user account until it
is verified. This adds a much-needed element of security when building public-facing registration
forms, as the user needs to additionally verify the account before being permitted to use the
application.
Let's see how this works in practice, with an example registration form (Listing 14).
Listing 14. User account self-registration
<?php
if (isset($_POST['submit'])) {
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// validate input and create user record
// send activation code by email to user
try {
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$fname = strip_tags($_POST['first_name']);
$lname = strip_tags($_POST['last_name']);
$password = strip_tags($_POST['password']);
$user = Cartalyst\Sentry\Facades\Native\Sentry::createUser(array(
'email' => $email,
'password' => $password,
'first_name' => $fname,
'last_name' => $lname,
'activated' => false
));
$code = $user->getActivationCode();
$subject = 'Your activation code';
$message = 'Code: ' . $code;
$headers = 'From: webmaster@example.com';
if (!mail($email, $subject, $message, $headers)) {
throw new Exception('Email could not be sent.');
}
echo 'User successfully registered and activation code sent.';
exit;
} catch (Exception $e) {
echo $e->getMessage();
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 12 of 20
exit;
}
}
?>
<html>
<head></head>
<body>
<h1>Register</h2>
<form action="register.php" method="post">
Email address: <br/>
<input type="text" name="email" /> <br/>
Password: <br/>
<input type="password" name="password" /> <br/>
First name: <br/>
<input type="text" name="first_name" /> <br/>
Last name: <br/>
<input type="text" name="last_name" /> <br/>
<input type="submit" name="submit" value="Create" />
</form>
</body>
</html>
Listing 14 produces a form with fields for email address, password, and first and last names. A
user would self-register by filling out the form and submitting it. On submission, the processing
script would validate the user's input (this step is intentionally kept brief in this and other listings)
and then invoke the createUser() method to create the user account without activating it.
The next step is to validate the information provided by the user. This is done by sending a unique
activation code to the supplied email address and asking the recipient to validate the registration
by visiting the website and entering this code. The activation code can be retrieved using the User
object's getActivationCode() method. All that's left is to transmit it through email to the supplied
email address.
Of course, this is only half of the puzzle. You need one additional script, this one to receive and
check the activation code entered by registrants and then switch their accounts to active. Typically,
the activation code would be embedded in a clickable link in the email message so that when the
user clicks the link he or she is transferred to the confirmation page in your application.
Listing 15 contains the necessary code for this confirmation page, assuming that the link clicked by
the user is of the form 'http://your-site.com/confirm.php?code=[code]&email=[email]'.
Listing 15. User account confirmation
<?php
if (isset($_GET['code']) && $_GET['email']) {
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// find user by email address
// activate user with activation code
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 13 of 20
try {
$code = strip_tags($_GET['code']);
$email = filter_var($_GET['email'], FILTER_SANITIZE_EMAIL);
$user = Cartalyst\Sentry\Facades\Native\Sentry::findUserByCredentials(array(
'email' => $email
));
if ($user->attemptActivation($code)) {
echo 'User activated.';
} else {
throw new Exception('User could not be activated.');
}
} catch (Exception $e) {
echo $e->getMessage();
}
}
?>
There's nothing very complicated about Listing 15. It inspects the incoming request for 'code'
and 'email' variables and, if present, uses the findUserByCredentials() method seen earlier
to identify the user account being activated. It then calls the attemptActivation() method with
the supplied activation code. If the activation code matches what's already in the database, the
account is activated; if not, the activation attempt fails and the account remains inactive.
Of course, if you don't want users to go through the additional step of account activationfor
example, if you're building an intranet application that is only accessible to known users, or if user
accounts will only be created by trusted administratorsthen you can set 'activated' to true in
the call to the createUser() method and user accounts will be created as active accounts.
Searching for users
You've seen a few examples of the findUserByCredentials() method in previous listings, but this
isn't the only game in town when it comes to searching for users.
The getUser() method returns the currently logged-in user.
The findUserByLogin() method returns the user matching the supplied email address.
The findUserByActivationCode() method returns the user matching the supplied activation
code.
The findUserByResetPasswordCode() method returns the user matching the supplied
password reset code.
The findAllUsers() method returns a list of all users (whether active or inactive).
The findAllUsersWithAccess() method returns a list of all users with a specific permission.
The findAllUsersInGroup() method returns a list of all users in a group (groups and
permissions are discussed in the second part of this article).
These methods come in handy when you're trying to find a user based on a fragment of user
informationsomething you'd need, for example, if you added a user search engine to your
application. You can also use them when constructing administration toolsfor example, a user
management tool.
Let's now look at how you'd use Sentry 2 to speed up development of such a user management
tool. Typically, you'd need to implement four basic functions: adding new users, listing existing
users, editing existing users, and deleting users. You've already seen how the createUser()
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 14 of 20
method makes it easy to implement the first one (and there's an example in the code archive as
well), so let's focus on the others.
Listing 16 demonstrates how to list all user accounts in the system:
Listing 16. User account listing
<?php
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// find all users
$users = Cartalyst\Sentry\Facades\Native\Sentry::findAllUsers();
?>
<html>
<head></head>
<body>
<h1>Users</h1>
<table border="1">
<tr>
<td>Email address</td>
<td>First name</td>
<td>Last name</td>
<td>Status</td>
<td>Last login</td>
</tr>
<?php foreach ($users as $u): ?>
<?php $userArr = $u->toArray(); ?>
<tr>
<td><?php echo $userArr['email']; ?></td>
<td><?php echo isset($userArr['first_name']) ?
$userArr['first_name'] : '-'; ?></td>
<td><?php echo isset($userArr['last_name']) ?
$userArr['last_name'] : '-'; ?></td>
<td><?php echo ($userArr['activated'] == 1) ?
'Active' : 'Inactive'; ?></td>
<td><?php echo isset($userArr['last_login']) ?
$userArr['last_login'] : '-'; ?></td>
<td><a href="edit.php?id=<?php echo $userArr['id']; ?>">
Edit</a></td>
<td><a href="delete.php?id=<?php echo $userArr['id']; ?>">
Delete</a></td>
</tr>
<?php endforeach; ?>
</table>
<a href="create.php">Add new user</a>
<body>
</html>
Listing 16 uses the findAllUsers() method to return a list of all users from the Sentry 2 database.
It's now easy to iterate over this collection, convert each object to an array using the toArray()
method, and then display relevant bits of information in a table. Notice that each user record is
accompanied with links that point to edit and delete functions and that contain the unique user
id. More on these later, but in the meanwhile, Figure 4 illustrates an example of the output from
Listing 16.
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 15 of 20
Figure 4. Figure 4: User account listing
Editing and deleting user accounts
Sentry 2 provides built-in methods to update or delete user accounts. Consider Listing 17, which is
the target of the 'delete' links in Listing 16.
Listing 17. User account deletion
<?php
if (isset($_GET['id'])) {
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
new PDO($dsn, $u, $p));
// find user by id and delete
try {
$id = strip_tags($_GET['id']);
$user = Cartalyst\Sentry\Facades\Native\Sentry::findUserById($id);
$user->delete();
echo 'User successfully deleted.';
} catch (Exception $e) {
echo 'User could not be deleted.';
}
}
?>
Listing 17 receives the id of the selected user as a GET request parameter, locates the
corresponding record using the findUserById() method, and then calls the User object's delete()
method to remove the user record from the database.
There's also a save() method, which is useful for making changes to an existing user record.
Consider Listing 18, which uses it to build a simple editing tool for users.
Listing 18. User account modification
<?php
// set up autoloader
require ('vendor\autoload.php');
// configure database
$dsn = 'mysql:dbname=appdata;host=localhost';
$u = 'sentry';
$p = 'g39ejdl';
Cartalyst\Sentry\Facades\Native\Sentry::setupDatabaseResolver(
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 16 of 20
new PDO($dsn, $u, $p));
if (isset($_POST['submit'])) {
try {
$id = strip_tags($_POST['id']);
$user = Cartalyst\Sentry\Facades\Native\Sentry::findUserById($id);
$user->email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$user->first_name = strip_tags($_POST['first_name']);
$user->last_name = strip_tags($_POST['last_name']);
$user->password = strip_tags($_POST['password']);
if ($user->save()) {
echo 'User successfully updated.';
exit;
} else {
throw new Exception('User could not be updated.');
}
} catch (Exception $e) {
echo 'User could not be created.';
exit;
}
} else if (isset($_GET['id'])) {
try {
$id = strip_tags($_GET['id']);
$user = Cartalyst\Sentry\Facades\Native\Sentry::findUserById($id);
$userArr = $user->toArray();
} catch (Exception $e) {
echo 'User could not be found.';
exit;
}
?>
<html>
<head></head>
<body>
<h1>Edit User</h2>
<form action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>"
method="post">
Email address: <br/>
<input type="text" name="email"
value="<?php echo $userArr['email']; ?>" /> <br/>
Password: <br/>
<input type="password" name="password"
value="<?php echo $userArr['password']; ?>" /> <br/>
First name: <br/>
<input type="text" name="first_name"
value="<?php echo $userArr['first_name']; ?>" /> <br/>
Last name: <br/>
<input type="text" name="last_name"
value="<?php echo $userArr['last_name']; ?>" /> <br/>
<input type="hidden" name="id"
value="<?php echo $userArr['id']; ?>" /> <br/>
<input type="submit" name="submit" value="Update" />
</form>
</body>
</html>
<?php
}
?>
Like Listing 17, Listing 18 too receives a user id from Listing 16. It then uses this id to produce the
corresponding User object, convert it to an array, and extract the necessary information to pre-fill
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 17 of 20
the fields of the editing form (including a hidden field for the user id). A user can modify these fields
and then submit the form back for processing.
After being submitted, the user's input is sanitized and validated, and the hidden user id is used to
again retrieve the corresponding User object. The new values submitted through the form are set
as properties of the User object, and the object's save() method is finally invoked to save the new
values to the database.
Summary
As these examples illustrate, Sentry 2 provides a full-featured framework for managing and
authenticating users within a PHP web application. However, what you've seen so far is just the
tip of the iceberg: Sentry 2 also includes features for groups, permissions, and additional security
features like user bans and login throttles.
Sounds interesting? Come back for the second and final part of this article, which will explain these
features and a few others besides. See you then!
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 18 of 20
Downloads
Description Name Size
Sample PHP scripts for this tutorial code-1.zip 8KB
ibm.com/developerWorks/ developerWorks
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 19 of 20
Resources
View Sentry 2 documentation.
Download Composer.
Download Sentry 2 from Github.
Follow the developerWorks security zone on Twitter.
Follow developerWorks on Twitter.
Visit the Security on developerWorks blog to find the latest how-to guides, videos, and
technical articles on developerWorks.
Get Security-related content on the Security site on developerWorks.
developerWorks ibm.com/developerWorks/
Sentry 2 and PHP, Part 1: Authentication and access control for
PHP
Page 20 of 20
About the author
Vikram Vaswani
Vikram Vaswani is the founder and CEO of Melonfire, a consulting services firm with
special expertise in open source tools and technologies. He is also the author of the
books Zend Framework: A Beginners Guide and PHP: A Beginners Guide.
Copyright IBM Corporation 2013
(www.ibm.com/legal/copytrade.shtml)
Trademarks
(www.ibm.com/developerworks/ibm/trademarks/)

You might also like