why deepstreamHub? compare us getting started feature roadmap faq
use cases pricing
products
developers
company
blog contact

A frequent requirement for any app is the need to send different data to different users. Whether it's updates to a social feed, discounts for frequent buyers, a list of matches on a dating platform or any other kind of private or at least user-specific information. Fortunately, all three of deepstreamHub's core concepts - it's realtime datastore, publish-subscribe, and request-response - provide various means to achieve this. The trick? Combine user-specific record or event names with deepstream's permissioning language Valve.

User-Specific Records

Providing private or user-specific records is as simple as including the username in the record name. If your social network has a profile for Lisa Miller, simply store the profile in a record called profile/lisa-miller:

var profile = ds.record.getRecord( 'profile/lisa-miller' );

Now we need to make sure that everyone can read that profile but only Lisa can edit her information. We can enforce this using Valve. In the record section of your dashboard's permission file let us create the following rule:

  "profile/$username":
    read: true
    write: "user.id === $username"

How does this rule work? First we specify profile/$username as a pattern. Whenever a record with a name matching this pattern is accessed, the rule will be applied. read: true makes sure that everyone can read the record's data. user.id === $username ensures that the $username part of the record name needs to match the username the user is currently logged in with if they wish to write.

User-Specific RPCs

Ok, so far, so simple. Let's look at a more advanced example including an HTTP authentication endpoint and a backend process that provides user-specific data as a response to remote procedure calls (RPCs). Say we're running an online pet-food shop and the more frequently a user orders, the higher a discount she gets. This means we need three things in our e-marketplace setup:

  • a HTTP-Server that checks the credentials of the user trying to log in,
  • a backend process that has access to prices as well as user discounts and can provide a RPC to retrieve a price
  • a way to make sure that the username the client provides when asking for the price is in fact their own.

To summarize, our setup will look as follows:

RPC permission flow

Let's go through the various components step by step, shall we? First off, the client needs to log in. We'll use a very basic login form: username, password, and an button labeled "login" is all we need.

Login Form

You can find this and all other files for this guide in the accompanying Github repo.

Once the user hits "login", the client executes deepstream's login method, providing the username and password as data.

login() {
    this.ds = deepstream( 'wss://123.deepstreamhub.com?apiKey=xxx' ).login({
        type: 'webhook',
        username: this.username(),
        password: this.password()
    }, this._onLogin.bind( this ))
}
Please note: I'm using ES6 class syntax and the amazingly simple yet powerful KnockoutJS for view-bindings. The same principles however apply for React, Angular, Vue, Android, iOS, or whatever else your heart desires.

The username and password now need to be validated. We'll do this by telling deepstreamHub to make an HTTP POST request to a given Webhook-URL. This can be configured in the authentication section of of the dashboard:

Webhook config on deepstreamHub dashboard

We'll process this request with a simple HTTP server written in Express that checks the credentials and returns the HTTP status code 200 for successful and 403 for failed login attempts.

For simplicity's sake we'll use a hardcoded map of cleartext passwords here. In the real world this information would ideally be hashed and stored in a database or provided by an open authentication API.

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
const users = {
  'user-a': { password: 'user-a-pass', serverData: { role: 'user' } },
  'user-b': { password: 'user-b-pass', serverData: { role: 'user' } },
  'data-provider': {
    password: 'provider-pass', serverData: { role: 'provider'}
  }
}

app.use(bodyParser.json());

app.post('/authenticate-user', function (req, res) {
    console.log( 'received auth request for ' + req.body.authData.username );
    var user = users[ req.body.authData.username ];
    if( user && user.password === req.body.authData.password ) {
        res.status( 200 ).json({
            username: req.body.authData.username,
            serverData: user.serverData
        });
    } else {
        res.sendStatus( 403 );
    }
});

app.listen( port, function () {
    console.log( `listening on port ${port}` );
});

Did you notice the extra "user" called data-provider above? We'll use it to authenticate connections from backend processes that can provide data to the user. Such a "provider" would first need to connect and log into deepstreamHub:

const deepstream = require( 'deepstream.io-client-js' );
const ds = deepstream( 'wss://154.deepstreamhub.com?apiKey=xxx'  );
const credentials = {
    username: 'data-provider',
    password: 'provider-pass',
    type: 'webhook'
};


ds.login( credentials, ( success, data ) => {
    if( success ) {
        console.log( 'connected to deepstreamHub' );
    }
});

Next up, we'll want to provide remote procedure call called get-price. This means we tell deepstream that whenever a client asks for get-price, this provider will be able to answer that question.

const itemPrice = 100;
const userdata = {
    'user-a': { discount: 0.1 },
    'user-b': { discount: 0.3 }
}

ds.rpc.provide( 'get-price', ( data, response ) => {
    var discount = userdata[ data.username ].discount;
    var finalPrice = itemPrice - ( discount * itemPrice );
    response.send( finalPrice );
});

Let's look through the snippet above. For simplicity, we specify our price and our various discounts as static data. Now when a user makes a request, they send their username as part of the RPC data:

this.ds.rpc.make( 'get-price', { username: this.username() },
this._onRpcResponse.bind( this ) );

To ensure that the provided username is in fact the one the user is logged in with, we'll use deepstream's permissioning language Valve again:

    request: "data.username === user.id"

The rule above means that the provider can be sure to get a valid, authenticated username and can return a price with the correct discount applied.

User-Specific Events

Ok, so what about user-specific "events", deepstream's publish-subscribe mechanism. Fundamentally, they work the same way as records: Make the username a part of the event name and use Valve to ensure that only the right user can subscribe to the right event.