Zend_Auth_Adapter_DbTable.xml 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- Reviewed: no -->
  3. <sect1 id="zend.auth.adapter.dbtable">
  4. <title>Database Table Authentication</title>
  5. <sect2 id="zend.auth.adapter.dbtable.introduction">
  6. <title>Introduction</title>
  7. <para>
  8. <classname>Zend_Auth_Adapter_DbTable</classname> provides the ability to
  9. authenticate against credentials stored in a database table. Because
  10. <classname>Zend_Auth_Adapter_DbTable</classname> requires an instance of
  11. <classname>Zend_Db_Adapter_Abstract</classname> to be passed to its
  12. constructor, each instance is bound to a particular database
  13. connection. Other configuration options may be set through the
  14. constructor and through instance methods, one for each option.
  15. </para>
  16. <para>
  17. The available configuration options include:
  18. </para>
  19. <itemizedlist>
  20. <listitem>
  21. <para>
  22. <emphasis><property>tableName</property></emphasis>: This is the name of the
  23. database table that contains the authentication credentials,
  24. and against which the database authentication query is
  25. performed.
  26. </para>
  27. </listitem>
  28. <listitem>
  29. <para>
  30. <emphasis><property>identityColumn</property></emphasis>: This is the name of
  31. the database table column used to represent the identity.
  32. The identity column must contain unique values, such as
  33. a username or e-mail address.
  34. </para>
  35. </listitem>
  36. <listitem>
  37. <para>
  38. <emphasis><property>credentialColumn</property></emphasis>: This is the name
  39. of the database table column used to represent the credential.
  40. Under a simple identity and password authentication
  41. scheme, the credential value corresponds to the
  42. password. See also the <property>credentialTreatment</property>
  43. option.
  44. </para>
  45. </listitem>
  46. <listitem>
  47. <para>
  48. <emphasis><property>credentialTreatment</property></emphasis>: In many cases,
  49. passwords and other sensitive data are encrypted,
  50. hashed, encoded, obscured, salted or otherwise treated
  51. through some function or algorithm. By specifying a
  52. parameterized treatment string with this method, such as
  53. '<methodname>MD5(?)</methodname>' or
  54. '<methodname>PASSWORD(?)</methodname>', a
  55. developer may apply such arbitrary <acronym>SQL</acronym> upon input
  56. credential data. Since these functions are specific to
  57. the underlying <acronym>RDBMS</acronym>, check the database manual for the
  58. availability of such functions for your database system.
  59. </para>
  60. </listitem>
  61. </itemizedlist>
  62. <example id="zend.auth.adapter.dbtable.introduction.example.basic_usage">
  63. <title>Basic Usage</title>
  64. <para>
  65. As explained in the introduction, the
  66. <classname>Zend_Auth_Adapter_DbTable</classname> constructor requires an
  67. instance of <classname>Zend_Db_Adapter_Abstract</classname> that serves as
  68. the database connection to which the authentication adapter
  69. instance is bound. First, the database connection should be
  70. created.
  71. </para>
  72. <para>
  73. The following code creates an adapter for an in-memory database,
  74. creates a simple table schema, and inserts a row against
  75. which we can perform an authentication query later. This example
  76. requires the <acronym>PDO</acronym> SQLite extension to be available:
  77. </para>
  78. <programlisting language="php"><![CDATA[
  79. // Create an in-memory SQLite database connection
  80. $dbAdapter = new Zend_Db_Adapter_Pdo_Sqlite(array('dbname' =>
  81. ':memory:'));
  82. // Build a simple table creation query
  83. $sqlCreate = 'CREATE TABLE [users] ('
  84. . '[id] INTEGER NOT NULL PRIMARY KEY, '
  85. . '[username] VARCHAR(50) UNIQUE NOT NULL, '
  86. . '[password] VARCHAR(32) NULL, '
  87. . '[real_name] VARCHAR(150) NULL)';
  88. // Create the authentication credentials table
  89. $dbAdapter->query($sqlCreate);
  90. // Build a query to insert a row for which authentication may succeed
  91. $sqlInsert = "INSERT INTO users (username, password, real_name) "
  92. . "VALUES ('my_username', 'my_password', 'My Real Name')";
  93. // Insert the data
  94. $dbAdapter->query($sqlInsert);
  95. ]]></programlisting>
  96. <para>
  97. With the database connection and table data available, an
  98. instance of <classname>Zend_Auth_Adapter_DbTable</classname> may be
  99. created. Configuration option values may be passed to the
  100. constructor or deferred as parameters to setter methods after
  101. instantiation:
  102. </para>
  103. <programlisting language="php"><![CDATA[
  104. // Configure the instance with constructor parameters...
  105. $authAdapter = new Zend_Auth_Adapter_DbTable(
  106. $dbAdapter,
  107. 'users',
  108. 'username',
  109. 'password'
  110. );
  111. // ...or configure the instance with setter methods
  112. $authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);
  113. $authAdapter
  114. ->setTableName('users')
  115. ->setIdentityColumn('username')
  116. ->setCredentialColumn('password')
  117. ;
  118. ]]></programlisting>
  119. <para>
  120. At this point, the authentication adapter instance is ready to
  121. accept authentication queries. In order to formulate an
  122. authentication query, the input credential values are passed to
  123. the adapter prior to calling the <methodname>authenticate()</methodname>
  124. method:
  125. </para>
  126. <programlisting language="php"><![CDATA[
  127. // Set the input credential values (e.g., from a login form)
  128. $authAdapter
  129. ->setIdentity('my_username')
  130. ->setCredential('my_password')
  131. ;
  132. // Perform the authentication query, saving the result
  133. ]]></programlisting>
  134. <para>
  135. In addition to the availability of the
  136. <methodname>getIdentity()</methodname> method upon the authentication result
  137. object, <classname>Zend_Auth_Adapter_DbTable</classname> also supports
  138. retrieving the table row upon authentication success:
  139. </para>
  140. <programlisting language="php"><![CDATA[
  141. // Print the identity
  142. echo $result->getIdentity() . "\n\n";
  143. // Print the result row
  144. print_r($authAdapter->getResultRowObject());
  145. /* Output:
  146. my_username
  147. Array
  148. (
  149. [id] => 1
  150. [username] => my_username
  151. [password] => my_password
  152. [real_name] => My Real Name
  153. )
  154. ]]></programlisting>
  155. <para>
  156. Since the table row contains the credential value, it is
  157. important to secure the values against unintended access.
  158. </para>
  159. </example>
  160. </sect2>
  161. <sect2 id="zend.auth.adapter.dbtable.advanced.storing_result_row">
  162. <title>Advanced Usage: Persisting a DbTable Result Object</title>
  163. <para>
  164. By default, <classname>Zend_Auth_Adapter_DbTable</classname> returns the
  165. identity supplied back to the auth object upon successful
  166. authentication. Another use case scenario, where developers want to
  167. store to the persistent storage mechanism of <classname>Zend_Auth</classname>
  168. an identity object containing other useful information, is solved by
  169. using the <methodname>getResultRowObject()</methodname> method to return a
  170. <emphasis>stdClass</emphasis> object. The following code snippet illustrates
  171. its use:
  172. </para>
  173. <programlisting language="php"><![CDATA[
  174. // authenticate with Zend_Auth_Adapter_DbTable
  175. $result = $this->_auth->authenticate($adapter);
  176. if ($result->isValid()) {
  177. // store the identity as an object where only the username and
  178. // real_name have been returned
  179. $storage = $this->_auth->getStorage();
  180. $storage->write($adapter->getResultRowObject(array(
  181. 'username',
  182. 'real_name',
  183. )));
  184. // store the identity as an object where the password column has
  185. // been omitted
  186. $storage->write($adapter->getResultRowObject(
  187. null,
  188. 'password'
  189. ));
  190. /* ... */
  191. } else {
  192. /* ... */
  193. }
  194. ]]></programlisting>
  195. </sect2>
  196. <sect2 id="zend.auth.adapter.dbtable.advanced.advanced_usage">
  197. <title>Advanced Usage By Example</title>
  198. <para>
  199. While the primary purpose of <classname>Zend_Auth</classname> (and consequently
  200. <classname>Zend_Auth_Adapter_DbTable</classname>) is primarily
  201. <emphasis>authentication</emphasis> and not
  202. <emphasis>authorization</emphasis>, there are a few
  203. instances and problems that toe the line between which domain they fit
  204. within. Depending on how you've decided to explain your problem, it
  205. sometimes makes sense to solve what could look like an
  206. authorization problem within the authentication adapter.
  207. </para>
  208. <para>
  209. With that disclaimer out of the way, <classname>Zend_Auth_Adapter_DbTable</classname>
  210. has some built in mechanisms that can be leveraged for additional checks at
  211. authentication time to solve some common user problems.
  212. </para>
  213. <programlisting language="php"><![CDATA[
  214. // The status field value of an account is not equal to "compromised"
  215. $adapter = new Zend_Auth_Adapter_DbTable(
  216. $db,
  217. 'users',
  218. 'username',
  219. 'password',
  220. 'MD5(?) AND status != "compromised"'
  221. );
  222. // The active field value of an account is equal to "TRUE"
  223. $adapter = new Zend_Auth_Adapter_DbTable(
  224. $db,
  225. 'users',
  226. 'username',
  227. 'password',
  228. 'MD5(?) AND active = "TRUE"'
  229. ]]></programlisting>
  230. <para>
  231. Another scenario can be the implementation of a salting mechanism.
  232. Salting is a term referring to a technique which can highly improve
  233. your application's security. It's based on the idea that
  234. concatenating a random string to every password makes it impossible
  235. to accomplish a successful brute force attack on the database using
  236. pre-computed hash values from a dictionary.
  237. </para>
  238. <para>
  239. Therefore, we need to modify our table to store our salt string:
  240. </para>
  241. <programlisting language="php"><![CDATA[
  242. $sqlAlter = "ALTER TABLE [users] "
  243. . "ADD COLUMN [password_salt] "
  244. . "AFTER [password]";
  245. ]]></programlisting>
  246. <para>
  247. Here's a simple way to generate a salt string for every user at
  248. registration:
  249. </para>
  250. <programlisting language="php"><![CDATA[
  251. for ($i = 0; $i < 50; $i++) {
  252. $dynamicSalt .= chr(rand(33, 126));
  253. ]]></programlisting>
  254. <para>
  255. And now let's build the adapter:
  256. </para>
  257. <programlisting language="php"><![CDATA[
  258. $adapter = new Zend_Auth_Adapter_DbTable(
  259. $db,
  260. 'users',
  261. 'username',
  262. 'password',
  263. "MD5(CONCAT('"
  264. . Zend_Registry::get('staticSalt')
  265. . "', ?, password_salt))"
  266. );
  267. ]]></programlisting>
  268. <note>
  269. <para>
  270. You can improve security even more by using a static salt value
  271. hard coded into your application. In the case that your database
  272. is compromised (e. g. by an <acronym>SQL</acronym> injection attack) but your web
  273. server is intact your data is still unusable for the attacker.
  274. </para>
  275. </note>
  276. <para>
  277. Another alternative is to use the <methodname>getDbSelect()</methodname> method
  278. of the <classname>Zend_Auth_Adapter_DbTable</classname> after the adapter has been
  279. constructed. This method will return the <classname>Zend_Db_Select</classname> object
  280. instance it will use to complete the <methodname>authenticate()</methodname> routine.
  281. It is important to note that this method will always return the same object regardless
  282. if <methodname>authenticate()</methodname> has been called or not. This object
  283. <emphasis>will not</emphasis> have any of the identity or credential information in it
  284. as those values are placed into the select object at
  285. <methodname>authenticate()</methodname> time.
  286. </para>
  287. <para>
  288. An example of a situation where one might want to use the
  289. <methodname>getDbSelect()</methodname> method would check the status of a user, in
  290. other words to see if that user's account is enabled.
  291. </para>
  292. <programlisting language="php"><![CDATA[
  293. // Continuing with the example from above
  294. $adapter = new Zend_Auth_Adapter_DbTable(
  295. $db,
  296. 'users',
  297. 'username',
  298. 'password',
  299. 'MD5(?)'
  300. );
  301. // get select object (by reference)
  302. $select = $adapter->getDbSelect();
  303. $select->where('active = "TRUE"');
  304. // authenticate, this ensures that users.active = TRUE
  305. $adapter->authenticate();
  306. ]]></programlisting>
  307. </sect2>
  308. </sect1>