Skip to content

OAuth Reference

This page discusses support for CiviCRM as an OAuth2 client. This was 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, OAuthSysToken, OAuthSessionToken and OAuthContactToken). 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
options . tenancy bool true if the provider URLs require a tenant ID to be passed in, using the {{tenant}} token n/a
mailSettingsTemplate array If the provider is specifically used for registering email accounts (MailSettings), then you can define a template for the MailSettings.
contactTemplate array If the provider is used for creating contacts, you must define a template for new contacts.

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
tenant string (Optional) The tenancy or organisation ID assigned by the remote provider to dedicated services NULL
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 provides three 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.
  • Contact token (OAuthContactToken): Some access-tokens are associated with a particular CiviCRM contact, and need to be stored so they can be used at a later time, perhaps repeatedly. (Example: Periodically updating a contact's photo by fetching it from their account on another site.) These tokens are stored in the database as first-order records.
  • Session token (OAuthSessionToken): 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 is strictly associated with one logged-in user's interaction with CiviCRM (see notes below regarding anonymous users). After the user's momentary interaction, the token can (and probably should) be forgotten.

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.
tag string Optional, freeform string which may be used to describe, categorize or identify a token. When tag is set in a call to OAuthClient.authorizationCode, any token that is created as a result will have the tag set accordingly. Some tag values have special meaning when used with the OAuthContactToken storage type (see Contact tokens).
contact_id int (OAuthContactToken only) Foreign key to the civicrm_contact table.
cardinal int (OAuthSessionToken only) Order in which the token was created in the context of the session. It differs from an "ID" as used most places in CiviCRM, in that "IDs" are typically unique over the lifespan of the CiviCRM instance, whereas cardinal is unique only over the lifespan of the session.
id int (OAuthSysToken and OAuthContactToken only) The unique, internal "ID" assigned by MySQL. This is typically a short number.

Session Tokens

These tokens are stored in the user's session. If the user is logged in, tokens are destroyed when the user logs out, ending the session. If the tokens are created in the context of an anonymous (non-login) session, they will remain associated with the web browser's interactions with CiviCRM unless they are destroyed (via the APIv4 delete action) or the session expires. Either way, you are strongly advised to carefully manage the lifecycle of the OAuthSessionTokens you create, doing proactive garbage collection and/or obtaining tokens with a short time to live.

Contact Tokens

A contact can have any number of Contact Tokens attached to it via the contact_id foreign key on the tokens.

When you use the API4 OAuthClient authorizationCode action to initiate an authorization code flow and specify OAuthContactToken as the storage type, you can include instructions for how to link the resulting token to a contact.

  • If you set tag to nullContactId, the resulting token's contact_id will be assigned null.
  • If you set tag to createContact, Civi will create a new contact and link the token to it. In this case, you must specify a contactTemplate when you define your Provider so that Civi knows what to put in the new contact record.
  • If you set tag to "linkContact:" followed by a valid existing contact ID (e.g. linkContact:1234), Civi will link the resulting token to that contact.
  • If tag is none of the above, contact_id will be set to the contact ID of the currently logged-in user. If the session is anonymous (not logged in), the contact ID will be null.

Permissions

The following CiviCRM permissions pertain to OAuth:

Permission Description and notes
manage OAuth client Create and delete OAuth client connections, including the client secret.
manage OAuth client secrets Access the access_token and refresh_token fields of System Tokens. Despite the name, this permission does not currently affect a user's ability to view/edit the secret field of an OAuthClient.
create OAuth tokens via auth code flow Create OAuth tokens (of any storage type) via the authorization code flow. Some use cases may call for this permission to be given to anonymous users — these use cases most likely involve Contact Tokens or Session Tokens. Other OAuth-related permissions should not be given to anonymous users.
manage my OAuth contact tokens A logged in user who has this permission can use API4 to create Contact Tokens associated with their own contact ID, and read/update/delete those tokens if they have at least view access to their own contact record. Other contacts' OAuthContactToken records are invisible to the user.
manage all OAuth contact tokens Manage OAuth Contact Tokens for all contacts via API4.

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/{{tenant}}/oauth2/v2.0/authorize",
    "urlAccessToken": "https://login.microsoftonline.com/{{tenant}}/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"
    ],
    "tenancy": true
  }
}

Dedicated Microsoft services require a tenant ID, which can be input when registering the OAuth client entity (see above). CiviCRM will automatically replace the {{tenant}} token with the provided ID, or 'common' where none has been set (ie: a consumer account).

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 (OAuthSysToken, OAuthSessionToken or OAuthContactToken). 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. As of this writing, only OAuthSysTokens support the refresh action.

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 screen:

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();