Skip to content

OAuth Reference

OAuth2 client support is being phased-in with CiviCRM v5.32+.

This page discusses support for CiviCRM as an OAuth2 client. This is introduced in CiviCRM v5.32, but it is hidden by default. It will be more visible in future versions.

The OAuth protocols define an access-control system for Internet-based services. Compared to traditional access-control mechanisms (like simple password-based logins), OAuth provides more security features. For example, it can grant limited access to specific resources, and it can de-list devices or applications which misbehave. These features make it well-suited to large-scale technology providers like Google, Microsoft, or Facebook. If you wish to have CiviCRM interact with their APIs, then you probably need to configure OAuth.

Before beginning development with CiviCRM and OAuth, you may wish to read:

At time of writing, most implementations use OAuth version 2 (OAuth2). For this developer documentation, we will consider how to use CiviCRM as an OAuth2 client which accesses resources from a remote provider.

Model

The core extension oauth-client defines a programmatic model and helpers for managing CiviCRM as an OAuth2 client. Specifically, it exposes APIv4 entities (OAuthProvider, OAuthClient, and OAuthSysToken). We'll look at the entity definitions generally before we dive into usage examples.

Provider

Every remote service provider (such as Google or Microsoft) should be listed as a provider (OAuthProvider). This record gives well-known details about the provider, such as its name and authorization URL.

CiviCRM's OAuthProvider is built on top of the PHP League's oauth2-client.

Key properties of the OAuthProvider include:

Property Type Description Default
name string Symbolic name such as gmail or ms-exchange n/a
title string Displayable title such as "Google Mail" or "Microsoft Exchange Online" n/a
class string PHP class. In many cases, this is a generic class (e.g. League\OAuth2\Client\Provider\GenericProvider or Civi\OAuth\CiviGenericProvider). In some cases, it may be a specialized class (e.g. League\OAuth2\Client\Provider\Google). Civi\OAuth\CiviGenericProvider
options array Open-ended list of arguments to give to the provider class. Below are some common sub-properties supported by generic classes:
options . urlAuthorize string Well-known end-point for initiating authorization workflows n/a
options . urlAccessToken string Well-known end-point for requesting access-tokens n/a
options . urlResourceOwnerDetails string Well-known end-point for getting expanded information about the user. May be null. For CiviGenericProvider, this may use the string-literal {{use_id_token}} for OpenID Connect. n/a
options . scopeSeparator string
options . scopes array List of scopes to request access to n/a
mailSettingsTemplate array If the provider is specifically used for registering email accounts (MailSettings), then you can define a template for the MailSettings.

Note: OAuthProvider is a logical entity managed via hook. It is not stored in the MySQL database.

Client

To send requests to a provider, each CiviCRM deployment must be pre-registered with the provider. As part of this registration, the provider assigns a "Client ID" and "Client Secret". These are stored as properties in an OAuthClient record:

Property Type Description Default
provider string Symbolic name of the provider, such as gmail or ms-exchange n/a
guid string The unique, public "Client ID" assigned by the remote provider. This is typically a long alphanumeric identifier. n/a
secret string The secret "Client Secret" assigned by the remote provider. n/a
id int The unique, internal "ID" assigned by MySQL. This is typically a short number. auto
Question: What is the difference between client id and guid?

In OAuth2's specification, the client "ID" refers to a long, public identifier (often random alphanumerics) registered with the remote web-service. In CiviCRM's data management system, an "ID" is an internal, incrementing integer. Each OAuthClient will have both identifiers, stored in two fields:

  • The id field is the local/internal identifier required by the data-management layer. It is an incrementing integer.
  • The guid field is the public/external identifier. It is the value presented in web-service requests.

Informally, the word "ID" may describe either. In technical examples referencing the Civi API, the symbols id and guid should be interpreted as id (local/internal) and guid (public/external).

Token

When a user or administrator authorizes CiviCRM to access resources, the OAuth protocol will ultimately produce an access token.

The lifecycle of an access-token depends on the use-case -- based on the preferred lifecycle, one may choose different storage options. The oauth-client anticipates two forms of storage:

  • System token (OAuthSysToken): Some access-tokens are used by an automated agent of the system when executing background jobs. (Example: Periodically polling a system mailbox for email-bounces.) These are system tokens. These tokens are stored in the database as first-order records, and they are not strictly attached to one CiviCRM user.
  • User token (OAuthUserToken): Some access-tokens are only needed for a brief period -- while the user interacts with the application. (Example: Importing a spreadsheet from Google Docs.) Each token should be strictly associated with one user. After the user's momentary interaction, the token can (and probably should) be forgotten. These tokens may be stored in an ephemeral location, such as the user's session.

At time of writing, only OAuthSysToken is implemented. OAuthUserToken is in backlog/patch-welcome.

Key properties of a token record:

Property Type Description
client_id int Reference to the OAuthClient which obtained the token.
grant_type string The procedure used to request this token. Ex: authorization_code, client_credentials, password
scopes array List of scopes that were requested for this token. Some providers may emit a confirmation about the approved scopes - in which case this is updated.
token_type string Indicates how the token may be used for sending requests. Typically, Bearer.
access_token string A long, opaque value used for accessing resources (via HTTP/REST/IMAP/etc).
expires int The expiration time of the access_token.
refresh_token string A long, opaque value used for refreshing the access_token.
resource_owner_name string An optional symbolic identifier of the person/agent who authorized access. Semantics vary by provider.
resource_owner array Any information we have about the person/agent who authorized access. Structure varies by provider.
id int The unique, internal "ID" assigned by MySQL. This is typically a short number.

Tutorial

To develop a new integration that relies on a remote OAuth2 service provider, you will need to go through a few steps:

Define a provider

For any remote service provider that you wish to interact with, there should be an OAuthProvider record. For example, at time of writing, the ms-exchange provider is defined by a JSON file which specifies:

// FILE: ext/oauth-client/providers/ms-exchange.dist.json
{
  "title": "Microsoft Exchange Online",
  "class": "Civi\\OAuth\\CiviGenericProvider",
  "options": {
    "urlAuthorize": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
    "urlAccessToken": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
    "urlResourceOwnerDetails": "{{use_id_token}}",
    "scopeSeparator": " ",
    "scopes": [
      "https://outlook.office.com/IMAP.AccessAsUser.All",
      "https://outlook.office.com/POP.AccessAsUser.All",
      "https://outlook.office.com/SMTP.Send",
      "openid",
      "email",
      "offline_access"
    ]
  }
}

Here are a few ways to define a provider:

  • To add core support for a provider: Create a JSON file in [civicrm.root]/ext/oauth-client/providers/*.json
  • To temporarily add a provider on the local system: Create a JSON file in [civicrm.private]/oauth-providers/*.json
  • To add a provider to an extension: Implement hook_civicrm_oauthProviders

After adding or modifying a provider, clear the cache (cv flush).

You can inspect the list of available providers via APIv4 (OAuthProvider), e.g.

cv api4 OAuthProvider.get -T +s name,title
+-------------+----------------------------+
| name        | title                      |
+-------------+----------------------------+
| gmail       | Google Mail                |
| ms-exchange | Microsoft Exchange Online  |
| demo        | Local Demo                 |
+-------------+----------------------------+

Create a client

As presented in CiviCRM System Administrator Guide: Setup: OAuth, there is a generic administrative interface for registering clients. Alternatively, if you wish to register the client programmatically, then use APIv4's OAuthClient.create:

$client = civicrm_api4('OAuthClient', 'create', [
  'provider' => 'NAME',
  'guid' => 'CLIENT_ID',
  'secret' => 'CLIENT_SECRET',
])->single();

Grant access

After registering the client and provider, the client should request access to specific resources. RFC 6749 defines multiple ways to request access:

  • Get access to a resource on behalf of a user using a web-based authorization form. If authorized, this yields an "authorization-code". This is the "authorization-code grant-type".
  • Get access to a resource on behalf of a user using a username/password. This is the "user-password grant-type".
  • Get access to a resource on behalf of the non-human client-agent. This only requires the client_id and client_secret registered earlier. This is a "client credentials grant-type".

In all three cases, the ultimate goal is to retrieve and store an access token (and, most likely, a corresponding refresh token).

Many OAuth providers regard the "authorization code grant" as the more secure paradigm -- and they design policies to encourage use of "authorization code grant". For example, some APIs may only be available with "authorization code grant". Of course, if "client credentials grant" or "password grant" are accepted, then they may allow a more fluid user-experience or simpler process-automation. Ultimately, you will have to consult the provider documentation to determine the appropriate grant-type.

Once you've determined the appropriate grant type, you can use the corresponding API action:

Grant access via authorization code
## Begin authorizationCode grant via PHP
$start = civicrm_api4('OAuthClient', 'authorizationCode', [
  'where' => [['id', '=', 123]],
  'landingUrl' => CRM_Utils_System::url('civicrm/page/to/come/back/to', NULL, TRUE, NULL, FALSE),
])->single();
CRM_Utils_System::redirect($start['url']);
## Alternatively, begin authorizationCode grant via CLI
cv api4 OAuthClient.authorizationCode +w id=123

In the most well-known OAuth2 flow, one directs the user's browser to visit a remote web-service. They will confirm that they wish to grant permission - and then redirect back to CiviCRM.

Note that the API call does not immediately produce a valid token. Instead, it returns $start['url'] -- you need to redirect the user to this URL.

After they confirm access, the resulting token is stored for usage. (By default, it is stored in OAuthSysToken.)

Additionally, you may define more steps by:

Grant access via username/password
## Perform userPassword grant via PHP
$token = civicrm_api4('OAuthClient', 'userPassword', [
  'where' => [['id', '=', 123]],
  'username' => 'johndoe',
  'password' => 'abcd1234',
])->single();
## Alternatively, perform userPassword grant via CLI
cv api4 OAuthClient.userPassword +w id=123 username=johndoe password=abcd1234

As with "client credentials", this requires access to a specific OAuthClient. Additionally, it requires a username and password for some user.

If successful, the resulting token is stored for usage. (By default, it is stored in OAuthSysToken.)

Grant access via client credentials
## Perform clientCredentials grant via PHP
$token = civicrm_api4('OAuthClient', 'clientCredentials', [
  'where' => [['id', '=', 123]],
])->single();
## Alternatively, perform clientCredentials via CLI
cv api4 OAuthClient.clientCredentials +w id=123

Technically, this flow requires access to the client_id (guid) and client_secret (secret). Those values are loaded from the matching client (#123). This is the simplest form of authentication because it does not require any extra proof to get access.

If successful, the resulting token is stored for usage. (By default, it is stored in OAuthSysToken.)

In each grant-type, there are a few extra options that can be mixed in:

Assign a tag to the token

In each of the above examples, we request a token and store it. But how will we lookup the token in the future?

One simple mechanism is to assign a tag to the token, e.g.

$result = civicrm_api4('OAuthClient', '...grantType...', [
  ...
  'tag' => 'foobar',
  ...
])->single();

In the future, when you need access to the token, you can locate it by tag, e.g.

$tokens = civicrm_api4('OAuthSysToken', 'get', [
  'where' => [['tag', '=', 'foobar']]
]);

This is useful for some simple cases -- e.g. where a token only has a single use. If a token may be re-used in multiple ways, then tagging may not be sufficient -- as an alternative, consider storing your own foreign-key reference as-needed.

Choose the OAuthClient by type

In each of the above examples, the access was granted on behalf of a specific OAuthClient (#123):

$result = civicrm_api4('OAuthClient', '...grantType...', [
  'where' => [['id', '=', 123]],
])->single();

But how do you know to use #123? Perhaps the user chose #123 from a list? Or perhaps there was a setting?

Of course, if there is only one plausible answer (ie there's a 1:1 relationship between your OAuthProvider and your OAuthClient), then we don't have to bother with that question. Instead, lookup the OAuthClient by type:

$result = civicrm_api4('OAuthClient', '...grantType...', [
  'where' => [['provider', '=', 'gmail']],
  'orderBy' => ['id' => 'DESC'],
  'limit' => 1,
])->single();
Choose specific scopes

What do you request access to?

In each of the above examples, it uses the list of scopes registered for this provider. However, you may request access using a more fine-tuned list:

$result = civicrm_api4('OAuthClient', '...grantType...', [
  ...
  'scopes' => ['openid', 'read_timeline', 'add_comment'],
  ...
])->single();

Use a token

Once access has been granted, there is a stored token record (e.g. OAuthSysToken or OAuthUserToken). To use the token, you should first look it up via APIv4.

The conventional way to load a record in APIv4 is with the action get. This is useful for inspecting the current status of the token. However, OAuth tokens have an expiration-time built-in, and get may sometimes return a stale token. The alternative action refresh is very similar to get -- but it adds support for automatic refreshing.

Lookup token by ID (no refresh)
$tokenRecord = civicrm_api4('OAuthSysToken', 'get', [
  'checkPermissions' => FALSE,
  'where' => [['id', '=', '123']]
])->single();
Lookup token by tag (no refresh)
$tokenRecord = civicrm_api4('OAuthSysToken', 'get', [
  'checkPermissions' => FALSE,
  'where' => [['tag', '=', 'foobar']]
])->single();
Lookup token by ID (auto refresh)
$tokenRecord = civicrm_api4('OAuthSysToken', 'refresh', [
  'checkPermissions' => FALSE,
  'where' => [['id', '=', '123']]
])->single();
Lookup token by tag (auto refresh)
$tokenRecord = civicrm_api4('OAuthSysToken', 'refresh', [
  'checkPermissions' => FALSE,
  'where' => [['tag', '=', 'foobar']]
])->single();

By default, refresh is lazy -- it only refreshes the token if it has expired (or if it will expire within 60 seconds). You can optionally tune this behavior.

Auto-refresh with 5 minute threshold

Look up a token. Automatically refresh if it has less than 5 minutes of validity:

$tokenRecord = civicrm_api4('OAuthSysToken', 'refresh', [
  'checkPermissions' => FALSE,
  'where' => [['id', '=', '123']]
  'threshold' => 5 * 60,
])->single();
Auto-refresh -- no matter what

Look up a token. Automatically refresh, regardless of whether it has expired.

$tokenRecord = civicrm_api4('OAuthSysToken', 'refresh', [
  'checkPermissions' => FALSE,
  'where' => [['id', '=', '123']]
  'threshold' => -1,
])->single();

When you have a reference to a fresh $tokenRecord, you can use $tokenRecord['access_token'] with your favorite client library, such as guzzlehttp/guzzle or league/oauth2-client.

Use a token with Guzzle HTTP

In this example, we create a new instance of GuzzleHttp\Client.

$client = new GuzzleHttp\Client([
  'base_uri' => 'https://openidconnect.googleapis.com/v1/',
  'headers' => [
    'Authorization' => 'Bearer ' . $tokenRecord['access_token'],
    'Accept' => 'application/json',
  ],
]);
echo $client->get('userinfo')->getBody();

The $client has various defaults (such as base_uri and the Authorization header) to ensure that any calls to get(), post(), request(), etc are suitably authenticated.

Use a token with the PHP League provider object

If you recall from Model: Provider, CiviCRM's OAuth is based on the PHP League's oauth2-client. In their model, one may use a generic provider class -- or a specialized provider class. These classes may define extra helper methods for interacting with the provider.

Given a stored token, you may instantiate the corresponding $provider and $accessToken objects:

$lg = \Civi::service('oauth2.league')->create($tokenRecord);
$request = $lg['provider']->getAuthenticatedRequest('GET', 'https://service.example.com/resource',
    $lg['token']
);

Putting these together, we can make a more complete example scren:

Lookup token for Gmail and request user info
// Get a reference to the most recent 'gmail' token.
$tokenRecord = civicrm_api4('OAuthSysToken', 'refresh', [
  'checkPermissions' => FALSE,
  'where' => [['client.provider', '=', 'gmail']],
  'orderBy' => ['id' => 'DESC'],
  'limit' => 1,
])->single();

// Create a Guzzle client using this token
$client = new GuzzleHttp\Client([
  'base_uri' => 'https://openidconnect.googleapis.com/v1/',
  'headers' => [
    'Authorization' => 'Bearer ' . $tokenRecord['access_token'],
    'Accept'        => 'application/json',
  ],
]);

// Make specific request with the token.
echo $client->get('userinfo')->getBody();