Login / Status
developer.Resource
Home . Documentation . Document Library . Extension Manuals
Sponsors
hosted by punkt.deTYPO3 and Open Source Magazine

1.5. Making auth-services

The implementation of a service should be started with the extension kickstarter. Use auth as service type for authentication services. You'll have to define parts of the service you want to implement. This is done by the subtype. Following subtypes are available:

  • getUserBE

  • authUserBE

  • getGroupsBE (not yet implemented)

  • authGroupsBE (not yet implemented)

  • getUserFE

  • authUserFE

  • getGroupsFE

  • authGroupsFE

You see the subtypes are divided into backend (BE) and frontend (FE). Both have the functionality to

  • find a user depending on some information like submitted username (getUserXX)

  • authenticate the user (authUserXX)

  • find the users usergroups (getGroupsXX)

  • check the users groups if they are valid (authGroupsXX)

If you want to synchronize an external user database with the local TYPO3 BE-users you can implement the getUserBE subtype. This service fetch the user from the external database and write the data into the be_users table of TYPO3. The password authentication don't have to be implemented by this service because it's already available by the system services. Easy, isn't it?

Important for own implementations

  1. You have to move the t3lib_extMgm::addService() registration from the file ext_tables.php (created by the kickstarter) to ext_localconf.php - or simplier, just rename ext_tables.php to ext_localconf.php. Normally services are registered in the ext_tables.php files, but this is not available during authentication.

  2. The priority of the system auth-services (username/password) is 50. If you want to implement some kind of auto login your service should have a higher priority.

Service API

A service object have following predefined variables:

$this->pObj

Parent object which is and instance of t3lib_userauth or one of the extended classes of  t3lib_userauth. Normally it shouldn't be needed to access a method or a variable of that object, but for lowlevel extensions it might be handy to have access to.

$this->mode

Subtype of the service which is used to call the service.

Examples: “getUserBE”, “getGroupsFE”

$this->login

Submitted login form data and some additional data as array with following keys:

uname

username

uident

user identification (password) in a format which depends on the current configured security_level. This one shouldn't be used.

uident_text

password in plaintext if available

uident_challenged

password md5 hashed in following format (pseudo code):

uident_challenged = (string)md5(uname.':'.uident_text.':'.chalvalue)

This is normally not used but is here for historical reasons.

uident_superchallenged

password md5 hashed in following format (pseudo code):

uident_superchallenged = (string)md5(uname.':'.md5(uident_text).':'.chalvalue)

chalvalue

just some md5 unique value

$this->authInfo

Some data as array with following keys:

loginType

'FE' or 'BE'

refInfo

parse_url(t3lib_div::getIndpEnv('HTTP_REFERER'));

HTTP_HOST

t3lib_div::getIndpEnv('HTTP_HOST');

REMOTE_ADDR

t3lib_div::getIndpEnv('REMOTE_ADDR');

REMOTE_HOST

t3lib_div::getIndpEnv('REMOTE_HOST');

security_level

“normal”, “challenged” or “superchallenged”

showHiddenRecords

if set hidden records should be included which might be used int the frontend but this is not typical.

$this->db_user

Some data as array with following keys:

table

Table in database with user data

userid_column

Column for user-id (uid)

username_column

Column for login-name

userident_column

Column for password

usergroup_column

Column which store the enabled usergroups for the user

enable_clause

Default WHERE clause to be used with be_users/fe_users tables. Set by:

 $this->pObj->user_where_clause();

checkPidList

Pages ID's to search for user records (FE). Set by:

 $this->pObj->checkPid ? $this->pObj->checkPid_value : '';

check_pid_clause

Default WHERE clause to limit searching for user records on selected pages. Set by:

 $this->pObj->checkPid ? ' AND pid IN ('.$GLOBALS['TYPO3_DB']->cleanIntList(checkPidList).')' : '';

Set by $this->getServiceOption('db_user', $info['db_user'], false); in tx_sv_authbase. Can be overridden in localconf.php by $TYPO3_CONF_VARS['SVCONF'].

$this->db_groups

Some data as array with following keys:

table

Table in database with usergroup data

Set by $this->getServiceOption('db_groups', $info['db_groups'], false); in tx_sv_authbase. Can be overridden in localconf.php by $TYPO3_CONF_VARS['SVCONF'].

Configuration of the authentication behavior

Some variables can be set to change the behavior of the authentication class.  The configuration has to be done in typo3conf/localconf.php with the array $TYPO3_CONF_VARS['SVCONF']['auth']['setup']. Some auth-services extensions set those values automatically. For example to make auto login possible the  FE_fetchUserIfNoSession option have to be enabled. Those options are available for backend and frontend also. Just use the prefix BE_ for setting these options for the backend authentication.

XX_fetchUserIfNoSession

$TYPO3_CONF_VARS['SVCONF']['auth']['setup']['FE_fetchUserIfNoSession'] = false; // default

Normally an authentication process will be started only if an active login has occurred. That means that a user submitted username and password. If you want to provide an auto login service (like the cc_iplogin_fe) you have to enable this option.

XX_ alwaysFetchUser

$TYPO3_CONF_VARS['SVCONF']['auth']['setup']['FE_alwaysFetchUser'] = false; // default

If this option is set getUser services will be called always, even if a user session is already established.

XX_ fetchAllUsers

$TYPO3_CONF_VARS['SVCONF']['auth']['setup']['FE_fetchAllUsers'] = false; // default

If this option is set all getUser Services will be called to fetch user data. Otherwise fetching a user will be stopped when the first user was found.

XX_ alwaysAuthUser

$TYPO3_CONF_VARS['SVCONF']['auth']['setup']['FE_alwaysAuthUser'] = false; // default

If this option is set all authUser Services will be called always even if a user session exists.

Password submission

Data transmission over the Internet is normally in plain text. This means if you don't use an encrypted channel (https, ssl, ...) somebody might be able to 'listen' on that channel. This is the reason why the TYPO3 backend login form encrypts the password by JavaScript before submission. This encryption is done by a md5 hash which can't be decrypted. This is a good thing.

But one auth-service might need the original password for check it with an external source. In that case the service can  configure the backend login form to submit plaintext passwords:

$TYPO3_CONF_VARS['BE']['loginSecurityLevel'] = 'normal';

You should check if this is a serious security issue or not. It is a good idea to use https for backend access then.

In an auth-service you have access to the submitted password with

$password = $this->login['uident_text'];

$password = $this->login['uident_superchallenged'];

While  uident_text is only available in the backend if  loginSecurityLevel is set to normal,  uident_superchallenged is always available.  uident_superchallenged is the default for backend authentication.

Have a look in the Demo auth-service (cc_svauthdemo) extension or in the Magic Password (cc_magicpw) extension for example code.

Also have a look in the Todo section what have to be done to make plaintext password submission secure.

Debugging

Debugging authentication with TYPO3's builtin debug() function does not work because the debug() function output the data before a HTPP header could be sent. This break the authentication process because a Cookie can't be send to the browser. Therefore another debug concept is needed.

First activate the logging in the extensions options with the extension manager:

Now you can use the CCDevLog extension (or another extension which can display devlog data). Please keep in mind that for every HTTP request an authentication will be done. That means if you login to the backend the password authentication will be done once, than the cookie session will be used to identify the user. Therefore you don't see the login form data in the “latest log run” of the CCDevLog output.

In own auth-services you can use logging like this:

if ($this->writeDevLog) t3lib_div::devLog('User found: '.t3lib_div::arrayToLogString($user, array($this->db_user['userid_column'],$this->db_user['username_column'])), 'tx_myauthservice');

if ($this->writeDevLog) t3lib_div::devLog('Password not accepted: '.$this->login['uident'], 'tx_myauthservice', 2);

Have a look in t3lib_div for the API of t3lib_div::devLog().

Logging

Normal logging looks like this. Have a look in class.tx_sv_authbase.php for the writelog() API.

if ($this->writeAttemptLog) {
    $this->writelog(255,3,3,2,
    "Login-attempt from %s (%s), username '%s' not found!!",
    Array($this->authInfo['REMOTE_ADDR'], $this->authInfo['REMOTE_HOST'], $this->login['uname']));

}

Auth service skeleton

In the doc/ folder of this extension you can find a skeleton for own auth-services.

/**

 * Service 'User authentication' skeleton
 * Look in sv/ for example code
 *
 * @author René Fritz <r.fritz@colorcube.de>
 */

class tx_my_auth_sv extends tx_sv_authbase{
// from tx_sv_authbase:
// var $pObj; // Parent object

// var $mode;// Subtype of the service which is used to call the service.

// var $login=array();// Submitted login form data
// var $authInfo=array();// Various data

// var $db_user=array();// User db table definition
// var $db_groups=array();// Usergroups db table definition

// var $writeAttemptLog = false;// If the writelog() functions is called if a login-attempt has be tried without success

// var $writeDevLog = false;// If the t3lib_div::devLog() function should be used

/**
 * find a user
 *
 * @return    mixed    user array or false
 */
function getUser(){

$user = false;

// most of the time there's no difference between FE and BE
// therefore this if-statement might not be needed
if($this->mode == 'getUserFE'){

} elseif ($this->mode == 'getUserBE'){

}

// return user data array (be_users/fe_users format) or false
return $user;
}

/**
 * authenticate a user
 *
 * @param    array     Data of user.
 * @return   boolean
 */
function authUser($user)    {
$OK = 100;

// most of the time there's no difference between FE and BE
// therefore this if-statement might noct be needed
if($this->mode == 'authUserFE'){

} elseif ($this->mode == 'authUserBE'){

}

// return values:
// 200 - authenticated and no more checking needed - useful for IP checking without password
// 100 - Just go on. User is not authenticated but there's still no reason to stop.
// false - this service was the right one to authenticate the user but it failed
// true - this service was able to authenticate the user

return $OK;
}

/**
 * find usergroups
 *
 * @param    array     Data of user.
 * @param    array     Group data array of already known groups. This is handy if you want select other related groups.
 * @return   mixed     groups array
 */
function getGroups($user, $knownGroups){

$groupDataArr = array();

if($this->mode == 'getGroupsFE'){

/*

$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $this->pObj->usergroup_table, 'NOT deleted ...');

while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)){

$groupDataArr[$row['uid']]=$row;

}

if ($res)$GLOBALS['TYPO3_DB']->sql_free_result($res);

*/

} elseif ($this->mode == 'getGroupsBE'){

# Get the BE groups here
# this subtype still needs to be implemented in t3lib_userauthgroup
}

return $groupDataArr;
}

/**
 * check a group if it's valid
 *
 * @paramarrayData of user.
 * @paramarrayroup row array.
 * @returnbooleantrue if group is valid
 */
function authGroup($user, $group){

$valid = true;

if($this->mode == 'authGroupsFE'){

/* example IP check

if ($IPList=trim($group['tx_ipauth_ip_list'])){

$baseIP = t3lib_div::getIndpEnv('REMOTE_ADDR');

$valid=t3lib_div::cmpIP($baseIP, $IPList);

}

*/

} elseif ($this->mode == 'authGroupsBE'){
# needs to be implemented in t3lib_userauthgroup
}

return $valid;
}

}

Example

Have a look in the Demo auth-service (cc_svauthdemo) extension or in the Magic Password (cc_magicpw) extension for example code.