Understanding ejabberd OAuth Support & Roadmap

Note: For up-to-date documentation on OAuth configuration and usage in recent ejabberd versions, check the corresponding page in the ejabberd Docs site: OAuth Support.

Login and password authentication is still the most commonly used auth mechanism on XMPP services. However, it is causing security concerns because it requires to store the credentials on the client app in order to login again without asking for the password.

Mobile APIs on iOS and Android can let you encrypt the data at REST, but still, it is best not to rely on storing any password at all.

Fortunately, several solutions exist – all supported by ejabberd. You can either use OAuth or Certificate-based authentication. Client certificate management being still quite a tricky issue, I will focus in this post on explaining how to set up and use ejabberd OAuth support.

Understanding ejabberd OAuth Support

The principle of OAuth is simple: OAuth offers a mechanism to let your users generate a token to connect to your service. The client can just keep that token to authenticate and is not required to store the password for subsequent authentications.

Implicit grant

As of ejabberd 19.09, ejabberd supports only the OAuth implicit grant. Implicit grant is often used to let third-party clients — clients you do not control — connect to your server.

The implicit grant requires redirecting the client to a web page, so the client does not even see the login and password of the user. Indeed, as you cannot trust third-party clients, this is the sane thing to do to keep your users’ passwords for being typed directly in any third-party client. You can never be sure that the client will not store it (locally, or worse, in the cloud).

With the implicit grant, the client app directs the user to the sign-in page on your server to authenticate and get the token, often with login and password (but the mechanism can be different and could involve 2FA, for example). Your website then uses a redirect URL that will be passed back to the client, containing the token to use for logging in. The redirect happens usually using client-registered domain or custom URL scheme.

… and password grant

The implicit grant workflow is not ideal if your ejabberd service is only useable with your own client. Using web view redirects can feel cumbersome in your onboarding workflow. As you trust the client, you probably would like to be able to directly call an API with the login and password, get the OAuth token back, and forget about the password. The user experience will be more pleasant and feel more native.

This flow is known in OAuth as the OAuth password grant.

In the upcoming ejabberd version, you will be able to use OAuth password grant as an addition to the implicit grant. The beta feature is already in ejabberd master branch, so you have a good opportunity to try it and share your feedback.

Let’s use ejabberd OAuth Password grant in practice

Step 1: ejabberd configuration

To support OAuth2 in ejabberd, you can add the following directives in ejabberd config file:

# Default duration for generated tokens (in seconds)
# Here the default value is 30 days
oauth_expire: 2592000
# OAuth token generation is enabled for all server users
oauth_access: all
# Check that the client ID is registered
oauth_client_id_check: db

In your web HTTPS ejabberd handler, you also need to add the oauth request handler:

listen:
  # ...
  -
    port: 5443
    ip: "::"
    module: ejabberd_http
    tls: true
    request_handlers:
      # ...
      "/oauth": ejabberd_oauth

Note: I am using HTTPS, even for a demo, as it is mandatory to work on iOS. During the development phase, you should create your own CA to add a trusted development certificate to ejabberd. Read the following blog post if you need guidance on how to do that: Using a local development trusted CA on MacOS

You can download my full test config file here: ejabberd.yml

Step 2: Registering an OAuth client

If you produce a first party client, you can bypass the need for OAuth to redirect to your browser to get the token.

As you trust the application you are developing, you can let the user of your app directly enter the login and password inside your client. However, you should never store the password directly, only the OAuth tokens.

In ejabberd, I recommend you first configure an OAuth client, so that it can check that the client id is registered.

You can use the ejabberdctl command oauth_add_client_password, or use the Erlang command line.

Here is how to use ejabberdctl to register a first-party client:

ejabberdctl oauth_add_client_password <client_id> <client_name> <secret>

As the feature is still in development, you may find it easier to register your client directly using Erlang command-line. Parameters are client_id, client_name and a secret:

1> ejabberd_oauth:oauth_add_client_password(<<"client-id-Iegh7ooK">>, <<"Demo client">>, <<"3dc8b0885b3043c0e38aa2e1dc64">>).
{ok,[]}

Once you have registered a client, you can start generating OAuth tokens for your users from your client, using an HTTPS API.

Step 3: Generation a password grant token

You can use the standard OAuth2 password grant query to get a bearer token for a given user. You will need to pass the user JID and the password. You need to require the OAuth scope sasl_auth so that the token can be used to authentication directly in the XMPP flow.

Note: As you are passing the client secret as a parameter, you must use HTTPS in production for those queries.

Here is an example query to get a token using the password grant flow:

curl -i -POST 'https://localhost:5443/oauth/token' -d grant_type=password -d username=test@localhost -d password=test -d client_id=client-id-Iegh7ooK  -d client_secret=3dc8b0885b3043c0e38aa2e1dc64 -d scope=sasl_auth

HTTP/1.1 200 OK
Content-Length: 114
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{"access_token":"DGV4JFzW15iZFmsnvzT7IymupTAYvo6U","token_type":"bearer","scope":"sasl_auth","expires_in":2592000}

As you can see, the token is a JSON string. You can easily extract the access_token from it. That’s the part you will use to authenticate on XMPP.

Step 4: Connecting on XMPP using an OAuth token

To authenticate over XMPP, you need to use the X-OAUTH2 mechanism. X-OAUTH2 was defined by Google for Google Talk and reused later by Facebook chat. You can find Google description here: XMPP OAuth 2.0 Authorization.

Basically, it encodes the JID and token as in the SASL PLAIN authorisation, but instead of passing the PLAIN keyword as mechanism, it uses X-OAUTH2. ejabberd will thus know that it has to check the secret against the token table in the database, instead of checking the credentials against the password table.

Quick demo

Next, let’s demonstrate the connection using Fluux Go XMPP library, which is the only library I know that supports OAuth tokens today.

Here is an example client login on XMPP with an OAuth2 token:

package main

import (
    "fmt"
    "log"
    "os"

    "gosrc.io/xmpp"
    "gosrc.io/xmpp/stanza"
)

func main() {
    config := xmpp.Config{
        Address:      "localhost:5222",
        Jid:          "test@localhost",
        Credential:   xmpp.OAuthToken("DGV4JFzW15iZFmsnvzT7IymupTAYvo6U"),
        StreamLogger: os.Stdout,
    }

    router := xmpp.NewRouter()
    router.HandleFunc("message", handleMessage)

    client, err := xmpp.NewClient(config, router)
    if err != nil {
        log.Fatalf("%+v", err)
    }

    // If you pass the client to a connection manager, it will handle the reconnect policy
    // for you automatically.
    cm := xmpp.NewStreamManager(client, nil)
    log.Fatal(cm.Run())
}

func handleMessage(s xmpp.Sender, p stanza.Packet) {
    msg, ok := p.(stanza.Message)
    if !ok {
        _, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
        return
    }

    _, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
}

The important part for OAuth is that you are telling the library to use an OAuth2 token with the following value in the xmpp.Config struct:

xmpp.Config{
    // ...
        Credential:   xmpp.OAuthToken("DGV4JFzW15iZFmsnvzT7IymupTAYvo6U"),
        }

You can check the example in Fluux XMPP example directory: xmpp_oauth2.go

There is more

As I said, ejabberd OAuth support is not limited to generating password grant. Since ejabberd 15.09, we support implicit grant generation and it is still available. You can find more information in ejabberd documentation: OAuth

Moreover, there is more than XMPP authentication with OAuth 2. In the current development version, you can authenticate your devices on ejabberd MQTT service using MQTT 5.0 Enhanced Authentication. The authentication method to use is the same as for XMPP: We reuse the X-OAUTH2 method name. When trying to use this method, the server will confirm you are allowed to use that method and you can pass your token in return.

Please, note that you will need to use an MQTT 5.0 client library to use OAuth2 authentication with MQTT.

Conclusion

ejabberd OAuth XMPP and MQTT authentication is using the informal auth mechanism that was introduced by Google Talk and reused by Facebook. It does the job and fills an important security need.

That said, I would love to see more standard support from the XMPP Standard Foundation regarding OAuth authentication. For example, getting a specification translating OAuth authentication to XMPP flow would be of great help.

Still, in the meantime, I hope more libraries support that informal OAuth specification, so that client developers have good alternative to local password storage for subsequent authentications.

Please, give it a try from master and send us feedback if you want to help us shape the evolution of OAuth support in ejabberd.

… And let’s end password-oriented client authentication :)


Let us know what you think 💬


4 thoughts on “Understanding ejabberd OAuth Support & Roadmap

  1. Hello,

    This is really nice, I have some questions:

    1) does Ejabberd supports or plans to support scopes? For instance, if I have a third party client which wants to only publish messages from the logged jid, or to access its roster (read only, or being able to modify it), is it possible?
    2) indeed this should goes to standards, do you have any plan to propose it?

    thanks!

  2. “Client certificate management being still quite a tricky issue”

    Which makes me even more curious! Is there a blog post or a howto about it?

  3. Hi,
    This is good simple way of teaching.
    This works fine for me on commend line code
    “curl -i -POST ‘https://localhost:5443/oauth/token’ -d grant_type=password -d username=test@localhost -d password=test -d client_id=client-id-Iegh7ooK -d client_secret=3dc8b0885b3043c0e38aa2e1dc64 -d scope=sasl_auth”

    But if I try to access this end point from angular client app, facing CROS issue.

    Thanks,
    Vijay A

Leave a Reply to Martin Cancel Reply


This site uses Akismet to reduce spam. Learn how your comment data is processed.