Background association requests
###############################

This document describes how to make signing in with OpenID faster for
users of your application by never making the users wait for an
association to be made, but using associations when they're
available. Most OpenID libraries and applications attempt to make
associations during the discovery phase of the OpenID authentication
request. Because association requests may have to do Diffie-Hellman
key exchange, which is time consuming. Even if Diffie-Hellman key
exchange is not used, the user still needs to wait for the association
request.

Setting up your application to make associations in the background
==================================================================

When making associations background, there are two components that
need access to the OpenID association store: the consumer application
and the background association fetcher. The consumer needs to be set
up to record the server URL for any request for which an association
does not exist or is expired instead of making a new association. The
background fetcher looks at the server URL queue and makes
associations for any server URLs that need them. After the
associations are made, the consumer will use them until they expire
again. While associations are expired or missing, the consumer will
use stateless mode to complete authentications with the servers that
need associations.

The OpenID server endpoint URL queue
-----------------------------------------------------------------

You will have to set up a conduit between the consumer and the
background association fetcher so that the background association
fetcher knows what servers need associations. The background
association fetcher will not fetch associations for servers that
already have them, so the queue does not have to be very smart. It
could be as simple as a file to which the server URLs are
appended. Either way, the queue needs to be write-able by the consumer
and readable by the background fetcher.

Configuring the consumer
-----------------------------------------------------------------

Create a subclass of ``GenericConsumer`` that overrides
``_negotiateAssociation`` so that it just records the server URL that
needs an association::

  from openid.consumer.consumer import GenericConsumer, Consumer

  class LazyAssociationConsumer(GenericConsumer):
      needs_assoc_file = None

      def _negotiateAssociation(self, endpoint):
          # Do whatever you need to do here to send the server_url to
          # the queue. This example just appends it to a file.
          self.needs_assoc_file.write(endpoint.server_url + '\n')
          self.needs_assoc_file.flush()

You could also store the whole endpoint object. When you instantiate
the consumer, pass this generic consumer class to the controlling
consumer::

  return Consumer(session, store, consumer_class=LazyAssociationConsumer)

The background association fetcher
-----------------------------------------------------------------

The background association fetcher is just a script that should be
added to ``cron`` or triggered periodically. If you are ambitious, you
could make the background fetcher listen for inserts into the queue.

The background fetcher needs to do something similar to the following::

  def refresh(consumer, endpoint):
      if consumer.store.getAssociation(endpoint.server_url):
          logging.info("We don't need to associate with %r", endpoint.server_url)
          return

      logging.info("Associating with %r", endpoint.server_url)
      now = time.time()
      assoc = consumer._negotiateAssociation(endpoint)
      if assoc:
          elapsed = time.time() - now
          logging.info('(%0.2f seconds) Associated with %r', elapsed,
                       endpoint.server_url)
          consumer.store.storeAssociation(endpoint.server_url, assoc)
      else:
          logging.error('Failed to make an association with %r',
                        endpoint.server_url)

The code in this example logs the amount of time that the association
request took. This is time that the user would have been waiting. The
``consumer`` in this example is a standard consumer, not the
``LazyAssociationConsumer`` that was defined in the section
above. This is important, because the lazy consumer above will not
actually make any associations.
