REST Bridge + JWT (x/post from Github)

Crossbar is fantastic - thank you for building it!

I’ve been battling through for a good week now hacking around to make it work in the way i need it to, after a succession of problems experimenting with Ratchet (only supports WAMP v1) and Thruway (only supports Symfony 4).

I have a backend written in Laravel 7 (PHP7), with JS clients using Autobahn to connect over SSL (wss://) and automatically negotiate the WAMP v2 protocol (Cloudflare > Nginx proxy on port 443 > Crossbar server on 127.0.0.1:7000). User auth is stored in Mongo. I’ve written a custom broadcast driver which pushes topic updates from PHP via the HTTP bridge (POSTing as a publisher). The clients successfully receive the data the backend publishes to them, which is fantastic.

For 90% of the app, we can use REST. But for the “real-time” part (e.g sending commands to devices), we need to be able to somewhat duplicate the same functionality over the socket to get the response immediately.

I have 2 things which i need to complete the project which i’m bashing my head against the wall on. I can’t use Thruway for it because the package isn’t compatible with Laravel 7.

  1. Dynamic authentication: I’d like to be able to send a JWT token (week-long TTL) as ticket auth and for it to be verified by the backend when connecting. The clients themselves could be within the integrated app (HTML etc), or outside. They already have JWT auth elsewhere, so i’d like to re-use it once the client has it. Assuming they have a valid JWT token, what’s the best practice for sending to the Laravel backend to check its validity? CRA? Ticket?

  2. Proxying RPC calls: i have 30+ named remote procedures in the Laravel backend which socket clients need to be able to call - NOT as REST (which they can do already via the API), but WAMP --> Crossbar --> HTTP REST/JSON on the remote server as an authenticated caller. The HTTP “callee” part seems to fit the part, but the examples aren’t clear on how to define a POST endpoint (webhook) where you can specify different procedures. Also - a) what should the remote host return as its JSON payload for the JS client to consider it success or failure? b) i can’t leave the RPC endpoint open if the caller is remote, so can Crossbar use basic auth when making its HTTP call, or add a pass-through of the JWT token in the Authorization header?

Thanks in advance!

thanks=) you’re welcome!

rgd your questions:

  1. Dynamic authentication with JWT token the client already has

I’d use WAMP-Ticket for that, send the JWT token as the WAMP-Ticket secret, and verify it in a custom dynamic authenticator

above is an authenticator written in python (and hence able to run embedded as a router component). but that’s just one way. you can of course write your authenticator as a plain old WAMP component in PHP which simply connects to the router (it will be called then during the authentication handshake of connecting clients)

  1. Proxying RPC calls

I agree the docs kinda suck. here are some useful pointers

lets say you have configured

            "components": [
                {
                    "type": "class",
                    "classname": "crossbar.bridge.rest.RESTCallee",
                    "realm": "realm1",
                    "extra": {
                        "procedure": "com.myapp.rest",
                        "baseurl": "https://httpbin.org/"
                    },
                    "transport": {
                        "type": "websocket",
                        "endpoint": {
                            "type": "tcp",
                            "host": "127.0.0.1",
                            "port": 8080
                        },
                        "url": "ws://127.0.0.1:8080/ws"
                    }
                }
            ]

you then can WAMP-call procedure com.myapp.rest, and when you provide a url parameter when doing the call, that will be appended to baseurl to actually reach one of your 30+ backend HTTP endpoints.

unfort., there is currently no convenient way of exposing your 30+ HTTP endpoints as 30+ individually registered WAMP procedures.

we could add sth like this quite easily … the https://github.com/crossbario/crossbar/blob/7a24af99fce6d057c63a9c7b144b0418ca627f4e/crossbar/bridge/rest/callee.py has all the main code there is to the HTTP callee bridge.

it would just be a matter of reading more config, and registering not 1 but many WAMP procs in callee.py

Really appreciate the help on this too - i’m planning to release a Symfony/Laravel package for this to aide integration into existing REST projects (Laravel uses Pusher for its one-way “broadcasting” component but has no inverse upstream messaging capability, and the community is huge). The JWT part is absolutely essential: we typically use this package with Axios: https://github.com/tymondesigns/jwt-auth. Nearly there! The only part left is the JWT (and/or generic access token, TOTP etc) piece.

The RPC thing i managed to piece together from a post on the mailing list about a web printer, which was fun to read. Attaching the JWT token there to do authorization is simple as it can be added to the headers dictionary of the main request to the callee.

I can see and agree with the approach you’re suggesting, and where i’m at is the dynamic authenticator part: i started with that Python code you mentioned as well as the PHP (which i believe exec’s it in the console?).

I need to get that JWT ticket over to a REST endpoint (not a .php file), which can authenticate a user.

Is it possible to use the REST bridge to do dynamic authentication? And if so, what response would the endpoint need to send back to Crossbar to indicate authentication has been successful? The actual procedure could be as simple as including the token in a header and receiving a blank HTTP 204, 401, 403 etc. It could also be a GET querystring or POST field.

If not, is the best approach going to be extending the Python authenticator to talk to the REST endpoint (i.e. proxying) as an alternative?

1 Like

and where i’m at is the dynamic authenticator part: i started with that Python code you mentioned as well as the PHP (which i believe exec’s it in the console?).

when you write your dynamic authenticator in Python+Twisted, you can run in embedded in a router worker (be careful then, as your code runs in the same process as the router), a container worker (recommended, because safer) or in a guest worker (eg using a different python than crossbar itself, or using asyncio instead of Twisted)

when you write your dynamic authenticator in PHP, you can start and run that PHP at command line yourself (eg using systemd), on the same box as crossbar or a different box, OR you can have crossbar start this as a guest worker (on the same box as crossbar).

Is it possible to use the REST bridge to do dynamic authentication?

if you mean having a REST endpoint being called directly instead of a WAMP endpoint, that wouldn’t work currently, as the REST bridge callee return value doesn’t match what a dynamic authenticator WAMP procedure expects. also, I never tried that combination, and it sounds brittle and overly complex.

I would simply do a HTTP request from your dynamic authenticator, but have that authenticator be a regular WAMP component.

doing a HTTP request in PHP (if you write your authenticator in PHP): I guess you will know how to do that … I forgot anything PHP;)

doing an asynchronous (!) HTTP request in python/twisted is trivial:

import treq

response = await treq.get(url)
data = await response.content()

the important point is: you want to use an asynchronous web library, not a synchronous (blocking) one

eg https://treq.readthedocs.io/, not https://requests.readthedocs.io/ when on python/twisted

if you want to use python/asyncio, you can use https://docs.aiohttp.org/

actually, we have a complete example: https://github.com/crossbario/crossbar-examples/tree/master/authentication/wampcra/dynamic/php

(this is doing WAMP-CRA, not WAMP-Ticket - but easy to adjust)

the interesting part is probably the guest worker configuration for PHP: https://github.com/crossbario/crossbar-examples/blob/92d0794ba8402c902edc98baedccef1b8ca0bea9/authentication/wampcra/dynamic/php/.crossbar/config.json#L186

That’s what i needed to know (whether or not it was possible to do with REST), thank you! And yep i saw the PHP example.

My initial thoughts are there are 2 routes for this: a) if on the command line, using Symfony Console (https://symfony.com/doc/current/components/console.html, on which Laravel’s Artisan command is based), or b) a simple HTTP request (as you mentioned).

Either way is going to require a custom component in the package; however, that’s useful in itself as i’m not chasing something in the REST bridge which isn’t there. The small mercy is it is only needed at connection time, rather than on every request (the token can be tested again on individual RPC calls).

The former might be a command line like this, with the file “artisan” (no extension) and the token as an argument - if necessary it could connect to a remote API or database:

/usr/bin/php /srv/domain.com/artisan token:authenticate 12345abcde012345

Authorization is a bit more nuanced, as it typically generates a 403 response: whereas the Callee component returns 200.

2 additional sidebar questions:

  1. Is it possible to have a time-limited connection (i.e. access online for 60mins, whereafter Crossbar drops the connection)?

  2. Is there an “entry point” to query what traffic is going through the router, which you could build a GUI around?

This exact feature is not built-in “as is”, but you can do that via a WAMP component in a couple of lines, using the WAMP meta API (which is part of Crossbar.io OSS).

You can subscribe to the meta API to get hold of new sessions

https://wamp-proto.org/_static/gen/wamp_latest.html#session-meta-events

and then after 60min, pro-actively kill a session

https://wamp-proto.org/_static/gen/wamp_latest.html#session-meta-procedures

Crossbar.io FX (extended commercial version of Crossbar.io OSS) includes a complete management API, which is itself WAMP based, and allows to do everything you can do from a node configuration file, but dynamically, at run-time, without restarting a node, and also across many nodes.

This management API also includes stuff you cannot do via node configuration file, including traffic statistics, and accessing those.