Ubuntu logo

Developer

Online Accounts developer guide

Terms and definitions

Online Accounts uses some straightforward terminology, that is nevertheless important to outline before further discussion.

  • account: an account is given by a provider to a user, and allows access to services.
  • provider: a provider gives out accounts to users, as well as allowing them to use the account with a variety of services. A user can have multiple accounts by the same provider, and each account can consist of multiple services.
  • service: a service is hosted by a provider, and allows the user to perform some task.
  • service type: a service type describes what is the main functionality provided by a service. Service types are a useful way to group services which support a certain functionality. Examples are “calendar”, “e-mail”, “sharing”.
  • short app ID: a unique identifier for the click application: it is given by the click package name followed by an underscore and the application name: <package>_<app>.

Packaging and manifest files

Applications using Online Accounts need to add the ”accounts” policy group to their AppArmor manifest file:

{
  "policy_groups": [
    "networking",
    "accounts",
    ...
  ],
  "policy_version": 1.1
}

You can read more about application confinement here.

Next, the application needs to ship files which describe the services it’s going to use; normally, they can be as simple as this:

app.service:

<?xml version="1.0" encoding="UTF-8"?>
<service>
  <type>ubuntu.com.developer.me.MyClick_MyApp</type>
  <provider>facebook</provider>
</service>

app.application:

<?xml version="1.0" encoding="UTF-8" ?>
<application>
  <services>
    <service id="com.ubuntu.developer.me.MyClick_MyApp">
      <description>Post your pictures to Facebook</description>
    </service>
  </services>
</application>

The names of two files need to be added to the main manifest.json file of package, like this:

"hooks":
{
  "facebook-photos": {
    "apparmor": "app.json",
    "account-application": "app.application",
    "account-service": "app.service"
  }
  ...
}

The app.service file describes the online service which the application will access; it needs to contain a element containing the ID of the account provider.

The available account providers can be found in /usr/share/accounts/providers/ (those installed via click packages will be found in ~/.local/share/accounts/providers/), and their ID is given by their filename minus the “.provider” suffix.

The app.application file needs to declare which services the application can use, and that can be done by writing the service ID (which in the typical case is the short app ID) into the “id” tag of the <service> element.

More information on how to write .service and .application files can be found here.

It’s anyway advisable to start with a minimal file, and then extend it only once the project is proven to be working.

Online Accounts for application developers

One of the main goals of the Online Accounts project is to simplify how applications obtain access to online services. The UI for setting up and configuring an online account can be quite complex, often requiring embedding a web view in order to perform the login on the remote website.

By integrating with the Online Accounts framework, an application can get rid of all the account management UI: all accounts are configured in the Accounts applet in the system settings, so the application is a simple consumer of the accounts.

Indeed, applications can also invoke the Accounts configuration panel
when they want to ask the user to configure a new account.

Using the configured accounts

Generally, the first step performed by an application using Online Accounts is the discovery of all the existing accounts which are supported to the application. If there is no available account (or if the application supports multiple accounts), it’s possible to request one new account from Online Accounts.

Once an application has found out what accounts it can use, the second step is attempting to log into the remote services where the accounts are registered and, finally, trying to do something useful with them.

The Online Accounts framework can help the application to perform the first two steps, as described in more detail in the following sections.

Accessing the accounts database

The API to enumerate the accounts is provided by the AccountServiceModel QML element.

It’s also possible to access the same functionality by using C++ (via libaccounts-qt and libsignon-qt) or C (via libaccounts-glib and libsignon-glib), but the QML bindings are the simplest and most recommended way; we’ll focus on the QML bindings throughout this document.

The AccountServiceModel offers a real-time view over the accounts database, listing all the configured accounts. The model exposes several properties which allow to filter the accounts according to different criteria, but the typical application would just want to set the “applicationId” property to its short app ID and will see only the accounts which it supports and which the user has authorized it to use:

import QtQuick 2.0
import Ubuntu.Components 0.1
import Ubuntu.OnlineAccounts 0.1

Item {
  AccountServiceModel {
    id: accounts
    applicationId: "com.ubuntu.developer.me.MyClick_MyApp"
  }

  ListView {
    model: accounts
    delegate: Text { text: "Account: " + model.displayName }
  }
}

When an application is run for the first time after being installed it won’t see any accounts in the model, because – even if the user might have some accounts already configured in the System Settings – the user hasn’t yet authorized it to use any accounts. The application needs to explicitly request access to the user’s accounts, and this is done via the Setup element:

import QtQuick 2.0
import Ubuntu.Components 0.1
import Ubuntu.OnlineAccounts 0.1
import Ubuntu.OnlineAccounts.Client 0.1

Item {
  AccountServiceModel {
    id: accounts
    applicationId: "com.ubuntu.developer.me.MyClick_MyApp"
  }

  Setup {
    id: setup
    applicationId: accounts.applicationId
    providerId: "facebook"
  }

  ListView {
    model: accounts
    delegate: Text { text: "Account: " + model.displayName }
  }

  Button {
    visible: accounts.count === 0 /* remove this if your app supports
                                     multiple accounts */
    text: "Authorize a Facebook account"
    onClicked: setup.exec()
  }
}

The code above would show a list of authorized accounts, and in case the list is empty it would show a button which, when pressed, would request the user to authorize a new account.

Logging in

Once the application has got an account, it can proceed and obtain a password or an authentication token for that account. This is simply done by instantiating an AccountService element and calling the authenticate() method on it:

import QtQuick 2.0
import Ubuntu.OnlineAccounts 0.1

Item {
  AccountServiceModel {
    id: accounts
    applicationId: "com.ubuntu.developer.me.MyClick_MyApp"
  }
  ListView {
    model: accounts
    delegate: Rectangle {
      id: rect
      Text { text: rect.model.displayName }
      AccountService {
        id: accountService
        objectHandle: rect.model.accountServiceHandle
        onAuthenticated: { console.log("Access token is " + reply.AccessToken) }
        onAuthenticationError: { console.log("Authentication failed, code " + error.code) }
      }
      MouseArea {
        anchors.fill: parent
        onClicked: accountService.authenticate()
      }
    }
  }
}

The AccountService element is initialized by setting its objectHandle property to the value of the accountServiceHandle role provided by the model. After the authenticate() method has been called, the AccountService element will emit either the authenticated() signal or, in case of error, the authenticationError() signal.

Applications might want to specify some additinal parameters when performing the authentication; for example, an application which is logging into an account which supports OAuth should specify its own client ID and client secret. It is possible to pass a dictionary of parameters to the authenticate() method, or write this additional parameters into the app.servicefile shipped with the application. For example, if an application wants to authenticate to an account supporting OAuth 2.0 with the "AaXx" client ID and "BbYy" client secret, it can do that by having a service file like this:

<?xml version="1.0" encoding="UTF-8"?>
<service>
  <type>ubuntu.com.developer.me.MyClick_MyApp</type>
  <provider>facebook</provider>

  <template>
    <group name="auth/oauth2/user_agent">
      <setting name="ClientId">AaXx</setting>
      <setting name="ClientSecret">BbYy</setting>
    </group>
  </template>
</service>

(you can read more about .service files here) or by passing the same parameters to authenticate():

...
      AccountService {
        id: accountService
        objectHandle: rect.model.accountServiceHandle
        onAuthenticated: { console.log("Access token is " + reply.AccessToken) }
        onAuthenticationError: { console.log("Authentication failed, code " + error.code) }
      }
      MouseArea {
        anchors.fill: parent
        onClicked: accountService.authenticate({
          "ClientId": "AaXx",
          "ClientSecret": "BbYy"
        })
      }
    ...

The parameters passed to the authenticate() method, as well as the reply object returned with the authenticated() signal, depend on the authentication method being used. In the next sections, the most common authentication method are described, along with their parameters and replies.

There is one generic authentication parameter which can be useful to alter the default behaviour of the authentication plugins:

  • UiPolicy: tells whether user interactions should happen. It can be set to any of these values:
    • 0 (default): user interaction will happen only if required.
    • 1: user interaction is required: even if a valid password or token is stored in the account, it must be discarded. In the case of username/password authentication, this means that the user will be requested to enter his password; for OAuth, the user will have to go through the authentication again.
    • 2: user interaction is forbidden: if user interaction is needed in order to obtain a password or complete the OAuth login, don’t attempt to perform any of these steps: instead, just return an error.
OAuth 2.0 authentication

The OAuth 2.0 method (called "oauth2" in the account configuration) supports two different authentication mechanisms, user_agent and web_server; the set of required parameters can differ depending on the mechanism used, and differences are mentioned in the list below:

  • Host: the server on which the authentication happens, for example “www.facebook.com”. Note: applications shouldn’t need to specify this parameter.
  • AuthPath: Authorization endpoint of the server, relative to the host parameter. Note: applications shouldn’t need to specify this parameter.
  • TokenPath: Token endpoint of the server, relative to the host parameter. Note: applications shouldn’t need to specify this parameter.
  • ResponseType: Response type of the authentication request. Note: applications shouldn’t need to specify this parameter.
  • RedirectUri: Redirection URI to collect the OAuth response. Note: applications shouldn’t need to specify this parameter.
  • ClientId: client application ID, usually obtained when registering an application with the service. This parameter is mandatory.
  • ClientSecret: client application secret. This parameter is mandatory for the web_server mechanism only.
  • Scope: Access token scope: this is a server-specific list of scopes. Refer to the provider’s documentation for a list of possible values.
  • ForceTokenRefresh (boolean): if set to true, the access token currently stored in the account is deleted. Applications can specify this flag when the access token they are currently using turns out to be invalid. This parameter should never be specified in a .service file.

The first five parameters above are typically only used by account plugins; applications rarely need to override them.

The OAuth 2.0 response consists of the following keys:

  • AccessToken: the OAuth access token.
  • ExpiresIn: token validity time, in seconds. Normally applications don’t need to worry about this.
OAuth 1.0a authentication

The OAuth 1.0a method (called "oauth2" in the account configuration) supports different signatures, which are represented by the PLAINTEXT, HMAC_SHA1 and RSA_SHA1 mechanisms. The authentication parameters are the same regardless of the mechanism being used:

  • RequestEndpoint: the URL where temporary credentials should be requested. Note: applications shouldn’t need to specify this parameter.
  • AuthorizationEndpoint: the URL where the user authorization should be requested. Note: applications shouldn’t need to specify this parameter.
  • TokenEndpoint: the URL where token credentials will be obtained. Note: applications shouldn’t need to specify this parameter.
  • Callback: callback URL, to collect the OAuth response. Note: applications shouldn’t need to specify this parameter.
  • Realm: optional realm for the HTTP Authorization header. Note: applications shouldn’t need to specify this parameter.
  • ConsumerKey: client application ID, usually obtained when registering an application with the service. This parameter is mandatory.
  • ConsumerSecret: client application secret, used to sign the authentication requests. This parameter is mandatory.
  • ForceTokenRefresh (boolean): if set to true, the access token currently stored in the account is deleted. Applications can specify this flag when the access token they are currently using turns out to be invalid. This parameter should never be specified in a .service file.

The first five parameters above are typically only used by account plugins; applications rarely need to override them.

The OAuth 1.0a response consists of the following keys:

  • AccessToken: the OAuth access token.
  • TokenSecret: the OAuth secret token, used for signing the requests.
  • UserId: this optional property might contain the ID of the authenticated user.
  • ScreenName: this optional property might contain the screen name of the authenticated user.

Once the client application has received the authentication reply, it will need to sign the requests made against the service APIs. In order to generate the signature, besides the AccessToken and TokenSecret values which it received from the server, also the ConsumerKey and ConsumerSecret. While typically both of these values should be already known to the application, it’s also possible to retrieve them via the AccountService::authData property:

...
  AccountService {
    onAuthenticated: {
      console.log("Access token is " + reply.AccessToken)
      console.log("Token secret is " + reply.TokenSecret)
      console.log("Consumer key is " + authData.parameters.ConsumerKey)
      console.log("Consumer secret is " + authData.parameters.ConsumerSecret)
    }
  }
  ...
Username/password authentication

In order to retrieve the username and password stored in an account, applications need to use the "password" method and "password" mechanism. No parameters are required when authenticating, and the response will contain:

  • UserName: the user’s login.
  • Secret: the user’s password.

Online Accounts for service developers

The list of providers and services supported by Online Accounts can be extended by third-party developers.

Adding a new provider consists of two steps: creating an XML file to register the provider with libaccounts, and creating a QML plugin which the Online Accounts configuration applet will load when an account for this provider needs to be created or edited.

Creating a .provider file

Providers are described with a simple XML file, whose format is fully described here.
In its simplest form, the XML file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<provider>
  <name>Five triangles</name>
  <icon>five-circles.svg</icon>
</provider>

In the case where the authentication can happen using OAuth (be that 1.0a or 2.0), it’s possible to store the authentication parameters in the .provider file, so that they can be used as default parameters by all applications (including the account plugin itself).
The "auth/method" and "auth/mechanism" keys respectively define which authentication method and mechanism will be used for authenticating on the service (note that applications can still override them, if they want to).
Under the group "auth/<method>/<mechanism>" one can store the authentication parameters specific to the given method and mechanism; for OAuth-based services, these are described in the sections OAuth 1.0a authentication and OAuth 2.0 authentication and their values must be found in the developer documentation provided by the service.
Here’s an example:

<?xml version="1.0" encoding="UTF-8"?>
<provider>
  <name>Five triangles</name>
  <icon>five-circles.svg</icon>

  <template>
    <group name="auth">
      <setting name="method">oauth2</setting>
      <setting name="mechanism">user_agent</setting>
      <group name="oauth2">
        <group name="user_agent">
          <setting name="Host">www.facebook.com</setting>
          <setting name="AuthPath">/dialog/oauth</setting>
          <setting name="RedirectUri">https://www.facebook.com/connect/login_success.html</setting>
          <setting type="as" name="Scope">['publish_stream','status_update','user_photos']</setting>
          <setting name="ClientId">412471239412</setting>
          <setting type="as" name="AllowedSchemes">['https','http']</setting>
        </group>
      </group>
    </group>
  </template>
</provider>

More examples can be found by looking at the other existing providers in /usr/share/accounts/providers/.

Creating the QML plugin

The QML plugin is responsible for creating the UI to be shown when an account needs to be created or edited. The root element of the plugin is expected to be a Flickable, and it will have access to an account context variable of type
Account which represents the account being created or edited. In order to decide whether the desired operation is the creation or the editing of an account, the plugin can check the account.accountId property, whose value will be 0 for the creation operation, and a number greater than zero if the account already exists in the database and just needs to be edited.
When the plugin is done creating or editing the account, it needs to emit a finished() signal.

Here’s an example of a plugin skeleton:

import QtQuick 2.0
import Ubuntu.Components 0.1

Flickable {
    id: root

    signal finished

    Loader {
        id: loader
        anchors.fill: parent
        sourceComponent: account.accountId != 0 ? existingAccountComponent : newAccountComponent

        Connections {
            target: loader.item
            onFinished: root.finished()
        }
    }

    Component {
        id: newAccountComponent
        NewAccount {} // UI for creating a new account
    }

    Component {
        id: existingAccountComponent
        EditAccount {} // UI for editing an existing account
    }
}

The Online Accounts UI comes with a QML module providing a few elements which can simplify the task of creating an account plugin. For instance, it offers an Options element which can be used as the UI for editing accounts (just replace EditAccount with Options in the example above to take it into use), as well as QML elements for OAuth based services.

In order to use the elements from this module, import it as

import Ubuntu.OnlineAccounts.Plugin 1.0

In the case of OAuth based services, the module provides a good starting point which gives an already usable account plugin in just a couple of lines:

import Ubuntu.OnlineAccounts.Plugin 1.0

OAuthMain {}

Some more complete examples can be found in the directory /usr/share/accounts/qml-plugins/.

Debugging a QML plugin

In order to see the console output from an account plugin, the following commands need to entered in the device where the Online Accounts UI will be running:

  export OAU_LOGGING_LEVEL=2
  export OAU_DAEMON_TIMEOUT=9999
  online-accounts-service

The last command won’t return (it can be stopped by pressing Ctrl+C at any time, though), and while running it will be showing all the output from the account plugin: for instance, if you add a console.log("Hello World") statement in your plugin, you’ll see it appearing in this terminal when your plugin is being run.

Packaging an account plugin

Account plugins can be shipped as part of a click package. The .provider file and the QML plugin simply need to be specified as values for the account-provider and account-qml-plugin hooks, respectively:

"hooks":
{
  "account-plugin": {
    "account-provider": "mysite.provider",
    "account-qml-plugin": "mysite"
  }
  ...
}

Note that while the account-provider value should be the exact .provider file, account-qml-plugin should be set to the name of the directory containing all the QML files; indeed, this is because an account plugin can cosist of several QML files.