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:
Static Credentials
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).
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).
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:
{
"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
:
"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
: Theauthrole
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 clientsalt
: If the secret is salted (i.e. is not stored in cleartext), the salt used for computing the derived secret provided insecret
.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:
{
"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:
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 joinauthid
: 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
: Theauthrole
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 clientsalt
: Ifsecret
was salted, the salt used (with pbkdf2)iterations
: Ifsecret
was salted, the iterations during salting (a parameter of the pbkdf2 algorithm used).keylen
: Ifsecret
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):
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¶
For more on dynamic authenticators read this documentation page
Configuration¶
{
"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`` |
|
``authenticator`` |
URI of custom authenticator to call. |