Skip to content

mod_auth_token

Module Description

This module implements handling of tokens in an OAuth-like authentication scheme. It provides services necessary to:

  • deserialize/serialize binary tokens received and issued by the server,
  • validate incoming binary tokens, i.e.:
    • check integrity using Message Authentication Codes (MAC) with server-side stored user keys,
    • check validity against the configured validity duration times,
    • check revocation status,
  • handle token requests from logged in users.

The module itself does not implement protocol related details - these are implemented in cyrsasl.erl. Generation of keys necessary to sign binary tokens is delegated to module mod_keystore.erl.

Options

modules.mod_auth_token.backend

  • Syntax: non-empty string
  • Default: "rdbms"
  • Example: backend = "rdbms"

Token storage backend. Currently only "rdbms" is supported.

modules.mod_auth_token.iqdisc.type

  • Syntax: string, one of "one_queue", "no_queue", "queues", "parallel"
  • Default: "no_queue"

Strategy to handle incoming IQ stanzas. For details, please refer to IQ processing policies.

modules.mod_auth_token.validity_period

  • Syntax: TOML table. Each key is either access or refresh. Each value is a nested TOML table with the following mandatory keys: value (non-negative integer) and unit ("days", "hours", "minutes" or "seconds").
  • Default: {access = {value = 1, unit = "hours"}, refresh = {value = 25, unit = "days"}}
  • Example: validity_period.access = {value = 30, unit = "minutes"}

Validity periods of access and refresh tokens can be defined independently - specifying one of them does not change the default value for the other one. Validity period configuration for provision tokens happens outside the module since the server does not generate provision tokens - it only validates them.

Required keys

To read more about the keys MongooseIM makes use of, please refer to mod_keystore documentation, where you can find an example configuration when using mod_auth_token.

Token types

Three token types are supported:

  • access tokens: These are short lived tokens which grants aren't tracked by the server (i.e. there's no need to store anything in a database). Access tokens can be used as a payload for the X-OAUTH authentication mechanism and grant access to the system. Access tokens can't be revoked. An access token is valid only until its expiry date is reached. In mod_keystore, the keyname for this token type is token_secret.

  • refresh tokens: These are longer lived tokens which are tracked by the server and therefore require persistent storage in a relational database. Refresh tokens can be used as a payload for the X-OAUTH authentication mechanism and to grant access to the system. Also they can result in a new set of tokens being returned upon successful authentication. They can be revoked - if a refresh token hasn't been revoked, it is valid until it has expired. On revocation, it immediately becomes invalid. As the server stores information about granted tokens, it can also persistently mark them as revoked. In mod_keystore, the keyname for this token type is token_secret.

  • provision tokens: These tokens are generated by a service external to the server. They grant the owner a permission to create an account. A provision token may contain information which the server can use to provision the VCard for the newly created account. Using a provision token to create an account (and inject VCard data) is done similarly to other token types, i.e. by passing it as payload for the X-OAUTH mechanism. The XMPP server has no way of tracking and revoking provision tokens, as they come from an outside source. In mod_keystore, the keyname for this token type is provision_pre_shared. The usage of this token type is optional.

Token serialization format

All tokens (access, refresh, provision) are to be exchanged as Base64 encoded binary data. Serialization format of the token before encoding with Base64 is dependent on its type:

1
2
3
4
5
'access' \0 <BARE_JID> \0 <EXPIRES_AT> \0 <MAC>

'refresh' \0 <BARE_JID> \0 <EXPIRES_AT> \0 <SEQUENCE_NO> \0 <MAC>

'provision' \0 <BARE_JID> \0 <EXPIRES_AT> \0 <VCARD> \0 <MAC>

For example (these tokens are randomly generated, hence field values don't make much sense - line breaks are inserted only for the sake of formatting,<vCard/> inner XML is snipped):

1
2
3
4
5
6
7
8
'access' \0 Q8@localhost \0 64875466454
    \0 0acd0a66d06934791d046060cf9f1ad3c2abb3274cc7e7d7b2bc7e2ac4453ed774b6c6813b40ebec2bbc3774d59d4087

'refresh' \0 qp@localhost \0 64875466457 \0 6
    \0 8f57cb019cd6dc6e7779be165b9558611baf71ee4a40d03e77b78b069f482f96c9d23b1ac1ef69f64c1a1db3d36a96ad

'provision' \0 Xmi4@localhost \0 64875466458 \0 <vCard>...</vCard>
    \0 86cd344c98b345390c1961e12cd4005659b4b0b3c7ec475bde9acc9d47eec27e8ddc67003696af582747fb52e578a715

Requesting access or refresh tokens when logged in

1
2
3
<iq type='get' to='john@localhost' id='123'>
    <query xmlns='erlang-solutions.com:xmpp:token-auth:0'/>
</iq>

To request access and refresh tokens for the first time a client should send an IQ stanza after they have successfully authenticated for the first time using some other method.

Token response format

Requested tokens are being returned by the server wrapped in IQ stanza with the following fields:

  • id: value taken from the request IQ stanza
  • type: result
  • from: bare user JID
  • to: full user JID

Example response (encoded tokens have been truncated in this example):

1
2
3
4
5
6
<iq  id='123' type='result' from='john@localhost' to='john@localhost/res1'>
    <items xmlns='erlang-solutions.com:xmpp:token-auth:0'>
        <access_token>cmVmcmVzaAGQ1Mzk1MmZlYzhkYjhlOTQzM2UxMw==</access_token>
        <refresh_token>cmVmcmVzaAGQ1Mzk1MmZlYzhkYjhlOTQzM2UxMw==</refresh_token>
    </items>
</iq>

Once a client has obtained a token, they may start authenticating using the X-OAUTH SASL mechanism when reaching the authentication phase of an XMPP connection initiation.

Login with access or refresh token

In order to log into the XMPP server using a previously requested token, a client should send the following stanza:

1
2
3
<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="X-OAUTH">
cmVmcmVzaAGQ1Mzk1MmZlYzhkYjhlOTQzM2UxMw== 
</auth>

The Base64 encoded content is a token obtained prior to authentication. Authentication will succeed unless the used tokens are expired, revoked, or the keys required for MAC verification could not be found by the server.

When using a refresh token to authenticate with the server, the server will respond with a new access token:

1
2
3
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
cmVmcmVzaAGQ1Mzk1MmZlYzhkYjhlOTQzM2UxMw==
</success>

The above response is to be expected unless the refresh token used is expired or there were some problems processing the key on the server side.

Token revocation using command line tool

Refresh tokens issued by the server can be used to:

  • log in a user: as an authentication valet,
  • request a new access token with refreshed expiry date.

An administrator may revoke a refresh token:

1
mongooseimctl token revokeToken --user owner@xmpphost

A client can no longer use a revoked token either for authentication or requesting new access tokens. After a client's token has been revoked, in order to obtain a new refresh token a client has to log in using some other method.

Caveat: as of now, the user's session is not terminated automatically on token revocation. Therefore, the user might request a new set of tokens for as long as the session is active, even though their previous token was just revoked (possibly due to a breach / token leak). Moreover, an access token still kept on a compromised device can be used to establish a new session for as long as it's valid - access tokens can't be revoked. To alleviate rerequesting tokens by the user, an operator can use mod_admin extension allowing to terminate the user's connection. Access token validity can't be sidestepped right now.

Example configuration

1
2
3
[modules.mod_auth_token]
  validity_period.access = {value = 13, unit = "minutes"}
  validity_period.refresh = {value = 13, unit = "days"}