Source code for file /openid/Auth/OpenID/Server.php
Documentation is available at Server.php
* OpenID server protocol and logic.
* An OpenID server must perform three tasks:
* 1. Examine the incoming request to determine its nature and validity.
* 2. Make a decision about how to respond to this request.
* 3. Format the response according to the protocol.
* The first and last of these tasks may performed by the {@link }
* Auth_OpenID_Server::decodeRequest()} and {@link }
* Auth_OpenID_Server::encodeResponse} methods. Who gets to do the
* intermediate task -- deciding how to respond to the request -- will
* depend on what type of request it is.
* If it's a request to authenticate a user (a 'checkid_setup' or
* 'checkid_immediate' request), you need to decide if you will assert
* that this user may claim the identity in question. Exactly how you
* do that is a matter of application policy, but it generally
* involves making sure the user has an account with your system and
* is logged in, checking to see if that identity is hers to claim,
* and verifying with the user that she does consent to releasing that
* information to the party making the request.
* Examine the properties of the {@link Auth_OpenID_CheckIDRequest}
* object, and if and when you've come to a decision, form a response
* by calling {@link Auth_OpenID_CheckIDRequest::answer()}.
* Other types of requests relate to establishing associations between
* client and server and verifing the authenticity of previous
* communications. {@link Auth_OpenID_Server} contains all the logic
* and data necessary to respond to such requests; just pass it to
* {@link Auth_OpenID_Server::handleRequest()}.
* Do you want to provide other information for your users in addition
* to authentication? Version 1.2 of the OpenID protocol allows
* consumers to add extensions to their requests. For example, with
* sites using the Simple Registration
* (http://www.openidenabled.com/openid/simple-registration-extension/),
* a user can agree to have their nickname and e-mail address sent to
* a site when they sign up.
* Since extensions do not change the way OpenID authentication works,
* code to handle extension requests may be completely separate from
* the {@link Auth_OpenID_Request} class here. But you'll likely want
* data sent back by your extension to be signed. {@link }
* Auth_OpenID_ServerResponse} provides methods with which you can add
* data to it which can be signed with the other data in the OpenID
* <pre> // when request is a checkid_* request
* $response = $request->answer(true);
* // this will a signed 'openid.sreg.timezone' parameter to the response
* response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre>
* The OpenID server needs to maintain state between requests in order
* to function. Its mechanism for doing this is called a store. The
* store interface is defined in Interface.php. Additionally, several
* concrete store implementations are provided, so that most sites
* won't need to implement a custom store. For a store backed by flat
* files on disk, see {@link Auth_OpenID_FileStore}. For stores based
* on MySQL, SQLite, or PostgreSQL, see the {@link }
* Auth_OpenID_SQLStore} subclasses.
* The keys by which a server looks up associations in its store have
* changed in version 1.2 of this library. If your store has entries
* created from version 1.0 code, you should empty it.
* LICENSE: See the COPYING file included in this distribution.
* @author JanRain, Inc. <openid@janrain.com>
* @copyright 2005-2008 Janrain, Inc.
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache
// Do not allow direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
require_once "Auth/OpenID.php";
require_once "Auth/OpenID/Association.php";
require_once "Auth/OpenID/CryptUtil.php";
require_once "Auth/OpenID/BigMath.php";
require_once "Auth/OpenID/DiffieHellman.php";
require_once "Auth/OpenID/KVForm.php";
require_once "Auth/OpenID/TrustRoot.php";
require_once "Auth/OpenID/ServerRequest.php";
require_once "Auth/OpenID/Message.php";
require_once "Auth/OpenID/Nonce.php";
define('AUTH_OPENID_HTTP_OK', 200);
define('AUTH_OPENID_HTTP_REDIRECT', 302);
define('AUTH_OPENID_HTTP_ERROR', 400);
global $_Auth_OpenID_Request_Modes;
$_Auth_OpenID_Request_Modes =
array('checkid_setup',
define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');
define('Auth_OpenID_ENCODE_URL', 'URL/redirect');
define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');
function Auth_OpenID_isError($obj, $cls =
'Auth_OpenID_ServerError')
* An error class which gets instantiated and returned whenever an
* OpenID protocol error occurs. Be prepared to use this in place of
* an ordinary server response.
function Auth_OpenID_ServerError($message =
null, $text =
null,
$reference =
null, $contact =
null)
$this->message =
$message;
$this->contact =
$contact;
$this->reference =
$reference;
* Returns the return_to URL for the request which caused this
* Encodes this error's response as a URL suitable for
* redirection. If the response has no return_to, another
* Auth_OpenID_ServerError is returned.
* Encodes the response to key-value form. This is a
* machine-readable format used to respond to messages which came
* directly from the consumer and not through the user-agent. See
* the OpenID specification.
return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs);
function toHTML($form_tag_attrs=
null)
return Auth_OpenID::autoSubmitHTML(
// Generate a Message object for sending to the relying party,
$namespace =
$this->message->getOpenIDNamespace();
if ($this->contact !==
null) {
if ($this->reference !==
null) {
* Returns one of Auth_OpenID_ENCODE_URL,
* Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of
* encoding expected for this error's payload.
global $_Auth_OpenID_Request_Modes;
if ($this->message->isOpenID2() &&
return Auth_OpenID_ENCODE_HTML_FORM;
return Auth_OpenID_ENCODE_URL;
if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
return Auth_OpenID_ENCODE_KVFORM;
* Returns this error message.
* Error returned by the server code when a return_to is absent from a
$text =
"No return_to URL available")
parent::Auth_OpenID_ServerError($message, $text);
return "No return_to available";
* An error indicating that the return_to URL is malformed.
$this->return_to =
$return_to;
parent::Auth_OpenID_ServerError($message, "malformed return_to URL");
* This error is returned when the trust_root value is malformed.
$text =
"Malformed trust root")
parent::Auth_OpenID_ServerError($message, $text);
return "Malformed trust root";
* The base class for all server request classes.
* A request to verify the validity of a previous response.
var $mode =
"check_authentication";
$invalidate_handle =
null)
$this->assoc_handle =
$assoc_handle;
if ($invalidate_handle !==
null) {
$required_keys =
array('assoc_handle', 'sig', 'signed');
foreach ($required_keys as $k) {
sprintf("%s request missing required parameter %s from \
query", "check_authentication", $k));
$signed_list =
explode(",", $signed_list);
$result->message =
$message;
$is_valid =
$signatory->verify($this->assoc_handle, $this->signed);
// Now invalidate that assoc_handle so it this checkAuth
// message cannot be replayed.
$signatory->invalidate($this->assoc_handle, true);
($is_valid ?
"true" :
"false"));
* A class implementing plaintext server sessions.
* An object that knows how to handle association requests with no
* A class implementing DH-SHA1 server sessions.
* An object that knows how to handle association requests with
* the Diffie-Hellman session type.
$this->consumer_pubkey =
$consumer_pubkey;
if ((($dh_modulus ===
null) &&
($dh_gen !==
null)) ||
(($dh_gen ===
null) &&
($dh_modulus !==
null))) {
if ($dh_modulus ===
null) {
'If non-default modulus or generator is '.
'supplied, both must be supplied. Missing '.
$lib =
& Auth_OpenID_getMathLib();
if ($dh_modulus ||
$dh_gen) {
$dh_modulus =
$lib->base64ToLong($dh_modulus);
$dh_gen =
$lib->base64ToLong($dh_gen);
if ($lib->cmp($dh_modulus, 0) ==
0 ||
$lib->cmp($dh_gen, 0) ==
0) {
$message, "Failed to parse dh_mod or dh_gen");
$dh =
new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
$dh =
new Auth_OpenID_DiffieHellman();
if ($consumer_pubkey ===
null) {
'Public key for DH-SHA1 session '.
$lib->base64ToLong($consumer_pubkey);
if ($consumer_pubkey ===
false) {
"dh_consumer_public is not base64");
return array($dh, $consumer_pubkey);
if (is_a($result, 'Auth_OpenID_ServerError')) {
list
($dh, $consumer_pubkey) =
$result;
$lib =
& Auth_OpenID_getMathLib();
$mac_key =
$this->dh->xorSecret($this->consumer_pubkey, $secret,
$lib->longToBase64($this->dh->public),
* A class implementing DH-SHA256 server sessions.
if (is_a($result, 'Auth_OpenID_ServerError')) {
list
($dh, $consumer_pubkey) =
$result;
* A request to associate with the server.
'no-encryption' =>
'Auth_OpenID_PlainTextServerSession',
'DH-SHA1' =>
'Auth_OpenID_DiffieHellmanSHA1ServerSession',
'DH-SHA256' =>
'Auth_OpenID_DiffieHellmanSHA256ServerSession');
$this->session =
& $session;
$this->assoc_type =
$assoc_type;
if ($message->isOpenID1()) {
if ($session_type ==
'no-encryption') {
// oidutil.log('Received OpenID 1 request with a no-encryption '
// 'assocaition session type. Continuing anyway.')
} else if (!$session_type) {
$session_type =
'no-encryption';
if ($session_type ===
null) {
"session_type missing from request");
$session_class =
Auth_OpenID::arrayGet(
if ($session_class ===
null) {
"Unknown session type " .
if (is_a($session, 'Auth_OpenID_ServerError')) {
'assoc_type', 'HMAC-SHA1');
if (!in_array($assoc_type, $session->allowed_assoc_types)) {
$fmt =
"Session type %s does not support association type %s";
sprintf($fmt, $session_type, $assoc_type));
$obj->message =
$message;
$obj->namespace =
$message->getOpenIDNamespace();
'expires_in' =>
sprintf('%d', $assoc->getExpiresIn()),
'assoc_type' =>
$this->assoc_type,
'assoc_handle' =>
$assoc->handle));
$this->session->answer($assoc->secret));
if (! ($this->session->session_type ==
'no-encryption'
&&
$this->message->isOpenID1())) {
$this->session->session_type);
$preferred_association_type=
null,
$preferred_session_type=
null)
if ($this->message->isOpenID1()) {
'error_code', 'unsupported-type');
if ($preferred_association_type) {
$preferred_association_type);
if ($preferred_session_type) {
$preferred_session_type);
* A request to confirm the identity of a user.
* Return-to verification callback. Default is
* Auth_OpenID_verifyReturnTo from TrustRoot.php.
* The mode of this request.
var $mode =
"checkid_setup"; // or "checkid_immediate"
* Whether this request is for immediate mode.
* The trust_root value for this request.
* The OpenID namespace for this request.
* deprecated since version 2.0.2
function make(&$message, $identity, $return_to, $trust_root =
null,
$immediate =
false, $assoc_handle =
null, $server =
null)
"server must not be null");
$r->namespace =
$message->getOpenIDNamespace();
if (!$r->trustRootValid()) {
$trust_root =
null, $immediate =
false,
$assoc_handle =
null, $server =
null,
$this->assoc_handle =
$assoc_handle;
$this->identity =
$identity;
if ($claimed_id ===
null) {
$this->claimed_id =
$identity;
$this->claimed_id =
$claimed_id;
$this->return_to =
$return_to;
$this->server =
& $server;
$this->mode =
"checkid_immediate";
$this->mode =
"checkid_setup";
(is_a($other, 'Auth_OpenID_CheckIDRequest')) &&
($this->assoc_handle ==
$other->assoc_handle) &&
($this->identity ==
$other->identity) &&
($this->claimed_id ==
$other->claimed_id) &&
($this->return_to ==
$other->return_to) &&
* Does the relying party publish the return_to URL for this
* response under the realm? It is up to the provider to set a
* policy for what kinds of realms should be allowed. This
* return_to URL verification reduces vulnerability to data-theft
* attacks based on open proxies, corss-site-scripting, or open
* This check should only be performed after making sure that the
* return_to URL matches the realm.
* @return true if the realm publishes a document with the
* return_to URL listed, false if not or if discovery fails
if ($mode ==
"checkid_immediate") {
$mode =
"checkid_immediate";
if (($message->isOpenID1()) &&
$fmt =
"Missing required field 'return_to' from checkid request";
if ($message->isOpenID1()) {
if ($identity ===
null) {
$s =
"OpenID 1 message did not contain openid.identity";
if ($identity &&
!$claimed_id) {
$s =
"OpenID 2.0 message contained openid.identity but not " .
} else if ($claimed_id &&
!$identity) {
$s =
"OpenID 2.0 message contained openid.claimed_id " .
// There's a case for making self.trust_root be a TrustRoot
// here. But if TrustRoot isn't currently part of the
// "public" API, I'm not sure it's worth doing.
if ($message->isOpenID1()) {
$trust_root_param =
'trust_root';
$trust_root_param =
'realm';
$trust_root =
$return_to;
if (! $message->isOpenID1() &&
($trust_root ===
null)) {
"openid.realm required when openid.return_to absent");
if (is_a($obj, 'Auth_OpenID_ServerError')) {
$obj->claimed_id =
$claimed_id;
// Is the identifier to be selected by the IDP?
// So IDPs don't have to import the constant
if ($this->return_to !==
null) {
* Respond to this request. Return either an
* {@link Auth_OpenID_ServerResponse} or
* {@link Auth_OpenID_ServerError}.
* @param bool $allow Allow this user to claim this identity, and
* allow the consumer to have this information?
* @param string $server_url DEPRECATED. Passing $op_endpoint to
* the {@link Auth_OpenID_Server} constructor makes this optional.
* When an OpenID 1.x immediate mode request does not succeed, it
* gets back a URL where the request may be carried out in a
* not-so-immediate fashion. Pass my URL in here (the fully
* qualified address of this server's endpoint, i.e.
* http://example.com/server), and I will use it as a base for the
* Optional for requests where {@link $immediate} is false or
* @param string $identity The OP-local identifier to answer with.
* Only for use when the relying party requested identifier
* @param string $claimed_id The claimed identifier to answer
* with, for use with identifier selection in the case where the
* claimed identifier and the OP-local identifier differ,
* i.e. when the claimed_id uses delegation.
* If $identity is provided but this is not, $claimed_id will
* default to the value of $identity. When answering requests
* that did not ask for identifier selection, the response
* $claimed_id will default to that of the request.
* This parameter is new in OpenID 2.0.
function answer($allow, $server_url =
null, $identity =
null,
if ((!$this->message->isOpenID1()) &&
(!$this->server->op_endpoint)) {
"server should be constructed with op_endpoint to " .
"respond to OpenID 2.0 messages.");
$server_url =
$this->server->op_endpoint;
} else if ($this->message->isOpenID1()) {
($this->message->isOpenID1())) {
"claimed_id is new in OpenID 2.0 and not " .
if ($identity &&
!$claimed_id) {
"This request uses IdP-driven identifier selection. " .
"You must supply an identifier in the response.");
$response_identity =
$identity;
$response_claimed_id =
$claimed_id;
} else if ($this->identity) {
($this->identity !=
$identity)) {
$fmt =
"Request was for %s, cannot reply with identity %s";
sprintf($fmt, $this->identity, $identity));
$response_identity =
$this->identity;
$response_claimed_id =
$this->claimed_id;
"This request specified no identity and " .
"you supplied ".
$identity);
$response_identity =
null;
if (($this->message->isOpenID1()) &&
($response_identity ===
null)) {
"Request was an OpenID 1 request, so response must " .
"include an identifier.");
'return_to' =>
$this->return_to,
if (!$this->message->isOpenID1()) {
'op_endpoint', $server_url);
if ($response_identity !==
null) {
$response->fields->setArg(
if ($this->message->isOpenID2()) {
$response->fields->setArg(
if (($this->message->isOpenID1()) &&
'setup_url is required for $allow=false \
in OpenID 1.x immediate mode.');
$setup_request->message =
$this->message;
$setup_url =
$setup_request->encodeToURL($server_url);
if ($setup_url ===
null) {
// Imported from the alternate reality where these classes are
// used in both the client and server code, so Requests are
// Encodable too. That's right, code imported from alternate
// realities all for the love of you, id_res/user_setup_url.
$q =
array('mode' =>
$this->mode,
'identity' =>
$this->identity,
'claimed_id' =>
$this->claimed_id,
'return_to' =>
$this->return_to);
if ($this->message->isOpenID1()) {
if ($this->assoc_handle) {
$q['assoc_handle'] =
$this->assoc_handle;
$this->message->getOpenIDNamespace());
return $response->toURL($server_url);
"Cancel is not an appropriate \
response to immediate mode \
$this->message->getOpenIDNamespace());
return $response->toURL($this->return_to);
* This class encapsulates the response to an OpenID server request.
$this->request =
& $request;
global $_Auth_OpenID_Request_Modes;
if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
if ($this->fields->isOpenID2() &&
return Auth_OpenID_ENCODE_HTML_FORM;
return Auth_OpenID_ENCODE_URL;
return Auth_OpenID_ENCODE_KVFORM;
* Returns the form markup for this response.
return $this->fields->toFormMarkup($this->request->return_to,
* Returns an HTML document containing the form markup for this
* response that autosubmits with javascript.
* Returns True if this response's encoding is ENCODE_HTML_FORM.
* Convenience method for server authors.
return $this->fields->toURL($this->request->return_to);
$extension_response->toMessage($this->fields);
return $this->fields->toKVForm();
* A web-capable response object which you can use to generate a
var $code =
AUTH_OPENID_HTTP_OK;
$this->headers =
$headers;
$this->headers =
array();
* Responsible for the signature of query data and the verification of
* OpenID signature values.
// = 14 * 24 * 60 * 60; # 14 days, in seconds
// keys have a bogus server URL in them because the filestore
// really does expect that key to be a URL. This seems a little
// silly for the server store, since I expect there to be only one
* Create a new signatory using a given store.
// assert store is not None
* Verify, using a given association handle, a signature with
* signed key-value pairs from an HTTP request.
function verify($assoc_handle, $message)
// oidutil.log("failed to get assoc with handle %r to verify sig %r"
// % (assoc_handle, sig))
return $assoc->checkMessageSignature($message);
* Given a response, sign the fields in the response's 'signed'
* list, and insert the signature into the response.
$signed_response =
$response;
$assoc_handle =
$response->request->assoc_handle;
if (!$assoc ||
($assoc->getExpiresIn() <=
0)) {
// fall back to dumb mode
'invalidate_handle', $assoc_handle);
$assoc_type =
($assoc ?
$assoc->assoc_type :
'HMAC-SHA1');
if ($assoc &&
($assoc->getExpiresIn() <=
0)) {
$signed_response->fields =
$assoc->signMessage(
$signed_response->fields);
* Make a new association.
$this->store->storeAssociation($key, $assoc);
* Given an association handle, get the association from the
* store, or return a ServerError or null if something goes wrong.
if ($assoc_handle ===
null) {
"assoc_handle must not be null");
$assoc =
$this->store->getAssociation($key, $assoc_handle);
if (($assoc !==
null) &&
($assoc->getExpiresIn() <=
0)) {
$this->store->removeAssociation($key, $assoc_handle);
* Invalidate a given association handle.
$this->store->removeAssociation($key, $assoc_handle);
* Encode an {@link Auth_OpenID_ServerResponse} to an
* {@link Auth_OpenID_WebResponse}.
* Encode an {@link Auth_OpenID_ServerResponse} and return an
* {@link Auth_OpenID_WebResponse}.
$encode_as =
$response->whichEncoding();
if ($encode_as ==
Auth_OpenID_ENCODE_KVFORM) {
$wr =
new $cls(null, null, $response->encodeToKVForm());
if (is_a($response, 'Auth_OpenID_ServerError')) {
} else if ($encode_as ==
Auth_OpenID_ENCODE_URL) {
$location =
$response->encodeToURL();
array('location' =>
$location));
} else if ($encode_as ==
Auth_OpenID_ENCODE_HTML_FORM) {
$response->toFormMarkup());
* An encoder which also takes care of signing fields when required.
$this->signatory =
& $signatory;
* Sign an {@link Auth_OpenID_ServerResponse} and return an
* {@link Auth_OpenID_WebResponse}.
// the isinstance is a bit of a kludge... it means there isn't
// really an adapter to make the interfaces quite match.
if (!is_a($response, 'Auth_OpenID_ServerError') &&
$response->needsSigning()) {
"Must have a store to sign request");
$response =
$this->signatory->sign($response);
return parent::encode($response);
* Decode an incoming query into an Auth_OpenID_Request.
$this->server =
& $server;
'checkid_setup' =>
'Auth_OpenID_CheckIDRequest',
'checkid_immediate' =>
'Auth_OpenID_CheckIDRequest',
'check_authentication' =>
'Auth_OpenID_CheckAuthRequest',
'associate' =>
'Auth_OpenID_AssociateRequest'
* Given an HTTP query in an array (key-value pairs), decode it
* into an Auth_OpenID_Request object.
* It's useful to have a Message attached to a
* ProtocolError, so we override the bad ns value to build
* a Message out of it. Kinda kludgy, since it's made of
* lies, but the parts that aren't lies are more useful
$old_ns =
$query['openid.ns'];
sprintf("Invalid OpenID namespace URI: %s", $old_ns));
"No mode value in message");
if (Auth_OpenID::isFailure($mode)) {
$handlerCls =
Auth_OpenID::arrayGet($this->handlers, $mode,
if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
array($message, $this->server));
if (Auth_OpenID::isFailure($mode)) {
sprintf("Unrecognized OpenID mode %s", $mode));
* An error that indicates an encoding problem occurred.
$this->response =
& $response;
* An error that indicates that a response was already signed.
// This response is already signed.
* An error that indicates that the given return_to is not under the
parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL");
$this->return_to =
$return_to;
$this->trust_root =
$trust_root;
return sprintf("return_to %s not under trust_root %s",
$this->return_to, $this->trust_root);
* I handle requests for an OpenID server.
* Some types of requests (those which are not checkid requests) may
* be handed to my {@link handleRequest} method, and I will take care
* of it and return a response.
* For your convenience, I also provide an interface to {@link }
* Auth_OpenID_Decoder::decode()} and {@link }
* Auth_OpenID_SigningEncoder::encode()} through my methods {@link }
* decodeRequest} and {@link encodeResponse}.
* All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}.
* <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path),
* "http://example.com/op");
* $request = $oserver->decodeRequest();
* if (in_array($request->mode, array('checkid_immediate',
* if ($app->isAuthorized($request->identity, $request->trust_root)) {
* $response = $request->answer(true);
* } else if ($request->immediate) {
* $response = $request->answer(false);
* $app->showDecidePage($request);
* $response = $oserver->handleRequest($request);
* $webresponse = $oserver->encode($response);</pre>
$this->op_endpoint =
$op_endpoint;
* Handle a request. Given an {@link Auth_OpenID_Request} object,
* call the appropriate {@link Auth_OpenID_Server} method to
* process the request and generate a response.
* @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request}
* returned by {@link Auth_OpenID_Server::decodeRequest()}.
* @return Auth_OpenID_ServerResponse $response A response object
* capable of generating a user-agent reply.
$handler =
array($this, "openid_" .
$request->mode);
* The callback for 'check_authentication' messages.
return $request->answer($this->signatory);
* The callback for 'associate' messages.
$assoc_type =
$request->assoc_type;
$session_type =
$request->session->session_type;
if ($this->negotiator->isAllowed($assoc_type, $session_type)) {
$assoc =
$this->signatory->createAssociation(false,
return $request->answer($assoc);
$message =
sprintf('Association type %s is not supported with '.
'session type %s', $assoc_type, $session_type);
list
($preferred_assoc_type, $preferred_session_type) =
$this->negotiator->getAllowedType();
return $request->answerUnsupported($message,
$preferred_session_type);
* Encodes as response in the appropriate format suitable for
* sending to the user agent.
return $this->encoder->encode($response);
* Decodes a query args array into the appropriate
* {@link Auth_OpenID_Request} object.
$query =
Auth_OpenID::getQuery();
return $this->decoder->decode($query);