:orphan: Challenge-Response Authentication ================================= Introduction ------------ Crossbar.io supports authenticating WAMP sessions using different mechanisms. One of those is WAMP-Challenge-Response-Authentication (`see the WAMP spec `__), or WAMP-CRA in short. WAMP-CRA is a `challenge-response authentication `__ mechanism using a secret shared between the client and server side: - The secret never travels the wire, hence WAMP-CRA can be used via non-TLS connections. Also, WAMP-CRA is protected from replay attacks. - WAMP-CRA also supports the use of `salted passwords `__, using the `PBKDF2 `__ key derivation algorithm. - The actual pre-sharing of the secret is outside the scope of the WAMP-CRA authentication mechanism. Further, Crossbar.io allows two ways of providing the credentials needed for WAMP-CRA authentication: 1. Static Credentials 2. Dynamic Credentials With *static credentials*, you configure users and secrets in the Crossbar.io node configuration. This comes in handy in many simple scenarios where nothing more than a bunch of static credentials are needed. With *dynamic credentials*, you specify the URI of a regular WAMP procedure in the Crossbar.io node configuration. The procedure will then be called to actually retrieve the secret for a given user at authentication time. You can use this to hook into your application's user database. Authenticating Components ------------------------- JavaScript ~~~~~~~~~~ Here is a WAMP component written in JavaScript/AutobahnJS that authenticates via WAMP-CRA (unsalted passwords). .. code:: javascript var user = "joe"; var key = "secret2"; // this callback is fired during WAMP-CRA authentication // function onchallenge (session, method, extra) { if (method === "wampcra") { return autobahn.auth_cra.sign(key, extra.challenge); } } var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', realm: 'realm1', // the following attributes must be set of WAMP-CRA authentication // authmethods: ["wampcra"], authid: user, onchallenge: onchallenge }); connection.onopen = function (session, details) { ... details contains authid, authrole, ... }; connection.open(); Python Frontend ~~~~~~~~~~~~~~~ Here is a WAMP component written in Python/AutobahnPython that authenticates via WAMP-CRA (unsalted passwords). .. code:: python class MyFrontendComponent(wamp.ApplicationSession): def onConnect(self): self.join(u"realm1", [u"wampcra"], u"joe") def onChallenge(self, challenge): if challenge.method == u"wampcra": signature = auth.compute_wcs(u"secret2".encode('utf8'), challenge.extra['challenge'].encode('utf8')) return signature.decode('ascii') else: raise Exception("don't know how to handle authmethod {}".format(challenge.method)) def onJoin(self, details): print("ok, session joined!") Static Credentials ------------------ With *static credentials*, you configure users and secrets in the Crossbar.io node configuration. You can find a complete example `here `__. E.g. here is part of a Crossbar.io node configuration: .. code:: javascript { "workers": [ { "type": "router", ... "transports": [ { "type": "web", ... "paths": { ... "ws": { "type": "websocket", "auth": { "wampcra": { "type": "static", "users": { "joe": { "secret": "secret2", "role": "frontend" }, "peter": { "secret": "prq7+YkJ1/KlW1X0YczMHw==", "role": "frontend", "salt": "salt123", "iterations": 100, "keylen": 16 } } } } } } } ] } ... ] } This node runs a Web transport. Part of the Web transport is a WebSocket path service running on path ``ws``. We configure WAMP-CRA on this transport by adding an ``auth`` attribute, which must be a dictionary with one key per authentication method. The ``auth.wampcra`` again needs to be a dictionary with one mandatory ``type`` attribute which can be - ``static`` - ``dynamic`` When ``wamp.wampcra.type == 'static'``, then the user credentials against which new incoming WAMP connection will get authenticated is provided within the configuration in a ``users`` dictionary, indexed by ``authid``: .. code:: json "users": { "joe": { "secret": "secret2", "role": "frontend" }, "peter": { "secret": "prq7+YkJ1/KlW1X0YczMHw==", "role": "frontend", "salt": "salt123", "iterations": 100, "keylen": 16 } } Here we define two users: ``joe`` and ``peter``. The mandatory attributes are: - ``secret``: The secret shared with the client. - ``role``: The ``authrole`` a successfully authenticated client with be assigned. Optional attributes are all related to the (optional) pbkdf2-based password salting: - ``authid``: The authentication ID which will be assigned to the client - ``salt``: If the secret is salted (i.e. is not stored in cleartext), the salt used for computing the derived secret provided in ``secret``. - ``iterations``: An integer parameter of the pbkdf2 algorithm. - ``keylen``: An integer parameter of the pbkdf2 algorithm. Dynamic Credentials ------------------- With *dynamic credentials*, you specify the URI of a regular WAMP procedure in the Crossbar.io node configuration. The procedure will then be called by Crossbar.io during authentication of (other) users. You can find complete examples for different languages `here `__. Here is part of a Crossbar.io node configuration: .. code:: javascript { "workers": [ { "type": "router", ... "transports": [ { "type": "web", ... "paths": { ... "ws": { "type": "websocket", "auth": { "wampcra": { "type": "dynamic", "authenticator": "com.example.authenticate" } } } } } ] } ... ] } Instead of a static list of user credentials, we now simply provide the URI ``com.example.authenticate`` of the procedure we want to be called to retrieve the credentials in attribute ``auth.wampcra.authenticator``. The procedure will be called with two arguments, the ``realm`` and the ``authid`` of the WAMP session that wants to authenticate via WAMP-CRA: .. code:: python def authenticate(realm, authid, details): ## return credentials (secret + role) for user 'authid' return {'secret': 'mypassword', 'role': 'sales'} The arguments are: - ``realm``: The realm the client wishes to join - ``authid``: The authentication ID the client announced (e.g. username). - ``details``: Additional information on the WAMP client that wishes to authenticate (such as transport level data, e.g. IP address or HTTP headers) The return value must be a dictionary with two mandatory attributes: - ``secret``: The secret shared with the client (possibly after salting) - ``role``: The ``authrole`` to assign to the client *if* successfully authenticated The dictionary can have these optional attributes: - ``authid``: The authentication ID which will be assigned to the client - ``salt``: If ``secret`` was salted, the salt used (with pbkdf2) - ``iterations``: If ``secret`` was salted, the iterations during salting (a parameter of the pbkdf2 algorithm used). - ``keylen``: If ``secret`` was salted, the keylen of the derived key (a parameter of the pbkdf2 algorithm used). To deny a user, just raise an exception in the procedure. Here is a complete custom authenticator (this is implemented in Python, but you can write custom authenticators in any WAMP support language): .. code:: python from twisted.internet.defer import inlineCallbacks from autobahn.twisted.wamp import ApplicationSession from autobahn.wamp.exception import ApplicationError class MyAuthenticator(ApplicationSession): USERDB = { 'joe': { 'secret': 'secret2', 'role': 'frontend' }, 'peter': { # autobahn.wamp.auth.derive_key(secret.encode('utf8'), salt.encode('utf8')).decode('ascii') 'secret': 'prq7+YkJ1/KlW1X0YczMHw==', 'role': 'frontend', 'salt': 'salt123', 'iterations': 100, 'keylen': 16 } } @inlineCallbacks def onJoin(self, details): def authenticate(realm, authid, details): print("authenticate called: realm = '{}', authid = '{}', details = '{}'".format(realm, authid, details)) if authid in self.USERDB: return self.USERDB[authid] else: raise ApplicationError("com.example.no_such_user", "could not authenticate session - no such user {}".format(authid)) try: yield self.register(authenticate, 'com.example.authenticate') print("custom WAMP-CRA authenticator registered") except Exception as e: print("could not register custom WAMP-CRA authenticator: {0}".format(e)) Examples -------- - `Static Challenge-Response Authentication `__ - `Dynamic/Custom Challenge-Response Authentication `__ For more on dynamic authenticators read :doc:`this documentation page ` Configuration ------------- .. code:: json { "auth": { "wampcra": { "type": "static", "users": { "foobar83": { "secret": "Xy$h2l-D", "role": "user" } } } } } Static ~~~~~~ +-----------+----------------------------------------------------------------------+ | parameter | description | +===========+======================================================================+ | **type** | "static" | +-----------+----------------------------------------------------------------------+ | **users** | A dictionary of names mapping to values being dictionaries as below. | +-----------+----------------------------------------------------------------------+ Each user has this associated dictionary: +--------------+---------------------------------------------------------------------------+ | attribute | description | +==============+===========================================================================+ | secret | Arbitrary text value used as shared secret (required). | +--------------+---------------------------------------------------------------------------+ | role | Optional authrole a client using this ticket will be authenticated under. | +--------------+---------------------------------------------------------------------------+ | salt | | +--------------+---------------------------------------------------------------------------+ | iterations | | +--------------+---------------------------------------------------------------------------+ | keylen | | +--------------+---------------------------------------------------------------------------+ Dynamic ~~~~~~~ +-------------------------+----------------------------------------+ | parameter | description | +=========================+========================================+ | **``type``** | ``"dynamic"`` | +-------------------------+----------------------------------------+ | **``authenticator``** | URI of custom authenticator to call. | +-------------------------+----------------------------------------+