Security Overview

How encryption, authentication and permissioning work together

Security in deepstream is based on three interrelated concepts:

- encrypted connections
- user authentication
- granular permissions

Here’s how they work together:

Encrypted Connections

deepstream supports transport layer security for web-facing connections using HTTPS and WSS. To setup SSL on deepstream, you need to provide the following configuration keys:

ssl:
  key: fileLoad(./my-key.key)
  cert: fileLoad(./my-key.key)

It’s highly recommended to always use a seperate process to do SSL termination. Usually via a load balancer (e.g. Nginx or HA Proxy). To learn more about this, head over to the Nginx Tutorial.

Authentication

Every incoming connection needs to pass an authentication step. This happens when the client calls login(data, callback). deepstream comes with three built-in authentication mechanisms:

  • none allows every connection. Choose this option for public sites that don’t require access controls.
  • file reads authentication data from a static file. This is a good choice for public read / private write use cases, e.g. sports result pages that let every user visit, but only a few backend processes update the result.
  • http contacts a configurable HTTP webhook to ask if a user is allowed to connect. This is the most flexible option as it allows you to write a tiny http server in any language that can connect to databases, active directories, oAuth providers or whatever else your heart desires.

Apart from just accepting / denying incoming connections, the authentication step can also provide two extra bits of information:

  • clientData is returned to the client upon login. This is useful to provide user specific settings upon login, e.g. { "view": "author" } for a blog
  • serverData is kept privately on the server and passed to the permission handler whenever a client performs an action. This makes certain security concepts possible, e.g. role based authentication.
"system-settings": {
    //publicly readable
    "read": true,
    //writable only by admin
    "write": "user.data.role === 'admin'"
}

Authentication FAQ

  • When exactly does authentication happen? When a deepstream client is created, it establishes a connection straight away. The connection however remains in a quarantine state until login() is called. This makes sure that auth-data is sent over an encrypted connection and helps to get the at times time-consuming connection establishment out of the way while the user is busy typing passwords.
  • Can I read usernames for auth purposes out of a deepstream record? There’s no built-in support for this at the moment, but it’s easy to use the http auth-type and write a server that reads from the same database or cache deepstream connects to.
  • Can a user connect more than once at the same time Yes. The same user can connect multiple times from separate browser windows or devices.

Permissioning

Permissioning is the act of deciding whether a specific action, e.g. writing to a record or subscribing to an event is allowed. To help with this, deepstream uses an expressive, JSON-based permissioning language called Valve. There’s a lot to say about Valve. Here’s a small Valve File that should give you a first impression, to learn more though head over to the permissioning tutorial

record:

  "*":
    create: true
    delete: true
    write: true
    read: true
    listen: true

  public-read-private-write/$userid:
    read: true
    create: "user.id === $userid"
    write: "user.id === $userid"

  only-increment:
    write: "!oldData.value || data.value > oldData.value"
    create: true
    read: true

  only-delete-egon-miller/$firstname/$lastname:
    delete: "$firstname.toLowerCase() === 'egon' && $lastname.toLowerCase() === 'miller'"

  only-allows-purchase-of-products-in-stock/$purchaseId:
    create: true
    write: "_('item/' + data.itemId ).stock > 0"

event:

  "*":
    listen: true
    publish: true
    subscribe: true

  open/"*":
    listen: true
    publish: true
    subscribe: true

  forbidden/"*":
    publish: false
    subscribe: false

  a-to-b/"*":
    publish: "user.id === 'client-a'"
    subscribe: "user.id === 'client-b'"

  news/$topic:
    publish: "$topic === 'tea-cup-pigs'"

  number:
    publish: "typeof data === 'number' && data > 10"

  place/$city:
    publish: "$city.toLowerCase() === data.address.city.toLowerCase()"

rpc:

  "*":
    provide: true
    request: true

  a-provide-b-request:
    provide: "user.id === 'client-a'"
    request: "user.id === 'client-b'"

  only-full-user-data:
    request: "typeof data.firstname === 'string' && typeof data.lastname === 'string'"