Custom OAuth 2 Authentication in Apigility
Getting Apigility's OAuth 2 integration to talk to a specific table name is quite easy. Simply add this config:
Join the DZone community and get the full member experience.
Join For Freei have a client that's writing an apigility api that needs to talk to a database that's already in place. this also includes the users table that is to be used with apigility's oauth2 authentication.
getting apigility's oauth 2 integration to talk to a specific table name is quite easy. simply add this config:
'storage_settings' => array(
'user_table' => 'user',
),
to the relevant adapter within zf-mvc-auth => authentication config.
however, if you want to use different column names, that's a bit trickier as they are hardcoded in theoauth2\storage\pdo class. to get apigility's oauth2 components to look at the correct columns, you create your own oauth2 adapter. i chose to extend zf\oauth2\adapter\pdoadapter which extends oauth2\storage\pdo and go from there.
zf\oauth2\adapter\pdoadapter extends the base class to add bcrypt hashing. this is good, so it's a good place to start from. i created a new module, myauth to hold my adapter and its factory. the adapter looks like this:
<?php
namespace myauth;
use zf\oauth2\adapter\pdoadapter;
/**
* custom extension of pdoadapter to validate against the web_user table.
*/
class oauth2adapter extends pdoadapter
{
public function __construct($connection, $config = array())
{
$config = [
'user_table' => 'legacy_user'
];
return parent::__construct($connection, $config);
}
public function getuser($username)
{
$sql = sprintf(
'select * from %s where email_address=:username',
$this->config['user_table']
);
$stmt = $this->db->prepare($sql);
$stmt->execute(array('username' => $username));
if (!$userinfo = $stmt->fetch(\pdo::fetch_assoc)) {
return false;
}
// the default behavior is to use "username" as the user_id
return array_merge(array(
'user_id' => $username
), $userinfo);
}
public function setuser($username, $password,
$firstname = null, $lastname = null)
{
// do not store in plaintext, use bcrypt
$this->createbcrypthash($password);
// if it exists, update it.
if ($this->getuser($username)) {
$sql = sprintf(
'update %s set pwd=:password, firstname=:firstname,
surname=:lastname where username=:username',
$this->config['user_table']
);
$stmt = $this->db->prepare($sql);
} else {
$sql = sprintf(
'insert into %s (email_address, pwd, firstname, surname)
values (:username, :password, :firstname, :lastname)',
$this->config['user_table']
);
$stmt = $this->db->prepare($sql);
}
return $stmt->execute(compact('username', 'password', 'firstname',
'lastname'));
}
protected function checkpassword($user, $password)
{
return $this->verifyhash($password, $user['pwd']);
}
}
this code for getuser and setuser() is lifted directly from oauth2\storage\pdo and all i've done is changed the column names. in this case i have email_address for my username, and pwd for the password column. similar, i wrote my own checkpassword based on zf\oauth2\adapter\pdoadapter, again changing the array key to check to'pwd'.
now that we have the actual work done, we need to wire it into apigility.
firstly we need a factory so that the dic can instantiate our adapter:
<?php
namespace myauth;
use zend\servicemanager\factoryinterface;
use zend\servicemanager\servicelocatorinterface;
use zend\db\adapter\driver\pdo\pdo as pdodriver;
class oauth2adapterfactory implements factoryinterface
{
/**
* create service
*
* @param servicelocatorinterface $servicelocator
* @return oauth2adapter
*/
public function createservice(servicelocatorinterface $servicelocator)
{
$connection = $servicelocator->get('db\master');
if (!$connection->getdriver() instanceof pdodriver) {
throw new \runtimeexception("need a pdo connection!");
}
$pdo = $connection->getdriver()->getconnection()->getresource();
return new oauth2adapter($pdo);
}
}
this is fairly standard code. note that the db\master is the name of the database connection that is set up in the apigility admin. i've been a bit lazy and assume that it's a pdo based adapter. if it isn't, it'll blow up, so if you're not using pdo, then it won't work as is!
to register your new authentication adapter with apigility, create a config file in config/autoload and call itmyauth.global.php or something:
<?php
return [
'zf-mvc-auth' => [
'authentication' => [
'adapters' => [
'myauth' => [
'adapter' => 'zf\\mvcauth\\authentication\\oauth2adapter',
'storage' => [
'storage' => 'myauth\oauth2adapter',
'route' => '/oauth',
],
],
],
],
],
];
the adapter is called myauth and is now available to select in the api configuration pages of the admin:
to sum up
all in all, it's really easy to write custom oauth 2 authentication for apigility as it's a very flexible platform. i've simply changed the column names here, but it would be easy enough to write an adapter against a different storage system altogether, though you would have to override more methods and possibly start from a more appropriate base class.
Published at DZone with permission of Rob Allen, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments