text: import copy
  import random
  from pprint import pformat
  from txaio import make_logger
  from autobahn.wamp.exception import ApplicationError
  from autobahn.util import hl, hltype, hlid, hlval
  
  # a simple principals database. in real world use, this likey would be
  # replaced by some persistent database used to store principals.
  PRINCIPALS = [
      {
          # when a session is authenticating use one of the authorized_keys,
          # then assign it all the data below
          "authid": "client01@example.com",
          "realm": "devices",
          "role": "device",
          "extra": {
              "foo": 23
          },
          "authorized_keys": [
              "545efb0a2192db8d43f118e9bf9aee081466e1ef36c708b96ee6f62dddad9122"
          ]
      },
      {
          "authid": "client02@example.com",
          "realm": "devices",
          "role": "device",
          "extra": {
              "foo": 42,
              "bar": "baz"
          },
          "authorized_keys": [
              "9c194391af3bf566fc11a619e8df200ba02efb35b91bdd98b424f20f4163875e",
              "585df51991780ee8dce4766324058a04ecae429dffd786ee80839c9467468c28"
          ]
      }
  ]
  
  
  log = make_logger()
  
  
  async def create_authenticator(config, controller):
      """
      Creates and returns a function to do authentication. The actual
      authentication method will be called like:
  
          authenticate(realm, authid, session_details)
  
      Note that this function can itself do async work (as can the
      "authenticate" method). For example, we could connect to a
      database here (and then use that connection in the authenticate()
      method)
  
      'controller' will be None unless `"expose_controller": true` is in
      the config.
      """
      log.info(
          'create_authenticator(config={config}) {func}',
          config=pformat(config),
          func=hltype(create_authenticator),
      )
  
      pubkey_to_principals = {}
      for p in PRINCIPALS:
          for k in p['authorized_keys']:
              if k in pubkey_to_principals:
                  raise Exception("ambiguous key {}".format(k))
              else:
                  pubkey_to_principals[k] = p
  
      async def authenticate(realm, authid, details):
          """
          this is our dynamic authenticator procedure that will be called by Crossbar.io
          when a session is authenticating
          """
          log.info(
              'authenticate(realm="{realm}", authid="{authid}", details={details}) {func}',
              realm=hl(realm),
              authid=hl(authid),
              details=details,
              func=hltype(create_authenticator),
          )
  
          assert('authmethod' in details)
          assert(details['authmethod'] == 'cryptosign')
          assert('authextra' in details)
          assert('pubkey' in details['authextra'])
  
          pubkey = details['authextra']['pubkey']
          log.info(
              'authenticating session using realm="{realm}", pubkey={pubkey} .. {func}',
              realm=hl(realm),
              pubkey=hl(pubkey),
              func=hltype(create_authenticator),
          )
  
          if pubkey in pubkey_to_principals:
              principal = pubkey_to_principals[pubkey]
              auth = {
                  'pubkey': pubkey,
                  'realm': principal['realm'],
                  'authid': principal['authid'],
                  'role': principal['role'],
                  'extra': principal['extra'],
                  'cache': True
              }
  
              # Note: with WAMP-cryptosign, even though a client may or may not request a `realm`, but in any case, the
              # effective realm the client is authenticated will be returned in the principal `auth['role']` (!)
              effective_realm = auth['realm']
  
              log.info(
                  'found valid principal authid="{authid}", authrole="{authrole}", realm="{realm}" matching given client public key {func}',
                  func=hltype(create_authenticator),
                  authid=hl(auth['authid']),
                  authrole=hl(auth['role']),
                  realm=hl(effective_realm),
              )
  
              return auth
          else:
              msg = 'no principal with matching public key 0x{}'.format(pubkey)
              log.warn(msg)
              raise ApplicationError('com.example.no_such_user', msg)
  
      return authenticate
  