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
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.
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.
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']));
}
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;
}
}