Using JSON Web Token (JWT) for Thruway Authentication

JSON Web Tokens (JWT) are a great way to easily add authentication to your Thruway router. It allows you to generate authentication credentials from any supported environment. It is great for integrating your new Thruway router into your existing traditional web application.

In this example, I am going to create a module that lives inside the router process. It will use a shared key (shared by the web application and the router) to verify people’s authid. This based on the ticket-based authentication from the Web Application Messaging Protocol (WAMP) Advanced Profile.

Generating the JWT from your PHP application

We will use the jwt library at https://github.com/firebase/php-jwt to generate the JWT token. I assume you are using composer for your project. Run:

composer require firebase/php-jwt

JWT does have standards for use as far as field values for issuer and expiration, etc. (if you use the exp, the library will verify that the token is not expired). This tutorial is not going to address that. We just care about getting token, making sure it is a valid token, and then making sure the authid is correct. This way we will be able to follow along in future tutorials when we are trying to do authorization and stuff.

Very simply:

<?php

$authid = "joe"; 
$key = "example_key"; // CHANGE THIS

$token = array(
     "authid" => $authid
);

$jwt = \Firebase\JWT\JWT::encode($token, $key);

“example_key” is your shared key. You should pick a better key in your project. You would probably set $authid using some information stored in your regular web site session.

$jwt will contain a signed token with the authid that can be verified (using the shared key). Please note that this token is not encrypted, so don’t put anything particularly sensitive in the $token array.

Router Authentication

For the Thruway router, we will be creating a custom AuthenticationProvider module.

Go through the quick start here: https://github.com/voryx/Thruway#quick-start-with-composer and then come back.

You should have a router running now. You can stop it with Control-C.

Add the JWT libraries to this project so that we can use them to verify the JWTs from the clients:

composer require firebase/php-jwt

Copy the example router (SimpleWsServer.php) to a new file called RouterWithJwtAuth.php. Replace the require at the top with the regular autoloader.

Create a new file named JwtAuthenticationProvider.php:

<?php

class JwtAuthenticationProvider extends Thruway\Authentication\AbstractAuthProviderClient {
    private $jwtKey;

    public function __construct(Array $authRealms, $jwtKey) {
        $this->jwtKey = $jwtKey;
        parent::__construct($authRealms);
    }

    public function getMethodName() {
        return 'jwt';
    }

    public function processAuthenticate($signature, $extra = null)
    {
        $jwt = \Firebase\JWT\JWT::decode($signature, $this->jwtKey, ['HS256']);

        if (isset($jwt->authid)) {
            return ["SUCCESS", ["authid" => $jwt->authid]];
        } else {
            return ["FAILURE"];
        }
    }
}

Now we need to add the JWT auth provider to the router. To do this we need to add the AuthenticationManager as the router runs without one by default. Then we can add our client. Because this client makes no I/O calls and isn’t generally doing a whole lot to block, it is easy enough to run it as an internal client.

Add these lines to the router script right after $router is set:

$router->registerModule(new \Thruway\Authentication\AuthenticationManager());

$router->addInternalClient(new JwtAuthenticationProvider(['realm1'], 'example_key')); // CHANGE YOUR KEY

You can now start up your router, which will be authenticating with JWT on realm1.

Give it a test a this Plunker.

8 responses to “Using JSON Web Token (JWT) for Thruway Authentication

  1. Hey Matt,

    nice article, the code works fine.
    Do you have a short code example for Cookie Based Authentification (found here: https://github.com/tavendo/WAMP/blob/master/spec/advanced.md#http-cookie-based-authentication).

    In the ‘RatchetTransportProvider.php’ file in the method ‘ onOpen(ConnectionInterface $conn)’ the ConnectionInterface has a ‘WebSocket’ property, which has a request property, where i can get the Cookie with this code:

    $conn->WebSocket->request->getCookie(ini_get(‘session.name’))

    So where is the best place to check authentication? With this cookie i can get the PHPSESSION and check if the user is logged in or i could get some other values in the session, for example the username and add this username to a message the client sends with publish

    1. Hi Eugen,

      Cookie-based authentication is not currently implemented in Thruway.

      I don’t think it would take much to get it going though.

      The way I envision it working is by adding transport details to the Hello message would would then get passed on to the AuthenticationManager and then onto the AuthenticationProvider, which would be able to use the details to authenticate the user.

      Adding the cookie information to RatchetTransport:


      public function getTransportDetails()
      {
      $transportAddress = null;
      $transportAddress = $this->conn->remoteAddress;
      $transportCookies = $this->conn->WebSocket->request->getCookies();

      return [
      "type" => "ratchet",
      "transportAddress" => $transportAddress,
      "cookies" => $transportCookies
      ];
      }

      And then in Realm.php on line 206:


      $msg->getDetails()->transport = $session->getTransport()->getTransportDetails();

      This would send all the details into the AuthenticationManager an in turn into the Provider.

      If you extend AbstractAuthenticationProvider, override the processHello method and do your session checking in there. If you want to let them in, just return [“NOCHALLENGE”, [“authid” => $theAuthId ]].

      Let me know if this helps. I may try to create a tutorial out of this later and maybe get these pieces put into the repo.

  2. Hey Matt,

    thx for the fast reply and thx for the piece of code. It works fine, i can access the cookie transport details in the ‘processHello’ method in the $args.

    I just started to work with thruway 2 days ago in combination with crossbar.io so i do not understand all the patterns/workflows yet.

    For example in a simple-chatapp, i could not find the right place to hook some code into a publish/subscribe flow:

    The user logs in via a normal GET/POST into the website, some infos are written into his session, and he is redirected to a websocket chat-app. There the websocket connection via AutobahnJS is established.
    With your code i can access the cookie, read the data out of the PHPSESSION and i check if this user is really logged in. After this successfull authentication he gets an subscription to a topic ‘all_chat’ where all messages from the different users are shown. So when the users writes his chat message and “sends” it, a publish method is called to “all_chat”.
    His username is stored in the PHPSessioninformation, which i will access with the cookie and i want to add his username to his published message, so that the other users can see, who sent this message.
    Where is the right place to add this information? Is this the right approach at all or is there a better one?

    I’m looking forward to hear from you

  3. So the $key is supposed to just be some random, hard to guess string, and $authid is the username of the user, or some other static, unique information. Is that correct?

    This authentication looks like it will allow me to reject connections from clients without a valid token, but how could this be used to only reject connections for specific topics instead of the entire connection?

    For example, on my website I want all users (whether logged in or not) to be able to subscribe to updates to a forum thread they’re viewing so that new posts will be pushed to them. However, I only want logged in users to be able to subscribe to “user notifications,” and I want to prevent users from subscribing to another user’s notifications.

    1. Hi Nate,

      Yes, the $key is just a shared secret. Keep is secret and difficult to guess.

      In this example I am assuming you are using your traditional web site to generate the token that is then used to connect to the Thruway router. If you want people to have different access levels, you can generate tokens that also include an auth group for the user. You would have to modify the authentication code above to extract the auth roles from the token and return them back.

      return [
      “SUCCESS”,
      [“authid” => $user->login, “authroles” => $roles]
      ];

      You would then be able to use authorization to limit by the role the user has.

      Let me know if you have any other questions about that or need more information.

      Matt

  4. Can anyone explain how to use this in Laravel app. I want the users to sign in using Laravel app and be authenticated as the same user in Thruway. The user should not be able to open a new tab and behave as a new user. He has to be the same user which was authenticated using Laravel during signin.

Leave a Reply