diff --git a/docs/index.txt b/docs/index.txt
index b2eba5e993311ea9fa0d7989b9d38d851252b983..8ecea739c635fb614e30b1bb603756a7e4971278 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -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:
  
diff --git a/docs/simplesamlphp-authsource.txt b/docs/simplesamlphp-authsource.txt
index 311099a2426aa3b3acd89d70b2ae9033ebebb4f6..ad63b4967bd9a962e899927127289fb447c3398a 100644
--- a/docs/simplesamlphp-authsource.txt
+++ b/docs/simplesamlphp-authsource.txt
@@ -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
diff --git a/docs/simplesamlphp-customauth.txt b/docs/simplesamlphp-customauth.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d32e8a4e081a1d092035bb2d14e27c6a2535cd20
--- /dev/null
+++ b/docs/simplesamlphp-customauth.txt
@@ -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',
+    ),
+