Loading...

Creating a new Driver

To create a new driver, first you will need to code-generate a base driver package using the following command, renaming as appropriate:

authstack-ctl driver:new yourdrivername

The new driver package can be found within ROOT . www/app/Addons/Drivers

The following files are generated:

File Description
AbstractDriver.php Contains implementation for most commonly used functions.
driver.json Contains information about the driver title, description, class path and most importantly - configuration JSON schema.
Driver.php Contains empty methods which you should implement. Each method contains comments on what it does.
User.php When authenticated successfully, User object is created and populated, containing common methods for manipulating user record in AuthStack

AbstractDriver.php

This file contains implementation for most commonly used methods, as well as code that lets AuthStack bootstrap the driver. In most cases, you won't have to edit this file.

Methods that should be modified or reviewed:


List of methods

Method visibility description
initialize   public static   Initialises the driver. Passes a string for configuration index and numeric identifier of the connection that's initializing this driver.
elevate public Elevates privileges if required. If you need to perform an action at the authentication source which requires an administrative privileges, you can implement the logic in elevate() method. Most prominent use of this method is with LDAP. Refer to our default LDAP driver to see the use of this function
setAuthSourceId public Sets a new ID for authentication source (id of connection from AuthStack database). Not used, but exists for unit testing purposes.
getAuthSourceId public Returns the numeric ID of authentication source (connection) in use
lastError public Returns last error reported that occurred during connection / authentication phase
provision private Provisions the user to AuthStack if user doesn't exist in the database yet
check private Receives an array with entered user credentials. This method performs validation. If you expect certain formats, lengths or even parameters, you can implement that logic in this method.
getUniqueId private Creates unique identifier for newly provisioned user. Shouldn't be modified, it produces unique identifies based on combination of connection ID and username.
config private Retrieves connection configuration variables that's saved in MySQL. Shouldn't be modified.

elevate

public boolean elevate();

This method is originally designed for LDAP, allowing driver to authenticate as administrator, allowing LDAP to run certain filters, since binding as regular user doesn't always come with sufficient privileges to run filters on various organisational units. If you are implementing a custom LDAP driver, take a look at our default LDAP driver implementation.

If your authentication source doesn't implement access hierarchy like LDAP does, it's sufficient to return true; from this method.

Return values

true if authenticated successfully.

false if authentication failed.

If authentication fails, set $this->last_error to an error message so it can be retrieved via lastError() method


check

protected void check(array $credentials);

Performs checks on user input. If validation fails, throw AuthStack\Driver\Errors\InsufficientCredentialsError exception.


driver.json

This file contains JSON Schema definition, title, description and whether driver is protected or not.

For information about JSON Schema, refer to JSON Schema documentation.

Default contents

{
  "title": "Your driver title. Change this.",
  "description": "Your driver description. Change this.",
  "class_path": "AuthStack\\Addons\\Drivers\\WordpressDriver",
  "is_protected": 0,
  "config": {
    "schema": {
      "$schema": "http://json-schema.org/draft-04/schema#",
      "properties": {
      }
    }
  }
}

title

Title of your driver. This will be the label shown in the AuthStack Admin GUI.

description

Description of your driver. This will be the description shown in the AuthStack Admin GUI.

class_path

Namespace of the class that initialises the driver. If you used authstack-ctl driver:new to initialise the driver, this value will be automatically populated.

is_protected

Whether this driver is protected or not. Protected drivers can't be removed from the AuthStack Admin GUI.

config

This property should be modified and JSON schema should be added that describes the schema of configuration expected by this driver.

Example: Creating a MySQL driver with the following information being required:

<?php

$config = [
    'host' => '127.0.0.1',
    'port' => 3306,
    'database' => 'my_database';
    'username' => 'admin_user',
    'password' => 'admin_password
];

The above example indicates that the configuration schema should contain keys ['host', 'port', 'database', 'username', 'password'].

AuthStack will not create new authentication connections if the supplied JSON configuration doesn't match the schema.

To specify ['host', 'port', 'database', 'username', 'password'] as required, driver.json would contain:

{
  "title": "Your driver title. Change this.",
  "description": "Your driver description. Change this.",
  "class_path": "AuthStack\\Addons\\Drivers\\WordpressDriver",
  "is_protected": 0,
  "config": {
    "schema": {
      "$schema": "http://json-schema.org/draft-04/schema#",
      "required": ["host", "database", "username", "password"],
      "type": "object",
      "properties": {
        "host": {
          "description": "MySQL IP/Hostname"
          "type": "string"
        },

        "port": {
          "description": "MySQL Port. Default is 3306 on TCP-based connections. If you use unix sockets, specify the path"
          "type": "string"
        },

        "database": {
          "description": "Name of the database to connect to",
          "type": "string"
        },

        "username": {
          "type": "string"
        },

        "password": {
          "type": "password"
        }
      }
    }
  }
}

Driver.php

Driver.php implements logic behind connecting to an authentication source and authenticating the user.

Each driver must implement the AuthStack\Driver\Driver\AuthDriverInterface interface.

Methods are documented below in order of execution.

Methods

The following methods must be implemented for the driver to be functional:

  1. install
  2. connect
  3. authenticate
  4. retrieveUser - OPTIONAL method
  5. passwordChangeAllowed
  6. changePassword
  7. getUser
  8. getUserByPrimaryKey
  9. getUserByUsername

install

array public static install();

install method reads driver.json file and returns the decoded json as array. Default implementation is sufficient.

This method is delivered implemented for people who prefer to have their JSON schema coded via PHP rather than typing out JSON in another file. If you're familiar with creating JSON schema then you can remove default implementation of this method and replace it with your own.


connect

bool public connect();

throw AuthStack\Driver\Errors\ConnectionError on error

This method should implement logic behind connecting to authentication source.

Sample code to implement connecting to MySQL:

<?php

   public function connect()
    {
        $result = false;

        try
        {
            $dsn = sprintf("mysql:dbname=%s;host=%s;charset=UTF8", $this->config('mysql.database'), $this->config("mysql.host"));

            $this->pdo = new \PDO($dsn, $this->config('mysql.username'), $this->config('mysql.password'));

            $result = true;
        }
        catch(\Exception $e)
        {
            throw new ConnectionError($e->getMessage());
        }

        return $result;
    }

authenticate

bool public authenticate(array $credentials);

This method should implement logic behind checking credentials supplied by user. The array $credentials contains the data from frontend SSO form. If you extended this form with additional fields, they will be available in the array as well.

Once this method authenticates the user successfully, it's a good idea to create the User object using information retrieved from authentication source (information are user attributes).

Interface doesn't enforce this, so the method that deals with retrieving User object doesn't have to exist at all. User class is found in the same namespace and directory as the rest of the files subject to this document.

Sample code that checks credentials against MySQL table:


<?php

public function authenticate(array $credentials)
{
    // Check whether the correct information exists within the $credentials array
    $this->check($credentials);

    $result = false;

    try
    {
        // Note: getPDO() is NOT a standard method, it's a custom one. You can implement it if required by your driver.
        $stmt = $this->getPDO()->prepare("SELECT * FROM users WHERE user_username = :username");

        $username = $credentials['username'];

        $stmt->bindValue(':username', $username);

        $stmt->execute();

        $attributes = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        if(!count($attributes))
        {
            throw new \Exception("No such user in database.");
        }

        // Note: method retrieveUser() is not enforced via interface, it's a custom method
        $user = $this->retrieveUser($attributes[0]);

        $result = true;
    }
    catch(\Exception $e)
    {
        $this->last_error = $e->getMessage();
    }

    return $result;
}

retrieveUser - OPTIONAL method

This method is completely optional, code below will outline what should happen in most cases when user authenticates successfully.

void protected retrieveUser(array $attributes);

Sample code:

<?php

protected function retrieveUser(array $attributes)
{
    // Collect the attributes
    $collection = collect($attributes);

    // Initialize user object related to this driver
    $this->user = User::initialize($collection, $this->cfg_key);

    // Check if user is admin. This sets the flag internally
    $this->getUser()->checkIfAdmin();

    // Provision user to AuthStack's database. Does nothing if user exists already
    $this->provision($this->user->getUsername());

    // Set the model with provisioned user to User object we created just now
    $this->getUser()->setProvisionedUser($this->getProvisionedUser());

    // AuthStack Id comes from ProvisionedUser model, so we'll set it now
    $this->getUser()->setAuthstackID($this->provisioned->id);

    // Set the authsource so we know which CONNECTION to look up (not driver)
    $this->getUser()->setAuthsourceID($this->getAuthSourceId());

    // Load two factor keys for this user
    $keys = KeyCollection::loadForUser($this->getUser()->getProvisionedUser()->id);

    // Set the key collection for the user
    $this->getUser()->setKeyCollection($keys);
}

passwordChangeAllowed

boolean public passwordChangeAllowed()

Indicates whether changing password will be allowed from AuthStack GUI. If allowed, you must implement changePassword method.

Sample code:

<?php

public function passwordChangeAllowed()
{
    return true;
}

changePassword

bool public changePassword(array $data);

Changes the password. $data array will contain 3 keys:

  • password_current
  • password
  • password_confirmation

You can use these to implement checks whether the user knew the old password, whether new password has been confirmed correctly or you can disregard this information altogether.

getUser

object public getUser();

Should return an instance of User object. This is available after successful authentication and if your authenticate method saved the User object to a member.

Throw AuthStack\Driver\Errors\NoUserObjectError if there's no User object available.

Sample code:

<?php

public function getUser()
{
    if(!$this->user instanceof User)
    {
        throw new \AuthStack\Driver\Errors\NoUserObjectError('There is no user object available. You must successfully authenticate first.');
    }

    return $this->user;
}

getUserByPrimaryKey

Note: method getUserByUsername takes preceedence over this method. You should probably implement this method to return $this->getUserByUsername($id); for full compatibility.

Reason this method exists is potential future changes where we might need to retrieve users using a different attribute. To keep backwards compatibility, we'll use getUserByPrimaryKey first, followed by getUserByUsername.

object public getUserByPrimaryKey($id);

This method is invoked by AuthStack when it performs auto-authentication. Auto-authentication is a process where user isn't asked for credentials because they authenticated successfully previously. AuthStack will save a few user-details into its own session and then pass it on to driver upon auto-authentication.

How to use:

<?php

public function getUserByPrimaryKey($id)
{
    $pdo = $this->getPDO();

    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");

    $stmt->bindValue(':id', $id, \PDO::PARAM_INT);

    $stmt->execute();

    $attributes = $stmt->fetchAll(\PDO::FETCH_ASSOC);

    if(!sizeof($attributes))
    {
        return false;
    }

    // Refer to retrieveUser documentation in this guide. retrieveUser method is not mandatory.
    return $this->retrieveUser($attributes[0]);
}

getUserByUsername

Preferred method for retrieving user information after auto-authentication.

object public getUserByUsername($username);

Retrieves user from authentication source using the username they supplied during authentication. Used for auto-authentication when user-session exists in AuthStack.

Sample implementation code:

<?php

public function getUserByUsername($username)
{
    $pdo = $this->getPDO();

    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");

    $stmt->bindValue(':username', $username);

    $stmt->execute();

    $attributes = $stmt->fetchAll(\PDO::FETCH_ASSOC);

    if(!sizeof($attributes))
    {
        return false;
    }

    // Refer to retrieveUser documentation in this guide. retrieveUser method is not mandatory.
    return $this->retrieveUser($attributes[0]);
}

User.php

User.php contains implementation for AuthStack\Driver\User\UserInterface. The default class doesn't have to be changed, but this guide will document common methods and what they're used for.


Methods

  1. initialize
  2. checkIfAdmin
  3. isAdmin
  4. setAdmin
  5. toArray
  6. getEmail
  7. getFullName

initialize

object static public initialize(\Illuminate\Support\Collection $collection, $cfg_key);

Initializes a new User object using the Collection passed as first parameter. This method shouldn't be changed.

$cfg_key is required. It's used for obtaining administrative attribute and value from connection configuration. It's used for method checkIfAdmin.

This method shouldn't be changed.


checkIfAdmin

bool public checkIfAdmin();

Performs a check whether user, who we authenticated via SSO, is an admin.

To perform this check, we have to obtain connection configuration values.

This method shouldn't be changed.


isAdmin

bool public isAdmin();

Returns bool indicating whether user is an admin or not. If true, administrative functions are accessible to this user.

This method shouldn't be changed.


setAdmin

object public setAdmin();

Marks the current user as admin. Used internally. Exposed as a public method for testing purposes.

This method shouldn't be changed.


toArray

Returns user's attributes as an associative array.

This method shouldn't be changed.


getEmail

string public getEmail();

Returns the attribute from collection which was marked as an email attribute. This information is used for AuthStack Profile page. The configuration for which attribute is an email one is performed while creating a new mapping set.

This method shouldn't be changed.


getFullName

Returns the attribute(s) from collection which were marked as full name. This information is used for AuthStack Profile page. The configuration for which attribute(s) to use for full name is performed while creating a new mapping set.

This method shouldn't be changed.


Previous Article

Introduction to Drivers

We're happy to talk

Our offices are open 8.30am - 7pm GMT, Monday to Friday - but you can always contact us via email. When we receive your email during opening hours, we aim to respond within 30 minutes or less. Should your email reach us out of hours, we will contact you when the office re-opens.

You can contact us using live chat