Add document describing how to implement username/password authentication.
git-svn-id: http://simplesamlphp.googlecode.com/svn/trunk@2872 44740490-163a-0410-bde0-09ae8108e29a
This commit is contained in:
parent
beee523cca
commit
94ef8f931a
|
@ -30,6 +30,8 @@ SimpleSAMLphp Documentation
|
|||
* [simpleSAMLphp Modules](simplesamlphp-modules) - how to create own customized modules
|
||||
* [Installing third party modules with the pack.php tool](pack)
|
||||
* [Key rollover](./saml:keyrollover)
|
||||
* [Creating authentication sources](./simplesamlphp-authsource)
|
||||
* [Implementing custom username/password authentication](./simplesamlphp-customauth)
|
||||
|
||||
Documentation on specific simpleSAMLphp modules:
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ If the username or password is incorrect, it should throw an error saying so:
|
|||
|
||||
throw new SimpleSAML_Error_Error('WRONGUSERPASS');
|
||||
|
||||
"[Implementing custom username/password authentication](./simplesamlphp-customauth)" describes how to implement username/password authentication using that base class.
|
||||
|
||||
|
||||
Generic rules & requirements
|
||||
|
|
|
@ -0,0 +1,350 @@
|
|||
Implementing custom username/password authentication
|
||||
====================================================
|
||||
|
||||
This is a step-by-step guide for creating a custom username/password [authentication source](./simplesamlphp-authsource) for simpleSAMLphp.
|
||||
An authentication source is responsible for authenticating the user, typically by getting a username and password, and looking it up in some sort of database.
|
||||
|
||||
<!-- {{TOC}} -->
|
||||
|
||||
Create a custom module
|
||||
----------------------
|
||||
|
||||
All custom code for simpleSAMLphp should be contained in a [module](./simplesamlphp-modules).
|
||||
This ensures that you can upgrade your simpleSAMLphp installation without overwriting your own code.
|
||||
In this example, we will call the module `mymodule`.
|
||||
It will be located under `modules/mymodule`.
|
||||
|
||||
First we need to create the module directory:
|
||||
|
||||
cd modules
|
||||
mkdir mymodule
|
||||
|
||||
Since this is a custom module, it should always be enabled.
|
||||
Therefore we create a `default-enable` file in the module.
|
||||
We do that by copying the `default-enable` file from the `core` module.
|
||||
|
||||
cd mymodule
|
||||
cp ../core/default-enable .
|
||||
|
||||
Now that we have our own module, we can move on to creating an authentication source.
|
||||
|
||||
|
||||
Creating a basic authentication source
|
||||
--------------------------------------
|
||||
|
||||
Authentication sources are implemented using PHP classes.
|
||||
We are going to create an authentication source named `mymodule:MyAuth`.
|
||||
It will be implemented in the file `modules/mymodule/lib/Auth/Source/MyAuth.php`.
|
||||
|
||||
To begin with, we will create a very simple authentication source, where the username and password is hardcoded into the source code.
|
||||
Create the file `modules/mymodule/lib/Auth/Source/MyAuth.php` with the following contents:
|
||||
|
||||
<?php
|
||||
class sspmod_mymodule_Auth_Source_MyAuth extends sspmod_core_Auth_UserPassBase {
|
||||
protected function login($username, $password) {
|
||||
if ($username !== 'theusername' || $password !== 'thepassword') {
|
||||
throw new SimpleSAML_Error_Error('WRONGUSERPASS');
|
||||
}
|
||||
return array(
|
||||
'uid' => array('theusername'),
|
||||
'displayName' => array('Some Random User'),
|
||||
'eduPersonAffiliation' => array('member', 'employee'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Some things to note:
|
||||
|
||||
- The classname is `sspmod_mymodule_Auth_Source_MyAuth`.
|
||||
This tells simpleSAMLphp to look for the class in `modules/mymodule/lib/Auth/Source/MyAuth.php`.
|
||||
|
||||
- Our authentication source subclassese `sspmod_core_Auth_UserPassBase`.
|
||||
This is a helper-class that implements much of the common code needed for username/password authentication.
|
||||
|
||||
- The `login` function receives the username and password the user enters.
|
||||
It is expected to authenticate the user.
|
||||
If the username or password is correct, it must return a set of attributes for the user.
|
||||
Otherwise, it must throw the `SimpleSAML_Error_Error('WRONGUSERPASS');` exception.
|
||||
|
||||
- Attributes are returned as an associative array of `name => values` pairs.
|
||||
All attributes can have multiple values, so the values are always stored in an array.
|
||||
|
||||
|
||||
Configuring our authentication source
|
||||
-------------------------------------
|
||||
|
||||
Before we can test our authentication source, we must add an entry for it in `config/authsources.php`.
|
||||
`config/authsources.php` contains an list of enabled authentication sources.
|
||||
|
||||
The entry looks like this:
|
||||
|
||||
'myauthinstance' => array(
|
||||
'mymodule:MyAuth',
|
||||
),
|
||||
|
||||
You can add it to the beginning of the list, so that the file looks something like this:
|
||||
|
||||
<?php
|
||||
$config = array(
|
||||
'myauthinstance' => array(
|
||||
'mymodule:MyAuth',
|
||||
),
|
||||
/* Other authentication sources follow. */
|
||||
);
|
||||
|
||||
`myauthinstance` is the name of this instance of the authentication source.
|
||||
(You are allowed to have multiple instances of an authentication source with different configuration.)
|
||||
The instance name is used to refer to this authentication source in other configuration files.
|
||||
|
||||
The first element of the configuration of the authentication source must be `'mymodule:MyAuth'`.
|
||||
This tells simpleSAMLphp to look for the `sspmod_mymodule_Auth_Source_MyAuth` class.
|
||||
|
||||
|
||||
Testing our authentication source
|
||||
---------------------------------
|
||||
|
||||
Now that we have configured the authentication source, we can test it by accessing "authentication"-page of the simpleSAMLphp web interface.
|
||||
By default, the web interface can be found on `http://yourhostname.com/simplesaml/`.
|
||||
(Obviously, "yourhostname.com" should be replaced with your real hostname.)
|
||||
|
||||
Then select the "Authentication"-tab, and choose "Test configured authentication sources".
|
||||
You should then receive a list of authentication sources from `config/authsources.php`.
|
||||
Select `myauthinstance`, and log in using "theusername" as the username, and "thepassword" as the password.
|
||||
You should then arrive on a page listing the attributes we return from the `login` function.
|
||||
|
||||
Next, you should log out by following the log out link.
|
||||
|
||||
|
||||
Using our authentication source in an IdP
|
||||
-----------------------------------------
|
||||
|
||||
To use our new authentication source in an IdP we just need to update the IdP configuration to use it.
|
||||
Open `metadata/saml20-idp-hosted.php`.
|
||||
In that file you should locate the `auth`-option for your IdP, and change it to `myauthinstance`:
|
||||
|
||||
<?php
|
||||
/* ... */
|
||||
$metadata['__DYNAMIC:1__'] = array(
|
||||
/* ... */
|
||||
/*
|
||||
* Authentication source to use. Must be one that is configured in
|
||||
* 'config/authsources.php'.
|
||||
*/
|
||||
'auth' => 'myauthinstance',
|
||||
/* ... */
|
||||
);
|
||||
|
||||
You can then test logging in to the IdP.
|
||||
If you have logged in previously, you may need to log out first.
|
||||
|
||||
|
||||
Adding configuration to our authentication source
|
||||
-------------------------------------------------
|
||||
|
||||
Instead of hardcoding options in our authentication source, they should be configurable.
|
||||
We are now going to extend our authentication source to allow us to configure the username and password in `config/authsources.php`.
|
||||
|
||||
First, we need to define the properties in the class that should hold our configuration:
|
||||
|
||||
private $username;
|
||||
private $password;
|
||||
|
||||
Next, we create a constructor for the class.
|
||||
The constructor is responsible for parsing the configuration and storing it in the properties.
|
||||
|
||||
public function __construct($info, $config) {
|
||||
parent::__construct($info, $config);
|
||||
if (!is_string($config['username'])) {
|
||||
throw new Exception('Missing or invalid username option in config.');
|
||||
}
|
||||
$this->username = $config['username'];
|
||||
if (!is_string($config['password'])) {
|
||||
throw new Exception('Missing or invalid password option in config.');
|
||||
}
|
||||
$this->password = $config['password'];
|
||||
}
|
||||
|
||||
We can then use the properties in the `login` function.
|
||||
The complete class file should look like this:
|
||||
|
||||
<?php
|
||||
class sspmod_mymodule_Auth_Source_MyAuth extends sspmod_core_Auth_UserPassBase {
|
||||
|
||||
private $username;
|
||||
private $password;
|
||||
|
||||
public function __construct($info, $config) {
|
||||
parent::__construct($info, $config);
|
||||
if (!is_string($config['username'])) {
|
||||
throw new Exception('Missing or invalid username option in config.');
|
||||
}
|
||||
$this->username = $config['username'];
|
||||
if (!is_string($config['password'])) {
|
||||
throw new Exception('Missing or invalid password option in config.');
|
||||
}
|
||||
$this->password = $config['password'];
|
||||
}
|
||||
|
||||
protected function login($username, $password) {
|
||||
if ($username !== $this->username || $password !== $this->password) {
|
||||
throw new SimpleSAML_Error_Error('WRONGUSERPASS');
|
||||
}
|
||||
return array(
|
||||
'uid' => array($this->username),
|
||||
'displayName' => array('Some Random User'),
|
||||
'eduPersonAffiliation' => array('member', 'employee'),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
We can then update our entry in `config/authsources.php` with the configuration options:
|
||||
|
||||
'myauthinstance' => array(
|
||||
'mymodule:MyAuth',
|
||||
'username' => 'theconfigusername',
|
||||
'password' => 'theconfigpassword',
|
||||
),
|
||||
|
||||
Next, you should go to the "Test configured authentication sources" page again, and test logging in.
|
||||
Note that we have updated the username & password to "theconfigusername" and "theconfigpassword".
|
||||
(You may need to log out first before you can log in again.)
|
||||
|
||||
|
||||
A more complete example - custom database authentication
|
||||
--------------------------------------------------------
|
||||
|
||||
The [sqlauth:SQL](./sqlauth:sql) authentication source can do simple authentication against SQL databases.
|
||||
However, in some cases it cannot be used, for example because the database layout is too complex, or because the password validation routines cannot be implemented in SQL.
|
||||
What follows is an example of an authentication source that fetches an user from a database, and validates the password using a custom function.
|
||||
|
||||
This code assumes that the database contains a table that looks like this:
|
||||
|
||||
CREATE TABLE userdb (
|
||||
username VARCHAR(32) PRIMARY KEY NOT NULL,
|
||||
password_hash VARCHAR(64) NOT NULL,
|
||||
full_name TEXT NOT NULL);
|
||||
|
||||
An example user (with password "secret"):
|
||||
|
||||
INSERT INTO userdb (username, password_hash, full_name)
|
||||
VALUES('exampleuser', 'QwVYkvlrAMsXIgULyQ/pDDwDI3dF2aJD4XeVxg==', 'Example User');
|
||||
|
||||
In this example, the `password_hash` contains a base64 encoded SSHA password.
|
||||
A SSHA password is created like this:
|
||||
|
||||
$password = 'secret';
|
||||
$numSalt = 8; /* Number of bytes with salt. */
|
||||
$salt = '';
|
||||
for ($i = 0; $i < $numSalt; $i++) {
|
||||
$salt .= chr(mt_rand(0, 255));
|
||||
}
|
||||
$digest = sha1($password . $salt, TRUE);
|
||||
$password_hash = base64_encode($digest . $salt);
|
||||
|
||||
The class follows:
|
||||
|
||||
<?php
|
||||
class sspmod_mymodule_Auth_Source_MyAuth extends sspmod_core_Auth_UserPassBase {
|
||||
|
||||
/* The database DSN.
|
||||
* See the documentation for the various database drivers for information about the syntax:
|
||||
* http://www.php.net/manual/en/pdo.drivers.php
|
||||
*/
|
||||
private $dsn;
|
||||
|
||||
/* The database username & password. */
|
||||
private $username;
|
||||
private $password;
|
||||
|
||||
public function __construct($info, $config) {
|
||||
parent::__construct($info, $config);
|
||||
|
||||
if (!is_string($config['dsn'])) {
|
||||
throw new Exception('Missing or invalid dsn option in config.');
|
||||
}
|
||||
$this->dsn = $config['dsn'];
|
||||
if (!is_string($config['username'])) {
|
||||
throw new Exception('Missing or invalid username option in config.');
|
||||
}
|
||||
$this->username = $config['username'];
|
||||
if (!is_string($config['password'])) {
|
||||
throw new Exception('Missing or invalid password option in config.');
|
||||
}
|
||||
$this->password = $config['password'];
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function for validating a password hash.
|
||||
*
|
||||
* In this example we check a SSHA-password, where the database
|
||||
* contains a base64 encoded byte string, where the first 20 bytes
|
||||
* from the byte string is the SHA1 sum, and the remaining bytes is
|
||||
* the salt.
|
||||
*/
|
||||
private function checkPassword($passwordHash, $password) {
|
||||
$passwordHash = base64_decode($passwordHash);
|
||||
$digest = substr($passwordHash, 0, 20);
|
||||
$salt = substr($passwordHash, 20);
|
||||
|
||||
$checkDigest = sha1($password . $salt, TRUE);
|
||||
return $digest === $checkDigest;
|
||||
}
|
||||
|
||||
protected function login($username, $password) {
|
||||
|
||||
/* Connect to the database. */
|
||||
$db = new PDO($this->dsn, $this->username, $this->password);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
/* Ensure that we are operating with UTF-8 encoding.
|
||||
* This command is for MySQL. Other databases may need different commands.
|
||||
*/
|
||||
$db->exec("SET NAMES 'utf8'");
|
||||
|
||||
/* With PDO we use prepared statements. This saves us from having to escape
|
||||
* the username in the database query.
|
||||
*/
|
||||
$st = $db->prepare('SELECT username, password_hash, full_name FROM userdb WHERE username=:username');
|
||||
|
||||
if (!$st->execute(array('username' => $username))) {
|
||||
throw new Exception('Failed to query database for user.');
|
||||
}
|
||||
|
||||
/* Retrieve the row from the database. */
|
||||
$row = $st->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$row) {
|
||||
/* User not found. */
|
||||
SimpleSAML_Logger::warning('MyAuth: Could not find user ' . var_export($username, TRUE) . '.');
|
||||
throw new SimpleSAML_Error_Error('WRONGUSERPASS');
|
||||
}
|
||||
|
||||
/* Check the password. */
|
||||
if (!$this->checkPassword($row['password_hash'], $password)) {
|
||||
/* Invalid password. */
|
||||
SimpleSAML_Logger::warning('MyAuth: Wrong password for user ' . var_export($username, TRUE) . '.');
|
||||
throw new SimpleSAML_Error_Error('WRONGUSERPASS');
|
||||
}
|
||||
|
||||
/* Create the attribute array of the user. */
|
||||
$attributes = array(
|
||||
'uid' => array($username),
|
||||
'displayName' => array($row['full_name']),
|
||||
'eduPersonAffiliation' => array('member', 'employee'),
|
||||
);
|
||||
|
||||
/* Return the attributes. */
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
And configured in `config/authsources.php`:
|
||||
|
||||
'myauthinstance' => array(
|
||||
'mymodule:MyAuth',
|
||||
'dsn' => 'mysql:host=sql.example.org;dbname=userdatabase',
|
||||
'username' => 'db_username',
|
||||
'password' => 'secret_db_password',
|
||||
),
|
||||
|
Reference in New Issue