Contents


Manage and authenticate users easily in IBM Bluemix applications with PHP and the Passport service, Part 1

Add user authentication and management to your application without starting from scratch

Speed development so you can focus on the more unique aspects of your app

Comments

Content series:

This content is part # of # in the series: Manage and authenticate users easily in IBM Bluemix applications with PHP and the Passport service, Part 1

Stay tuned for additional content in this series.

This content is part of the series:Manage and authenticate users easily in IBM Bluemix applications with PHP and the Passport service, Part 1

Stay tuned for additional content in this series.

Whenever I sit down to write a new web application, there are two tasks that I find unavoidable: creating a dashboard to add, modify, and delete users, and implementing a login/logout workflow. As you might imagine, these two pieces are inevitable when building any reasonably sized application...but they're also so standardized and well-understood that implementing them is usually a fairly routine (and boring) exercise for most developers.

In search of a better (as in less tedious) approach, I stumbled across IBM Bluemix's Passport service integration, which provides a way for developers to outsource all the nitty-gritties of user management and authentication to an external API. This significantly reduces the amount of code to be written (and tested) when developing a new application...plus, there's the added bonus of quickly checking "user login" off your to-do list so you can move on to more interesting things.

In this two-part tutorial, I'll walk you through the process of building a simple web application and deploying it on Bluemix. This example application supports multiple users, but I won't be writing any user management code; instead, I'll integrate Bluemix's Passport service and let it handle all the heavy lifting. Keep reading!

Try the demoGet the code on GitHub

What you will need

Normally, user management within an application involves creating a user dashboard with functions to add new users, edit existing users, and activate, deactivate or delete user accounts. Typically, you'd also need a way for users to log into and out of the application, and check if a user is authenticated before offering access to certain functions. Some applications also include additional features, such as role-based access, user profiles, and additional workflows to handle forgotten passwords and user reactivation.

Behind the scenes, implementing all of this usually involves (at minimum) creating a user database, using a secure algorithm to encrypt and validate user passwords, and writing SQL queries to create, update, delete, and authenticate users. With Bluemix's Passport integration, there's no user database to be created or SQL to be written; instead, all these tasks are handled via API calls to the Passport service.

Since the Passport API is REST-compliant, you can access it using any programming language; I'll be using PHP in this article. I'll also use Bootstrap to create a mobile-optimized interface, the Slim PHP micro-framework to manage application flows, the Guzzle PHP client to access the Passport API, and Bluemix for infrastructure and hosting.

Before starting, make sure you have everything that you'll need:

Note: Any application that uses the Passport service must comply with the Inversoft License Agreement. Similarly, any application that uses the Bluemix must comply with Bluemix's terms of use, as described here. Before beginning your project, spend a few minutes reading these requirements and ensuring that your application complies with them.

Step 1: Create the application skeleton

The first step is to initialize a basic application with the Slim PHP micro-framework and the Guzzle PHP HTTP client. These can be easily downloaded and installed using Composer, the PHP dependency manager. Use this Composer configuration file, which should be saved to $APP_ROOT/composer.json ("$APP_ROOT" refers to your project directory):

{
    "require": {
        "slim/slim": "^3.8",
        "slim/php-view": "^2.2",
        "guzzlehttp/guzzle": "^6.3"
    }
}

Next, install using Composer with the command:

shell> php composer.phar install

Once the necessary components have been downloaded via Composer, create two directories: $APP_ROOT/public for all web-accessible files, and $APP_ROOT/views for all views.

shell> cd myapp
shell> mkdir public views

Then, create the file $APP_ROOT/config.php with the following information (you'll fill in the placeholders in Step 3):

<?php
$config = [
  'settings' => [
    'displayErrorDetails' => true, // disable for production
    'passport_api_key' => 'PASSPORT-API-KEY',
    'passport_api_url' => 'PASSPORT-API-URL',
    'passport_app_id'  => 'PASSPORT-APP-ID',
  ]
];

To make it easier to access the application, you can also define a new virtual host in your development environment named "myapp.localhost" and point its document root to $APP_ROOT/public. You should also add a .htaccess file to the $APP_ROOT/public directory with the following settings:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^ index.php [QSA,L]
</IfModule>

Using a virtual host mapped to the $APP_ROOT/public directory allows you to access application routes directly using the virtual host name—for example, http://myapp.localhost/admin/users/index instead of http://localhost/public/admin/users/index. To learn more about virtual hosts and web server configuration for Slim framework applications, see Slim documentation, Slim configuration for Apache, and Composer documentation.

The next step is to create a controller script that will initialize the Slim framework. It will also contain callbacks for the application's routes, with each callback defining the code to be executed when the route is matched to an incoming request. Create this script at $APP_ROOT/public/index.php with the following content:

<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;

require '../vendor/autoload.php';
require '../config.php';

// configure Slim application instance
// initialize application
$app = new \Slim\App($config);

// initialize dependency injection container
$container = $app->getContainer();

// add view renderer
$container['view'] = function ($container) {
  return new \Slim\Views\PhpRenderer("../views/");
};

// index page handler
$app->get('/', function (Request $request, Response $response) {
  return $response->withHeader('Location', $this->router->pathFor('home'));
});

// public page handler
$app->get('/home', function (Request $request, Response $response) {
  return $this->view->render($response, 'home.phtml', [
    'router' => $this->router
  ]);
})->setName('home');

$app->run();

Slim works by defining callback functions for HTTP methods and endpoints. This is done by calling the corresponding Slim method—get() for GET requests, post() for POST requests, and so on—and passing the route to be matched as the first argument to the method. The second argument to the method is an anonymous function, which specifies the actions to be taken when the route is matched to an incoming request.

The script above sets up two handlers (we'll add more shortly). The first is a simple redirection, which redirects all requests for the "/" route to the "/home" route. The second is the "/home" route itself, which renders the content of the $APP_ROOT/views/home.phtml file. Create this file with the content below:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>My App</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->    
  </head>
  <body>

    <div class="container">
      <!-- header area -->
      <div class="panel panel-default">
        <div class="panel-heading clearfix">
          <h4 class="pull-left">Home</h4>
          <div class="btn-group pull-right">
            <a role="button" class="btn btn-primary" href="<?php echo $data['router']->pathFor('home'); ?>">Home</a>
          </div>
        </div>
      </div>  
      <!-- end of header area -->
      
      <!-- content area -->
      <p>This is the home page. It is public and accessible to everyone.</p>
      <!-- end of content area -->
      
      <!-- footer -->
      <div class="container">
      </div> 
      <!-- end of footer -->
    </div> 

  </body>
</html>

This template contains a simple Bootstrap-based user interface with header, footer, and content areas. This basic template will be used for all subsequent pages as well, and only the content area will change.

To see this in action, browse to the "/home" route (either http://myapp.localhost/home or http://localhost/public/home, depending on whether you are using a virtual host or not) and you should see the rendered version of the template above:

Figure 1. Static home page
Static home page
Static home page

Step 2: Create a user registration form

Once you've got the basic application skeleton set up, you can start building the rest of the application. As a first step to implementing user management functionality, create a form that registers new application users with the minimum required fields. Start by defining a "/admin/users/save" route and corresponding callback function, by adding the code below to $APP_ROOT/public/index.php:

<?php

// Slim application initialization - snipped

// user form handler
$app->get('/admin/users/save', function (Request $request, Response $response) {
  $response = $this->view->render($response, 'users-save.phtml', [
    'router' => $this->router
  ]);
  return $response;
})->setName('admin-users-save');

// other callbacks

In essence, this tells Slim to respond to GET requests for the /admin/users/save URL endpoint with the contents of the named template. That template, located at $APP_ROOT/views/users-save.phtml, should contain the form fields needed to create a new user. Here's the code:

<div>
  <form method="post" action="<?php echo $data['router']->pathFor('admin-users-save'); ?>">
    <div class="form-group">
      <label for="fname">First name</label>
      <input type="text" class="form-control" id="fname" name="fname">
    </div>
    <div class="form-group">
      <label for="lname">Last name</label>
      <input type="text" class="form-control" id="lname" name="lname">
    </div>
    <div class="form-group">
      <label for="email">Email address</label>
      <input type="text" class="form-control" id="email" name="email">
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" class="form-control" id="password" name="password">
    </div> 
    <div class="form-group">
      <button type="submit" name="submit" class="btn btn-default">Save</button>
    </div>
  </form>  
</div>

Short and sweet! All it asks for is the user's first name, last name, email address, and password. The email address also serves as a unique identifier when logging in.

When you access the "/admin/users/save" route in your browser, you should see something like this:

Figure 2. User account creation form
User account creation form
User account creation form

The entry point for adding new users is now complete. The next step is to process the data submitted in the form, which first requires that you configure the Passport service and integrate it with Bluemix.

Step 3: Configure the Passport service

You need three pieces of information to begin using the Passport API: an API key, the API URL, and an application ID. To configure a Bluemix service instance, you also need the back-end URL (usually the same as the API URL) and front-end URL for your Passport instance. Here's how you can collect this information:

  • When you first registered your Passport account, you were presented with a success page containing the API URL, front-end URL, and back-end URL. In case you didn't note it at the time, you can always access that information from your account page on the Inversoft website. Here's what it looks like:
    Figure 3. Passport URLs
    Passport URLs
    Passport URLs
  • During the Passport setup process, the Passport setup wizard prompted you to set up an API key. If you didn't note it at the time, browse to the Passport front-end URL and log in with your administrator account credentials. Then, obtain the API key from the "Settings -> API Keys" menu. Here's what it looks like:
    Figure 4. Passport API key
    Passport API key
    Passport API key
  • Also during setup process, the wizard would have prompted you to set up your first application and generated a unique application ID for you. If you didn't note it at the time, browse to the Passport front-end URL and log in with your administrator account credentials. Then, obtain the application ID from the "Settings -> Applications" menu.
    Figure 5. Passport application ID
    Passport application ID
    Passport application ID

Once you have all the necessary information, initialize a new Passport service instance on Bluemix by logging into your Bluemix account and, from the dashboard, clicking the Catalog button. From the resulting list of services, select Application Services and then Passport. Enter the API key, front-end URL, and back-end URL, then click the Create button to create the service. Leave the service unbound for the moment (you'll bind it to your application in Step 8).

Figure 6. Passport service creation on Bluemix
Passport service creation on Bluemix
Passport service creation on Bluemix

Although you eventually want to push the application to Bluemix and connect it to the Passport API by using the credentials from the bound service instance, your application is still under development. So, while you are in this development phase, manually update the $APP_ROOT/config.php file with the API key, API endpoint, and application ID so that you can use the Passport API from your development system and then import this configuration file into your Slim application.

For more information, see the Passport Bluemix service documentation, the Passport and Bluemix integration tutorial, and the Guzzle documentation.

Step 4: Process user registrations

Once you have the API key, API endpoint, and application ID configured, you can begin processing the user registrations that were submitted through the form in Step 2. First, initialize the Guzzle HTTP client and configure it for the Passport API by adding the code below to $APP_ROOT/public/index.php, prior to the callback functions:

<?php

// Slim application initialization - snipped

// add Passport API client
$container['passport'] = function ($container) {
  $config = $container->get('settings');
  return new Client([
    'base_uri' => $config['passport_api_url'],
    'timeout'  => 6000,
    'verify' => false,  // set to true for production
    'headers' => [
      'Authorization' => $config['passport_api_key'],
    ]
  ]);
};

// other callbacks

The code above uses the Slim dependency injection container to configure and prepare the Guzzle client for use. Notice that the client is configured to automatically include an Authorization header containing the API key (from the configuration file you updated in the previous step) with each request. This is a necessary authentication step when accessing the Passport API; in the absence of this header, API access will be denied. Learn more about Passport API authentication at Passport API authentication and Passport user registration API documentation.

Once the form is submitted, it goes to a form processor that accepts and validates the submission and then creates the user account using the Passport API. Here's the necessary code, which should be added to $APP_ROOT/public/index.php:

<?php

// Slim application initialization - snipped

// user form processor
$app->post('/admin/users/save', function (Request $request, Response $response) {
  // get configuration
  $config = $this->get('settings');

  // get input values
  $params = $request->getParams();

  // validate input  
  if (!($fname = filter_var($params['fname'], FILTER_SANITIZE_STRING))) {
    throw new Exception('ERROR: First name is not a valid string');
  }
  
  if (!($lname = filter_var($params['lname'], FILTER_SANITIZE_STRING))) {
    throw new Exception('ERROR: Last name is not a valid string');
  }
  
  $password = trim(strip_tags($params['password']));
  if (strlen($password) < 8) {
    throw new Exception('ERROR: Password should be at least 8 characters long');      
  }
      
  $email = filter_var($params['email'], FILTER_SANITIZE_EMAIL);
  if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
    throw new Exception('ERROR: Email address should be in a valid format');
  }
    
  // generate array of user data
  $user = [
    'registration' => [
      'applicationId' => $config['passport_app_id'],
    ],
    'skipVerification' => true,
    'user'  => [
      'email' => $email,
      'firstName' => $fname,
      'lastName' => $lname,
      'password' => $password
    ]
  ];
  
  // encode user data as JSON
  // POST to Passport API for user registration and creation
  $apiResponse = $this->passport->post('/api/user/registration', [
    'body' => json_encode($user),
    'headers' => ['Content-Type' => 'application/json'],
  ]);

  // if successful, display success message
  // with user id
  if ($apiResponse->getStatusCode() == 200) {
    $json = (string)$apiResponse->getBody();
    $body = json_decode($json);
    $response = $this->view->render($response, 'users-save.phtml', [
      'router' => $this->router, 'user' => $body->user
    ]);
    return $response;
 }
});

// other callbacks

This code listing defines a callback to handle form submissions via POST. There's a lot going on here, so let's go through it, step by step:

  • The callback begins by collecting the various input parameters—name, email address, password—and validating each using various validators. Invalid input is flagged and an exception is thrown to prevent further processing.
  • Once the input is sanitized and validated, it is converted into a PHP array containing two main keys. The registration key holds the application ID for which the user is being registered; this is obtained from the application configuration file. The user key holds the details of the user account to be created. This PHP array is then converted into a JSON document using PHP's json_encode() function.
  • Next, the Guzzle client sends a POST request to the Passport API's /api/user/registration endpoint with the JSON-encoded document. This API call creates a new user in the Passport service with the specified email address, password, and other details, and links the user to the specified application.
  • If successful, the API call returns a JSON document containing the new user record, together with the associated unique identifier. This JSON document is decoded and passed back to the template as a template variable.

Next, update the template at $APP_ROOT/views/users-save.phtml to check for the returned user record and display a success message, as shown in this revision of the user registration form:

<div>
  <?php if (!isset($_POST['submit'])): ?>      
  <form method="post" action="<?php echo $data['router']->pathFor('admin-users-save'); ?>">
    <div class="form-group">
      <label for="fname">First name</label>
      <input type="text" class="form-control" id="fname" name="fname">
    </div>
    <div class="form-group">
      <label for="lname">Last name</label>
      <input type="text" class="form-control" id="lname" name="lname">
    </div>
    <div class="form-group">
      <label for="email">Email address</label>
      <input type="text" class="form-control" id="email" name="email">
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" class="form-control" id="password" name="password">
    </div> 
    <div class="form-group">
      <button type="submit" name="submit" class="btn btn-default">Save</button>
    </div>
  </form>  
<?php else: ?>
  <div class="alert alert-success">
    <strong>Success!</strong> The user with identifier <strong><?php echo $data['user']->id; ?></strong> was successfully created. <a role="button" class="btn btn-primary" href="<?php echo $data['router']->pathFor('admin-users-save'); ?>">Add another?</a>
  </div>
<?php endif; ?>      
</div>

To see this in action, try creating a new user through the application. If successful, you should see a message like this, which contains the ID for the newly created user account:

Figure 7. New user creation
New user creation
New user creation

Step 5: List users

The Passport API includes a search API for users at /api/user/search, which comes in very handy when you want to retrieve a list of all the users registered for your application. To see this in action, add a route and callback function to $APP_ROOT/public/index.php that invokes this API endpoint, as shown here:

<?php

// Slim application initialization - snipped

// user list handler
$app->get('/admin/users/index', function (Request $request, Response $response) {
  // get configuration
  $config = $this->get('settings');

  $apiResponse = $this->passport->get('/api/user/search', [
    'query' => ['queryString' => 'user.registrations.applicationId:' . $config['passport_app_id']]
  ]);
  
  if ($apiResponse->getStatusCode() == 200) {
    $json = (string)$apiResponse->getBody();
    $body = json_decode($json);
    $response = $this->view->render($response, 'users-index.phtml', [
      'router' => $this->router, 'results' => $body
    ]);
    return $response;   
  }
})->setName('admin-users-index');

// other callbacks

This callback invokes the /api/user/search API method and retrieves all the users associated with the current application as a JSON document. This document is then converted to a PHP array and transferred to the view script, which displays the data in a table. Here's the relevant section of the view script, which should be saved to $APP_ROOT/views/users-index.phtml:

<?php if ($data['results']->total > 0): ?>
<table class="table table-striped">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email address</th>
    </tr>
  </thead>
<?php foreach ($data['results']->users as $user): ?>
  <tr>
    <td><?php echo $user->firstName; ?> <?php echo $user->lastName; ?></td>
    <td><?php echo $user->email; ?></td>
  </tr>
<?php endforeach; ?>
</table>
<?php else: ?>
<div>
  <div class="alert alert-info">
    No users found. <a role="button" class="btn btn-primary" href="<?php echo $data['router']->pathFor('admin-users-save'); ?>">Why not create one?</a>
  </div>
</div>  
<?php endif; ?>

And when you browse to the "/admin/users/index" route, here's what it looks like:

Figure 8. User list
User list
User list

See Passport user search API documentation for more information.

Step 6: Enable login and logout

So now you have a way to add users to your application. The next step is to add some authentication, so that you can distinguish between registered and unregistered users (and possibly make some functionality only available to the former category). In short, it's time to add a login workflow.

Passport offers a login API, which can be used to authenticate users against stored credentials. Using this API is fairly simple: Send a POST request to the /api/login endpoint with a JSON-encoded body containing the application ID, user's email address, and user's password. If successful, the API will return a 2xx response code; if not, it will return a 4xx response code. In both cases, a number of response codes are possible, depending on specific scenarios; the Passport user login API documentation has a complete list with details.

Here's the necessary code, which you should add to $APP_ROOT/public/index.php as callbacks for the "/login" route:

<?php

// Slim application initialization - snipped

session_start();

// login page handler
$app->get('/login', function (Request $request, Response $response) {
  return $this->view->render($response, 'login.phtml', [
    'router' => $this->router
  ]);
})->setName('login');

// login form processor
$app->post('/login', function (Request $request, Response $response) {
  // get configuration
  $config = $this->get('settings');
  
  // set user record to false by default
  $user = false;

  try {
    // get input values
    $params = $request->getParams();
    
    // validate and sanitize input
    $email = filter_var($params['email'], FILTER_SANITIZE_EMAIL);
    if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
      throw new Exception('ERROR: Email address should be in a valid format');
    }

    $password = trim(strip_tags($params['password']));
    if (empty($password)) {
      throw new Exception('ERROR: Password should not be an empty string');      
    }
        
    // generate array of data for authentication
    $auth = [
      'applicationId' => $config['passport_app_id'],
      'loginId' => $email,
      'password' => $password,
    ];

    // authenticate
    $apiResponse = $this->passport->post('/api/login', [
      'body' => json_encode($auth),
      'headers' => ['Content-Type' => 'application/json'],
    ]);
    
    // if 2xx error, authentication successful
    // set user information in session
    if ($apiResponse->getStatusCode() == 200 || $apiResponse->getStatusCode() == 202) {
      $json = (string)$apiResponse->getBody();
      $body = json_decode($json);
      $_SESSION['user'] = $body->user;
      $user = $body->user;
    }
  } catch (ClientException $e) {
    // in case of a Guzzle exception
    // if 4xx, authentication error 
    // bypass exception handler for login failure page
    // for other errors, transfer to exception handler as normal
    if (!($e->getResponse()->getStatusCode() >= 400 && $e->getResponse()->getStatusCode() < 500)) {
      throw new Exception($e->getResponse());
    } 
  } 
  return $this->view->render($response, 'login.phtml', [
    'router' => $this->router, 'user' => $user
  ]);
});

// other callbacks

The first handler receives GET requests for the "/login" route and returns a login form (you'll see this shortly).

The second handler receives submissions from the login form and checks to ensure that the submission includes a valid email address and password. If it does, it then creates a JSON document containing the application ID, email address, and password, and POSTs it to the /api/login endpoint.

  • If the API responds with a 200 or 202 response code, it implies that authentication was successful. The response will also contain the complete user record in JSON; this is decoded and then both are passed to the template and placed in a session variable named $_SESSION['user'], so that it can be used in subsequent operations.
  • If the API responds with a 4xx response code, this implies that authentication was unsuccessful. Normally, non-2xx response codes are automatically treated as errors by Guzzle and converted to exceptions. In this particular case, if the error code is a 4xx code, the callback interrupts the usual exception handling process and transfers control to the login page template, so that a custom failure message can be displayed instead of the usual exception template.

To accompany this, you should also create a login template, which contains both the initial login form and the success and failure messages that are displayed post-login. Here's the code, which should be saved as $APP_ROOT/views/login.phtml:

<?php if (!isset($_POST['submit'])): ?>
<div>
  <form method="post" action="<?php echo $data['router']->pathFor('login'); ?>">
    <div class="form-group">
      <label for="email">Email address</label>
      <input type="text" class="form-control" id="email" name="email" value="">
    </div>
    <div class="form-group">
      <label for="body">Password</label>
      <input type="password" class="form-control" id="password" name="password">
    </div>
    <div class="form-group">
      <button type="submit" name="submit" class="btn btn-default">Submit</button>
    </div>
  </form>
</div>
<?php else: ?>
<div>
  <?php if ($data['user'] !== false): ?>
  <div class="alert alert-success">
    <strong>Success!</strong>.
  </div>
  <p>You are currently logged in as <strong><?php echo $data['user']->firstName; ?> <?php echo $data['user']->lastName; ?></strong> with email address <strong><?php echo $data['user']->email; ?></strong>.</p>
  <p>Visit the <a href="<?php echo $data['router']->pathFor('account'); ?>">account information page.</p> 
  <?php else: ?>
  <div class="alert alert-danger">
    <strong>Failure!</strong> Please <a href="<?php echo $data['router']->pathFor('login'); ?>">try again</a>.
  </div>        
  <?php endif; ?>
</div>        
<?php endif; ?>

Most of this is already explained in the previous paragraphs. This template contains a login form with fields for the email address and password. On submission, depending on the value of the $_SESSION['auth'] template variable, either a success message or a failure message is displayed. In the event of a successful login, it's easy enough to extract the user's name, email address, and other details from the session variable and display them as part of the success message.

Here's what the login form looks like:

Figure 9. User login
User login
User login

And here's an example of what appears after a successful login:

Figure 10. User login result
User login result
User login result

Just as there's a "/login" route, you also need a "/logout" route. Although the Passport API includes a /logout method, it's only useful when you use refresh or access tokens. Since this application doesn't, there's no need to explicitly log the user out of Passport; instead, a logout operation can be accomplished simply by destroying the application-scope session variable. Here's the code:

<?php

// Slim application initialization – snipped

// logout page handler
$app->get('/logout', function (Request $request, Response $response) {
  unset($_SESSION['user']);
  return $response->withHeader('Location', $this->router->pathFor('login'));
})->setName('logout');

// other callbacks

Step 7: Implement authentication checks

Once you have a way to authenticate users, it's quite easy to protect specific pages and ensure that they are only visible to logged-in users. Here's a function that does just that (add it to $APP_ROOT/public/index.php before the other callback handlers):

<?php

// Slim application initialization - snipped
              
// simple authentication middleware
$authenticate = function ($request, $response, $next) {
  if (!isset($_SESSION['user'])) {
    return $response->withHeader('Location', $this->router->pathFor('login'));
  }
  return $next($request, $response);
};

// other callbacks

The authenticate() function checks for the presence of the user identifier in the session. If it's not present, it redirects the user to the /logout URL, forcing a re-login. This function is used as Slim "middleware," which is code that runs before a request is processed. By adding this middleware to specific route handlers, it becomes possible to protect access to application functions so that they are only available to authenticated users.

So, for example, if you have an account information page that you only want logged-in users to be able to access, you can create a callback handler for that page as usual, and then attach the middleware above to it. Here's what it would look like:

<?php

// Slim application initialization - snipped
              
// private page handler
$app->get('/account', function (Request $request, Response $response) {
  return $this->view->render($response, 'account.phtml', [
    'router' => $this->router, 'user' => $_SESSION['user']
  ]);
})->setName('account')->add($authenticate);

// other callbacks

With this middleware in place, unauthenticated users attempting to access the "/account" route would instead be redirected to the login page, while authenticated users would be able to access it normally. (See the Slim middleware documentation for more details.)

Step 8: Enable user activation and deactivation

An interesting feature of the Passport API is its built-in support for user activation and deactivation. Deactivation comes in handy when you want to temporarily suspend a user's account but not actually delete the account. Inactive users cannot log into the system until their account is reactivated.

The Passport API has the same API endpoint for both activations and deactivations, with the HTTP method itself used to signify the operation to be performed. Deactivation is accomplished by sending a DELETE request to the /api/user/USER_ID endpoint, while reactivation involves sending a PUT request to the same endpoint with an additional reactivate parameter. In both cases, the API request should include the unique user identifier from Passport.

To see this in practice, add the following callback handlers to $APP_ROOT/public/index.php:

<?php

// Slim application initialization - snipped
              
// user deactivation handler
$app->get('/admin/users/deactivate/{id}', function (Request $request, Response $response, $args) {
  // sanitize and validate input
  if (!($id = filter_var($args['id'], FILTER_SANITIZE_STRING))) {
    throw new Exception('ERROR: User identifier is not a valid string');
  }
  
  $apiResponse = $this->passport->delete('/api/user/' . $id);
  return $response->withHeader('Location', $this->router->pathFor('admin-users-index'));
})->setName('admin-users-deactivate');

// user activation handler
$app->get('/admin/users/activate/{id}', function (Request $request, Response $response, $args) {
  // sanitize and validate input
  if (!($id = filter_var($args['id'], FILTER_SANITIZE_STRING))) {
    throw new Exception('ERROR: User identifier is not a valid string');
  }

  $apiResponse = $this->passport->put('/api/user/' . $id , [
    'query' => ['reactivate' => 'true']
  ]);
  return $response->withHeader('Location', $this->router->pathFor('admin-users-index'));
})->setName('admin-users-activate');

// other callbacks

Both handlers first check that the route request includes the user identifier and, if it does, they generate either a DELETE or PUT request to the Passport API to deactivate or reactivate the user.

How do you trigger these routes? The easiest way is to add command buttons to the user list page created in Step 5, with each button linking to the appropriate route. It also makes sense to categorize the user list and separate the active users from the inactive ones, so that the appropriate button appears next to each user. Here's the revised list page handler:

<?php

// Slim application initialization - snipped
              
$app->get('/admin/users/index', function (Request $request, Response $response) {
  // get configuration
  $config = $this->get('settings');

  $apiResponse = $this->passport->get('/api/user/search', [
    'query' => ['queryString' => 'user.registrations.applicationId:' . $config['passport_app_id']]
  ]);
  
  if ($apiResponse->getStatusCode() == 200) {
    $json = (string)$apiResponse->getBody();
    $body = json_decode($json);

    $activeUsers = [];
    $inactiveUsers = [];      
    foreach ($body->users as $user) {
      if ($user->active == 1) {
        $activeUsers[] = $user;
      } else {
        $inactiveUsers[] = $user;
      }
    }
    
    $response = $this->view->render($response, 'users-index.phtml', [
      'router' => $this->router, 'active-users' => $activeUsers, 'inactive-users' => $inactiveUsers
    ]);
    return $response;   
  }
})->setName('admin-users-index');

// other callbacks

This route handler starts off the same as before: it uses the /api/user/search endpoint to return a list of all users registered for the application. It then iterates over the returned collection, separating users into two arrays based on their account status. These arrays are then passed to the page template, which takes care of formatting and displaying them with the appropriate buttons.

Revise the template at $APP_ROOT/views/users-index.phtml to include the new information and buttons:

<?php if (count($data['active-users']) > 0): ?>
<div class="panel panel-default">
  <div class="panel-heading clearfix">
    <h4 class="pull-left">Active Users</h4>
  </div>
  <table class="table table-striped">
    <thead>
      <tr>
        <th>Name</th>
        <th>Email address</th>
        <th></th>
      </tr>
    </thead>
  <?php foreach ($data['active-users'] as $user): ?>
    <tr>
      <td><?php echo $user->firstName; ?> <?php echo $user->lastName; ?></td>
      <td><?php echo $user->email; ?></td>
      <td><a href="<?php echo $data['router']->pathFor('admin-users-deactivate', array('id' => $user->id)); ?>" class="btn btn-danger">Deactivate</a></td> 
    </tr>
  <?php endforeach; ?>
  </table>
</div>
<?php else: ?>
<div>
  <div class="alert alert-info">
    No users found. <a role="button" class="btn btn-primary" href="<?php echo $data['router']->pathFor('admin-users-save'); ?>">Why not create one?</a>
  </div>
</div>  
<?php endif; ?>

<?php if (count($data['inactive-users']) > 0): ?>
<div class="panel panel-default">
  <div class="panel-heading clearfix">
    <h4 class="pull-left">Inactive Users</h4>
  </div>
  <table class="table table-striped">
    <thead>
      <tr>
        <th>Name</th>
        <th>Email address</th>
        <th></th>
      </tr>
    </thead>
  <?php foreach ($data['inactive-users'] as $user): ?>
    <tr>
      <td><?php echo $user->firstName; ?> <?php echo $user->lastName; ?></td>
      <td><?php echo $user->email; ?></td>
      <td><a href="<?php echo $data['router']->pathFor('admin-users-activate', array('id' => $user->id)); ?>" class="btn btn-success">Activate</a></td> 
    </tr>
  <?php endforeach; ?>
  </table>
</div>
<?php endif; ?>

And here's what it looks like:

Figure 11. User list
User list
User list

For more information, see Passport user deactivation API documentation and Passport user reactivation API documentation.

Step 9: Deploy to Bluemix

At this point, the application has a reasonable amount of functionality, so it's a good time to deploy it to Bluemix and bind it to the Passport service instance that you created in Step 3. First, create the application manifest file, remembering to use a unique host and application name by appending a random string to it (like your initials).

---
applications:
- name: myapp-[initials]
memory: 256M
instances: 1
host: myapp-[initials]
buildpack: https://github.com/cloudfoundry/php-buildpack.git
stack: cflinuxfs2

You must also configure the build pack to use the public directory of the application as the web server directory. Create a $APP_ROOT/.bp-config/options.json file with the following content:

{
    "WEB_SERVER": "httpd",
    "PHP_EXTENSIONS": ["bz2", "zlib", "curl"],
    "COMPOSER_VENDOR_DIR": "vendor",
    "WEBDIR": "public",
    "PHP_VERSION": "{PHP_56_LATEST}"
}

Typically, you'd also want to have the service credentials for the Passport service automatically sourced from Bluemix. This allows you to modify service passwords or disconnect/reconnect new service instances without needing to update the application codebase. To do this, first update the code to use Bluemix's VCAP_SERVICES variable:

<?php

// Slim application initialization - snipped
              
// if BlueMix VCAP_SERVICES environment available
// overwrite local config file with credentials from BlueMix
if ($services = getenv("VCAP_SERVICES")) {
  $services_json = json_decode($services, true);  
  $config['settings']['passport_api_key'] = $services_json["user-provided"][0]["credentials"]["api_key"];
  $config['settings']['passport_api_url'] = $services_json["user-provided"][0]["credentials"]["passport_backend_url"];
  if (getenv("passport_app_id")) {
    $config['settings']['passport_app_id'] = getenv("passport_app_id");
  } 
} 

// other callbacks

Notice that the code above obtains the Passport API key and API URL from the Bluemix VCAP_SERVICES variable, which is a special environment variable that stores connection details for bound services. You'll remember that you added these values when you initialized the Passport service instance back in Step 3.

Notice also that the code attempts to get the Passport application ID from a custom variable in the Bluemix application's environment. This is a good alternative to storing it in a configuration file, and I'll show you how to set this custom variable in a moment.

Now, go ahead and push the application to Bluemix, then bind the Passport service that you initialized earlier to it. Remember to use the correct ID for the service instance to ensure that the right instance is bound to the application. You can obtain the service ID from the service instance page in the Bluemix dashboard.

shell> cf api https://api.ng.bluemix.net
shell> cf login
shell> cf push
shell> cf bind-service myapp-[initials] "Passport-[id]"

Add a custom variable to hold your Passport application ID:

shell> cf set-env myapp-[initials] passport_app_id APP_ID

Once that's done, restage the application for the changes to take effect:

shell> cf restage myapp-[initials]

You should now be able to browse to the application at http://myapp-[initials].mybluemix.net and see the welcome page. If you don't, refer to the link at the top of this section for information on how to obtain a debug log. (For more information, see Debugging PHP errors on IBM Bluemix.)

Conclusion

As this article has demonstrated, Bluemix's Passport service makes it easy for you to add user authentication and management to your application, without having to write it from scratch. This speeds up development and gives you more time to focus on building out the more unique aspects of your application.

However, what you've seen here is just the tip of the iceberg. The Passport API offers a number of other useful features: modifying and deleting user accounts, storing custom user profile attributes, supporting user roles, and providing ready-to-use workflows for forgotten passwords. I'll walk you through all of those features in Part 2—so make sure you don't miss it!


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Cloud computing, Security
ArticleID=1046747
ArticleTitle=Manage and authenticate users easily in IBM Bluemix applications with PHP and the Passport service, Part 1: Add user authentication and management to your application without starting from scratch
publish-date=07242017