The following example shows how to use a group filter to authenticate against
EITHER a database or an LDAP directory.
1. <?php
2. require_once 'tutorial_autoload.php';
3.
4. $credentials = new ezcAuthenticationPasswordCredentials( 'jan.modaal', 'qwerty' );
5.
6. // create a database filter
7. $database = new ezcAuthenticationDatabaseInfo( ezcDbInstance::get(), 'users', array( 'user', 'password' ) );
8. $databaseFilter = new ezcAuthenticationDatabaseFilter( $database );
9.
10. // create an LDAP filter
11. $ldap = new ezcAuthenticationLdapInfo( 'localhost', 'uid=%id%', 'dc=example,dc=com', 389 );
12. $ldapFilter = new ezcAuthenticationLdapFilter( $ldap );
13. $authentication = new ezcAuthentication( $credentials );
14.
15. // use the database and LDAP filters in paralel (only one needs to succeed in
16. // order for the user to be authenticated
17. $authentication->addFilter( new ezcAuthenticationGroupFilter( array( $databaseFilter, $ldapFilter ) ) );
18. // add more filters if needed
19. if ( !$authentication->run() )
20. {
21. // authentication did not succeed, so inform the user
22. $status = $authentication->getStatus();
23. $err = array(
24. 'ezcAuthenticationLdapFilter' => array(
25. ezcAuthenticationLdapFilter::STATUS_USERNAME_INCORRECT => 'Incorrect username',
26. ezcAuthenticationLdapFilter::STATUS_PASSWORD_INCORRECT => 'Incorrect password'
27. ),
28. 'ezcAuthenticationDatabaseFilter' => array(
29. ezcAuthenticationDatabaseFilter::STATUS_USERNAME_INCORRECT => 'Incorrect username',
30. ezcAuthenticationDatabaseFilter::STATUS_PASSWORD_INCORRECT => 'Incorrect password'
31. )
32. );
33. foreach ( $status as $line )
34. {
35. list( $key, $value ) = each( $line );
36. echo $err[$key][$value] . "\n";
37. }
38. }
39. else
40. {
41. // authentication succeeded, so allow the user to see his content
42. }
43. ?>
First, a credentials object is created with username 'jan.modaal' and password
'qwerty'.
An authentication object is created using the credentials object. A group
filter is added to it, consisting of a Database filter and an LDAP filter.
After running the authentication (line 19), if the username and the password do
not pass through any of the filters in the group, then the credentials are
incorrect and the user must be informed. The getStatus() method is used for
this. The values in the status returned must be cycled through and for each
value a response is created for the user ("Username incorrect", "Password
incorrect").
If run() returned true (line 39) then the user is logged-in and he can see his
content.
To be able to use multiple credentials for authentication (each filter with its
own credentials), you must enable the multipleCredentials option for
ezcAuthenticationGroupFilter.
The following example demonstrates how to use multiple credentials.
1. <?php
2. require_once 'tutorial_autoload.php';
3.
4. $credentials1 = new ezcAuthenticationPasswordCredentials( 'jan.modaal', 'b1b3773a05c0ed0176787a4f1574ff0075f7521e' ); // incorrect password
5. $credentials2 = new ezcAuthenticationPasswordCredentials( 'john.doe', 'wpeE20wyWHnLE' ); // correct username + password
6.
7. $options = new ezcAuthenticationGroupOptions();
8. $options->multipleCredentials = true;
9. $options->mode = ezcAuthenticationGroupFilter::MODE_AND;
10. $group = new ezcAuthenticationGroupFilter( array(), $options );
11.
12. $group->addFilter( new ezcAuthenticationHtpasswdFilter( '../../tests/filters/htpasswd/data/htpasswd' ), $credentials1 );
13. $group->addFilter( new ezcAuthenticationHtpasswdFilter( '../../tests/filters/htpasswd/data/htpasswd' ), $credentials2 );
14.
15. $authentication = new ezcAuthentication( $credentials1 );
16. $authentication->addFilter( $group );
17. // add more filters if needed
18.
19. if ( !$authentication->run() )
20. {
21. // authentication did not succeed, so inform the user
22. $status = $authentication->getStatus();
23.
24. $err = array(
25. array( 'ezcAuthenticationHtpasswdFilter' => array(
26. ezcAuthenticationHtpasswdFilter::STATUS_OK => '',
27. ezcAuthenticationHtpasswdFilter::STATUS_USERNAME_INCORRECT => 'Incorrect username ' . $credentials1->id,
28. ezcAuthenticationHtpasswdFilter::STATUS_PASSWORD_INCORRECT => 'Incorrect password for ' . $credentials1->id
29. ) ),
30.
31. array( 'ezcAuthenticationHtpasswdFilter' => array(
32. ezcAuthenticationHtpasswdFilter::STATUS_OK => '',
33. ezcAuthenticationHtpasswdFilter::STATUS_USERNAME_INCORRECT => 'Incorrect username ' . $credentials2->id,
34. ezcAuthenticationHtpasswdFilter::STATUS_PASSWORD_INCORRECT => 'Incorrect password for ' . $credentials2->id
35. ) )
36. );
37.
38. foreach ( $status as $line => $error )
39. {
40. list( $key, $value ) = each( $error );
41. echo $err[$line][$key][$value] . "\n";
42. }
43. }
44. else
45. {
46. // authentication succeeded, so allow the user to see his content
47. }
48. ?>
First, two credentials objects are created.
A Group filter is created with the multipleCredentials option enabled.
Two Htpasswd filters are added to the Group filter, each with their own
credentials.
An Authentication object is created with a default credentials (which would
have been used for other filters outside the Group filter, and to save the
authenticated state in the session).
The Group filter is then added to the Authentication object.
After running the authentication (line 19), if the usernames and the passwords
do not pass through the htpasswd filters, then the credentials are incorrect and
the user must be informed. The getStatus() method is used for this. The values
in the status returned must be cycled through and for each value a response is
created for the user ("Username incorrect john.doe", "Password incorrect for
john.doe", etc).
If run() returned true (line 44) then the user is logged-in and he can see his
content.
The above example will output:
Incorrect password for jan.modaal
The following example shows how to authenticate against an htpasswd file.
1. <?php
2. require_once 'tutorial_autoload.php';
3.
4. $credentials = new ezcAuthenticationPasswordCredentials( 'jan.modaal', 'b1b3773a05c0ed0176787a4f1574ff0075f7521e' );
5. $authentication = new ezcAuthentication( $credentials );
6. $authentication->session = new ezcAuthenticationSession();
7. $authentication->addFilter( new ezcAuthenticationHtpasswdFilter( '/etc/htpasswd' ) );
8. // add other filters if needed
9. if ( !$authentication->run() )
10. {
11. // authentication did not succeed, so inform the user
12. $status = $authentication->getStatus();
13. $err = array(
14. 'ezcAuthenticationHtpasswdFilter' => array(
15. ezcAuthenticationHtpasswdFilter::STATUS_USERNAME_INCORRECT => 'Incorrect username',
16. ezcAuthenticationHtpasswdFilter::STATUS_PASSWORD_INCORRECT => 'Incorrect password'
17. )
18. );
19. foreach ( $status as $line )
20. {
21. list( $key, $value ) = each( $line );
22. echo $err[$key][$value] . "\n";
23. }
24. }
25. else
26. {
27. // authentication succeeded, so allow the user to see his content
28. }
29. ?>
First, a credentials object is created with username jan.modaal and password
'b1b3773a05c0ed0176787a4f1574ff0075f7521e' (sha1() hash).
An authentication object is created using the credentials object, and a
htpasswd filter (using the /etc/htpasswd file) is added to it.
After running the authentication (line 9), if the username and the password do
not pass through the htpasswd filter, then the credentials are incorrect and
the user must be informed. The getStatus() method is used for this. The values
in the status returned must be cycled through and for each value a response is
created for the user ("Username incorrect", "Password incorrect").
If run() returned true (line 25) then the user is logged-in and he can see his
content.
The following example shows how to authenticate agains an LDAP directory.
1. <?php
2. require_once 'tutorial_autoload.php';
3.
4. $credentials = new ezcAuthenticationPasswordCredentials( 'jan.modaal', 'qwerty' );
5. $ldap = new ezcAuthenticationLdapInfo( 'localhost', 'uid=%id%', 'dc=example,dc=com', 389 );
6. $authentication = new ezcAuthentication( $credentials );
7. $authentication->addFilter( new ezcAuthenticationLdapFilter( $ldap ) );
8. // add more filters if needed
9. if ( !$authentication->run() )
10. {
11. // authentication did not succeed, so inform the user
12. $status = $authentication->getStatus();
13. $err = array(
14. 'ezcAuthenticationLdapFilter' => array(
15. ezcAuthenticationLdapFilter::STATUS_USERNAME_INCORRECT => 'Incorrect username',
16. ezcAuthenticationLdapFilter::STATUS_PASSWORD_INCORRECT => 'Incorrect password'
17. )
18. );
19. foreach ( $status as $line )
20. {
21. list( $key, $value ) = each( $line );
22. echo $err[$key][$value] . "\n";
23. }
24. }
25. else
26. {
27. // authentication succeeded, so allow the user to see his content
28. }
29. ?>
First, a credentials object is created with username jan.modaal and password
'qwerty'.
An authentication object is created using the credentials object, and an
LDAP filter is added to it. The $ldap structure specifies the LDAP host
(localhost), the format of the directory entry (%id% is a placeholder which
will be replaced by the actual value at bind time), the base of the directory
entry ('dc=example,dc=com') and the port on which to connect to the host (389).
After running the authentication (line 7), if the username and the password do
not pass through the LDAP filter, then the credentials are incorrect and
the user must be informed. The getStatus() method is used for this. The values
in the status returned must be cycled through and for each value a response is
created for the user ("Username incorrect", "Password incorrect").
If run() returned true (line 34) then the user is logged-in and he can see his
content.
Any data that is defined for an acount can be fetched. Before running the
authentication process (before calling run(), register which data needs to be
fetched. Example:
// $filter is an ezcAuthenticationLdapFilter object
$filter->registerFetchData( array( 'name', 'company', 'mobile' ) );
After the authentication process is finished (after run()), retrieve the data
that was registered:
// $filter is an ezcAuthenticationLdapFilter object
$data = $filter->fetchData();
For the previous example, the $data array will be something like this:
array( 'name' => array( 'Dr. No' ),
'company' => array( 'SPECTRE' ),
'mobile' => array( '555-7732873' )
);
OpenID has 2 modes of operation: dumb and smart. These modes define the way
the consumer (the application server, on which the Components run) is
communicating with the OpenID provider (another server where users authenticate
with their OpenID username and password; there are many of these and users can
register on which one they want).
Dumb mode (stateless)
In this mode there are 3 http requests:
- Discovery
- The consumer requests the URL which the user entered and finds out the URL of
the provider.
- openid.checkid_setup
- The consumer redirects the browser to the provider, so that the user can
enter his username and password to the provider. The provider then redirects
back to the consumer. The checkid_immediate mode is supported as well, for
authentication in a pop-up window or iframe (or similar techniques). See
below the section OpenID immediate mode for details.
- openid.check_authentication
- The consumer sends to the provider the values received in step 2 and receives
the information if the user is authenticated or not.
Smart mode (keeping state)
In this mode there are also 3 http requests, but only 2 every time and 1
request from time to time:
- Discovery
- Same as in dumb mode.
- openid.checkid_setup
- Same as in dumb mode, but the handle associated with the shared secret is
sent as well.
The extra request (which is done from time to time) is:
- openid.associate
- The consumer and the provider establish a shared secret, which the consumer
uses when it redirects in step 2, and it will use the same secret for all
requests to the same provider. Step 3 (openid.check_authentication) is not
required anymore. The shared secret has a timeout period, so it must be
renewed from time to time.
The following example shows how to authenticate against OpenID in "dumb"
(stateless) mode.
1. <?php
2. require_once 'tutorial_autoload.php';
3.
4. // no headers should be sent before calling $session->start()
5. $session = new ezcAuthenticationSession();
6. $session->start();
7.
8. $url = isset( $_GET['openid_identifier'] ) ? $_GET['openid_identifier'] : $session->load();
9. $action = isset( $_GET['action'] ) ? strtolower( $_GET['action'] ) : null;
10.
11. $credentials = new ezcAuthenticationIdCredentials( $url );
12. $authentication = new ezcAuthentication( $credentials );
13. $authentication->session = $session;
14.
15. if ( $action === 'logout' )
16. {
17. $session->destroy();
18. }
19. else
20. {
21. $filter = new ezcAuthenticationOpenidFilter();
22. $authentication->addFilter( $filter );
23. }
24.
25. if ( !$authentication->run() )
26. {
27. // authentication did not succeed, so inform the user
28. $status = $authentication->getStatus();
29. $err = array(
30. 'ezcAuthenticationOpenidFilter' => array(
31. ezcAuthenticationOpenidFilter::STATUS_SIGNATURE_INCORRECT => 'OpenID said the provided identifier was incorrect',
32. ezcAuthenticationOpenidFilter::STATUS_CANCELLED => 'The OpenID authentication was cancelled',
33. ezcAuthenticationOpenidFilter::STATUS_URL_INCORRECT => 'The identifier you provided is invalid'
34. ),
35. 'ezcAuthenticationSession' => array(
36. ezcAuthenticationSession::STATUS_EMPTY => '',
37. ezcAuthenticationSession::STATUS_EXPIRED => 'Session expired'
38. )
39. );
40. foreach ( $status as $line )
41. {
42. list( $key, $value ) = each( $line );
43. echo $err[$key][$value] . "\n";
44. }
45. ?>
46. Please login with your OpenID identifier (an URL, eg. www.example.com or http://www.example.com):
47. <form method="GET" action="">
48. <input type="hidden" name="action" value="login" />
49. <img src="http://openid.net/login-bg.gif" /> <input type="text" name="openid_identifier" />
50. <input type="submit" value="Login" />
51. </form>
52.
53. <?php
54. }
55. else
56. {
57. ?>
58.
59. You are logged-in as <b><?php echo $url; ?></b> | <a href="?action=logout">Logout</a>
60.
61. <?php
62. }
63. ?>
A session class is created and used to start the PHP session. The OpenID
identifier (provided by the user through a GET form) is fetched and used to
create a credentials object. On subsequent requests to the page, the token is
loaded from session instead of the GET form. OpenID specifications recommend
the name 'openid_identifier' for the text field of the form in which users type
their OpenID identifier (so that browser can prefill the field if user chooses
this).
An authentication object is created using the credentials object, and the
session handler is added to it.
If the user is at logout (line 15), then the session is destroyed, which means
the user will see the login form.
If the user is not at logout (line 19), then an OpenID filter is created with
the credentials object.
After running the authentication (line 25), if the OpenID server did not
authorize the identifier, then the credentials are incorrect and the user must
be informed. The getStatus() method is used for this. The values in the status
returned must be cycled through and for each value a response is created for
the user ("Signature incorrect", "Session expired"). At line 46 a simple HTML
form is displayed, as example. The form displays the OpenID logo (as suggested
by the OpenID specifications).
If run() returned true (line 55) then the user is logged-in and he can see his
content. Line 59 contains an example of how to implement a logout option for
the application.
The following example shows how to authenticate against OpenID in "smart"
(stateful) mode.
1. <?php
2. require_once 'tutorial_autoload.php';
3.
4. // no headers should be sent before calling $session->start()
5. $session = new ezcAuthenticationSession();
6. $session->start();
7.
8. $url = isset( $_GET['openid_identifier'] ) ? $_GET['openid_identifier'] : $session->load();
9. $action = isset( $_GET['action'] ) ? strtolower( $_GET['action'] ) : null;
10.
11. $credentials = new ezcAuthenticationIdCredentials( $url );
12. $authentication = new ezcAuthentication( $credentials );
13. $authentication->session = $session;
14.
15. if ( $action === 'logout' )
16. {
17. $session->destroy();
18. }
19. else
20. {
21. $options = new ezcAuthenticationOpenidOptions();
22. $options->mode = ezcAuthenticationOpenidFilter::MODE_SMART;
23.
24. // define a file store
25. $options->store = new ezcAuthenticationOpenidFileStore( '/tmp/store' );
26.
27. $filter = new ezcAuthenticationOpenidFilter( $options );
28. $authentication->addFilter( $filter );
29. }
30.
31. if ( !$authentication->run() )
32. {
33. // authentication did not succeed, so inform the user
34. $status = $authentication->getStatus();
35. $err = array(
36. 'ezcAuthenticationOpenidFilter' => array(
37. ezcAuthenticationOpenidFilter::STATUS_SIGNATURE_INCORRECT => 'OpenID said the provided identifier was incorrect',
38. ezcAuthenticationOpenidFilter::STATUS_CANCELLED => 'The OpenID authentication was cancelled',
39. ezcAuthenticationOpenidFilter::STATUS_URL_INCORRECT => 'The identifier you provided is invalid'
40. ),
41. 'ezcAuthenticationSession' => array(
42. ezcAuthenticationSession::STATUS_EMPTY => '',
43. ezcAuthenticationSession::STATUS_EXPIRED => 'Session expired'
44. )
45. );
46. foreach ( $status as $line )
47. {
48. list( $key, $value ) = each( $line );
49. echo $err[$key][$value] . "\n";
50. }
51. ?>
52. Please login with your OpenID identifier (an URL, eg. www.example.com or http://www.example.com):
53. <form method="GET" action="">
54. <input type="hidden" name="action" value="login" />
55. <img src="http://openid.net/login-bg.gif" /> <input type="text" name="openid_identifier" />
56. <input type="submit" value="Login" />
57. </form>
58.
59. <?php
60. }
61. else
62. {
63. ?>
64.
65. You are logged-in as <b><?php echo $url; ?></b> | <a href="?action=logout">Logout</a>
66.
67. <?php
68. }
69. ?>
The only differences between this example and the one in the previous section
is defining the mode of the OpenID filter, and defining a store (file store in
this example or database store as shown in the OpenID example in
AuthenticationDatabaseTiein) which will hold the associations. In addition
the store will also hold the nonces which are used to prevent replay attacks.
The OpenID request checkid_immediate is supported, which allows for user
authentication in a pop-up window or iframe (or similar techniques). Instead of
redirecting the user agent as in the checkid_setup step, the developer has the
possibility to open a pop-up/iframe for the user to authenticate with the
OpenID provider.
A more detailed description of the process: when using checkid_immediate, the
OpenID provider is asked if the user can be authenticated on the spot, with no
redirection of the user agent. If the user cannot be authenticated, the provider
sends back a setup URL, which the application can use in a pop-up window or
iframe to display to the user so that he can authenticate himself to the OpenID
provider. After user enters his OpenID username and password at this page and
accepts the originating site, the pop-up window or iframe is redirected to the
return URL value (which should be a different page than the page which opens the
pop-up window). The return URL page will then inform the main page of success or
failure through JavaScript, and the main page can do the action that it needs to
perform based on the outcome in the pop-up page.
The checkid_immediate mode is enabled by setting the option immediate to true.
Note: retrieval of extra data during authentication (fullname, email, etc) is
not possible at the moment when using the immediate mode.
For example, this is one simple way of implementing checkid_immediate:
- the main page contains the OpenID login form (where the user types in his
OpenID identifier). This page contains also a hidded form value which
specifies to which page to return to in the pop-up window. The Enter key
and the submit button should be disabled on the form. When user clicks on
the Login button, the main page should employ AJAX to request the return
URL. When the return URL finishes loading, the main page will read from the
return URL page the setup URL and it will open it in a pop-up/iframe.
- the return URL page enables the option immediate to the OpenID filter, and
runs the filter. It gets back the setup URL and it echoes it to be picked-up
by the main page once the return URL page will finish loading. The setup URL
should be the only thing that the return URL page is echoing, to not interfere
with the main page.
- in the pop-up/iframe the setup URL will load, which basically depends on
the OpenID provider how it is handled by the user. After the user enters
his credentials on the setup URL page, he will be redirected to the return URL,
which should detect this, and which should inform the main page that the
user was authenticated to the OpenID provider.
A rudimentary source code example is provided below. It does not contain code
to inform the user that the session expired or the errors experienced during
the authentication process. The code has been tested on some browsers (Firefox
1.5, Konqueror 3.5, Internet Explorer 6.0), but it is possible that some
browsers might have issues with the JavaScript code.
The main page:
1. <?php
2. require_once 'tutorial_autoload.php';
3.
4. // no headers should be sent before calling $session->start()
5. $options = new ezcAuthenticationSessionOptions();
6.
7. // setting 60 seconds timeout for session for testing purposes only
8. $options->validity = 60;
9.
10. $session = new ezcAuthenticationSession( $options );
11. $session->start();
12.
13. $identity = $session->load();
14.
15. $url = isset( $_GET['openid_identifier'] ) ? $_GET['openid_identifier'] : $identity;
16. $action = isset( $_GET['action'] ) ? strtolower( $_GET['action'] ) : null;
17.
18. $credentials = new ezcAuthenticationIdCredentials( $url );
19. $authentication = new ezcAuthentication( $credentials );
20. $authentication->session = $session;
21.
22. if ( $action === 'logout' )
23. {
24. $session->destroy();
25. }
26.
27. if ( !$authentication->run() )
28. {
29. // authentication did not succeed, so inform the user
30.
31. ?>
32.
33. <script language="JavaScript">
34. var xmlhttp = false;
35.
36. /*@cc_on @*/
37. /*@if ( @_jscript_version >= 5 )
38. try
39. {
40. xmlhttp = new ActiveXObject( "Msxml2.XMLHTTP" );
41. }
42. catch ( e )
43. {
44. try
45. {
46. xmlhttp = new ActiveXObject( "Microsoft.XMLHTTP" );
47. }
48. catch ( E )
49. {
50. xmlhttp = false;
51. }
52. }
53. @end @*/
54.
55. if ( !xmlhttp && typeof XMLHttpRequest != 'undefined' )
56. {
57. try
58. {
59. xmlhttp = new XMLHttpRequest();
60. }
61. catch ( e )
62. {
63. xmlhttp = false;
64. }
65. }
66.
67. if ( !xmlhttp && window.createRequest )
68. {
69. try
70. {
71. xmlhttp = window.createRequest();
72. }
73. catch ( e )
74. {
75. xmlhttp = false;
76. }
77. }
78. </script>
79.
80. <script language="JavaScript">
81. function disableEnterKey( e )
82. {
83. var key;
84. key = ( window.event ) ? window.event.keyCode : e.which;
85.
86. return ( key != 13 );
87. }
88.
89. function login()
90. {
91. var url;
92. var form1;
93. var setupUrl;
94.
95. form1 = document.form1;
96.
97. url = form1.url.value + '?openid_identifier=' + escape( form1.openid_identifier.value ) +
98. '&action=login&immediate=true';
99.
100. xmlhttp.open( "GET", url, true );
101. xmlhttp.onreadystatechange = function()
102. {
103. if ( xmlhttp.readyState == 4 )
104. {
105. setupUrl = xmlhttp.responseText;
106. hwnd = window.open( setupUrl, 'Login' );
107. if ( hwnd.opener == null )
108. {
109. hwnd.opener = self;
110. }
111. }
112.