Skip to content

Authentication

Overview

When visiting a web-page or requesting an HTTP service, one must often authenticate to prove the identity of the person (or agent) making the request. There are multiple techniques for authentication.

Background

Identity

Identities in CiviCRM are defined in two ways:

  • CMS User
  • CiviCRM Contact

Most CMS users have a corresponding CiviCRM contact record; however, the relationship is not a strict 1-1. Here are a few permutations to consider:

CMS User CiviCRM Contact Example
Defined Defined A staff member logs in and uses CiviCRM administration UI. (Note: The link between user<=>contact is stored in civicrm_uf_match.)
Defined Not defined A new CMS user has been created. However, they have never logged in. The CMS and Civi records have not been synchronized yet.
Not defined Not defined An anonymous person browses brochure information.
Not defined Defined A contituent receives a targeted email and opens a link to update their profile.

When a CMS user has been authenticated, this will be flagged using the CMS's preferred API. (Thus, on D7, global $account refers to the authenticated user. On WP, wp_get_current_user() refers to the authenticated user.)

When a CiviCRM contact has been authenticated, this will be flagged in CRM_Core_Session as the userID.

Browser Authentication

CiviCRM is a browser-based application that integrates with other browser-based applications (Drupal, WordPress, Joomla, Backdrop, etc).

The most familiar authentication technique is to login through the web-browser. For example, the user may navigate to Drupal's /user/login or WordPress's /wp-admin/, fill in credentials, and initialize an authenticated session.

Subsequently, the user may request any CiviCRM web-page, and it recognizes their identity.

In this way, CiviCRM can be chameleon - adapting to whichever web-based authentication process is available on the local deployment.

Service Authentication

CiviCRM also acts as a web-service provider. It offers APIv3 REST and APIv4 REST to remote applications (mobile apps, enterprise integrations, and so on). The core extension "Authx" (v5.36+) defines a portable protocol for CiviCRM service authentication.

Key features:

  • Support consistent authentication protocols, regardless of the CMS (Drupal, WordPress, Joomla, Backdrop)
  • Integrate with CMS authentication, ensuring that Civi contact records are matched with CMS user records
  • Accept multiple types of credentials (username/password, API key, JSON Web Token)
  • Accept credentials through different data-flows (HTTP headers, HTTP parameters, login forms)

The remainder of this chapter explores portable authentication options for web services.

Credentials

TIP: The site administrator may enable or disable support for certain types of credentials. See also: Settings

To authenticate, one presents a credential to prove their identity. Here are a few common types of credentials:

  • Username/Password: The most familiar type of credential, consisting of a username and a secret password.
  • API Key: A unique, random code assigned for this person or agent. It may be used indefinitely -- until someone deletes/revokes/replaces it. (See also: Sysadmin Guide: Setup: API Keys)
  • JSON Web Token (JWT): A dynamic, digitally-signed token which identifies this person. A JWT does not require any persistent storage, and it will expire automatically.

    How do you generate a JSON web token?

    If you are developing a patch or extension for CiviCRM, then you may generate a sign-in token as follows:

    $token = Civi::service('crypto.jwt')->encode([
      'exp' => time() + 5*60,       // Expires in 5 minutes
      'sub' => 'cid:203',           // Subject (contact ID)
      'scope' => 'authx',           // Allow general authentication
    ]);
    

    This example $token will be valid for login during the next five minutes.

Flows

TIP: The site administrator may enable or disable support for certain flows. See also: Settings

There are two general dataflows for HTTP authentication:

  • Stateless / Ephemeral: The client submits a singular HTTP request (such as an API call) which includes credentials. The request is authenticated and processed, and then it is forgotten.
  • Stateful / Persistent / Session: The client makes a request for a persistent session, attaching the contact ID and/or user ID. These will be used in subsequent requests.

For each general flow, we have a few variations.

Flow Name Description
Stateless Common Header
(header)
The credential is submitted with an HTTP header (Authorization: <credential>).
Stateless X-Header
(xheader)
The credential is submitted with a custom HTTP header (X-Civi-Auth: <credential>). This header is otherwise identical to Authorization:.
Stateless Parameter
(param)
The credential is submitted with an HTTP GET or POST parameter (?_authx=<credential>)
Stateful Login session (login) The client explicitly logs in (POST /civicrm/authx/login) which creates a session and cookie.
Stateful Auto session (auto) The clients submits a GET request for any page (?_authx=<credential>&_authxSes=1). The session is initialized. The user redirects to original page.
What is <credential>?

In all cases, the <credential> is formatted per RFC-7617 ("Basic") or RFC-6750 ("Bearer"). For example:

  • Common Header: Authorization: Basic dXNlcjpwYXNz or Authorization: Bearer ZYXW9876
  • X-Header: X-Civi-Auth: Basic dXNlcjpwYXNz or X-Civi-Auth: Bearer ZYXW9876
  • Parameter: ?_authx=Basic+dXNlcjpwYXNz or ?_authx=Bearer+ZYXW9876
What's the difference between Common Header and X-Header?

The common Authorization: header is easier to integrate with external applications. However, this could be a double-edged sword - some environments may have extra or conflicted rules in how to handle Authorization:.

The custom header X-Civi-Auth: may require a little extra work for the downstream application. However, it is likely to bypass any middleware conflicts.

How do I test authentication?

You may send a request to /civicrm/authx/id, e.g.

curl -v 'https://example.com/civicrm/authx/id?_authx=Bearer+ZYXW9876'

If successful, it will respond with a JSON document describing the authenticated user:

> HTTP/1.1 200 OK
> Content-Type: application/json

{"contact_id":"202","user_id":"1","flow":"param","cred":"bearer"}
How do I manage an explicit session?

To login, submit the credential to /civicrm/authx/login. For example, this will login with username and password::

curl -v -X POST 'http://example.com/civicrm/authx/login' -d '_authx=Basic+dXNlcjpwYXNz'

If successful, it will respond with a session cookie and a summary of the logged-in user:

> HTTP/1.1 200 OK
> Set-Cookie: SESS1234=abcd1234; expires=Sat, 08-May-2021 12:28:19 GMT; Max-Age=2000000; path=/; domain=.example.com; HttpOnly
> Content-Type: application/json

{"contact_id":"202","user_id":"1","flow":"login","cred":"pass"}

You may now request any HTTP resources. Simply pass the Cookie in each HTTP request.

To logout, POST a request to /civicrm/authx/logout.

Guards

TIP: The site administrator may enable or disable support for certain types of guards. See also: Settings

A guard limits access to AuthX authentication services. By default, a user who presents a password or API key will only be authenticated if they can pass one of these guards:

  • Permission: Check if the user has permission authenticate with password or authenticate with api key.
  • Site Key: Check if the user knows the secret site key.
Why support both guards?

Traditional dataflows using APIv3 REST required the site-key as a guard. However, this is difficult to administer. The permission-guard is a more manageable alternative. Of course, we do not want to break backward-compatibility.

Do guards really prevent misuse?

Sort of.

Strictly, no. Regardless of how the user authenticates, the user will end up with the same permissions, so they can access the same records/actions/APIs/screens. Whether the user authenticates through a CMS login-form or AuthX, the upper-bound of malicious behavior is the same.

But there are still reasons why an administrator might limit access to AuthX, e.g.

  • They only wish to allow skilled/administrative users to make connections with off-the-shelf applications.
  • They wish to impose tighter rules (e.g. stricter password requirements) on users who have AuthX access.

Settings

For each authentication flow, one may toggle support for different credentials and user-links. Here is the default configuration:

Setting (Default) Description
authx_guards
(['site_key', 'perm'])
List of guards that permit authentication. If blank, then no guards are required.
authx_param_cred
(['jwt'])
List of credential-types that will be accepted via ?_authx parameters
authx_param_user
(optional)
Is it important to load a CMS user? (require, optional, ignore)
authx_header_cred
(['jwt'])
List of credential-types that will be accepted via Authorization: header
authx_header_user
(optional)
Is it important to load a CMS user? (require, optional, ignore)
authx_xheader_cred
(['jwt'])
List of credential-types that will be accepted via X-Civi-Auth: header
authx_xheader_user
(optional)
Is it important to load a CMS user? (require, optional, ignore)
authx_login_cred
(['jwt'])
List of credential-types that will be accepted on POST /civicrm/authx/login
authx_login_user
(require)
Is it important to load a CMS user? (require, optional, ignore)
authx_auto_cred
(['jwt'])
List of credential-types that will be accepted via ?_authx=...&_authxSes=1 parameters
authx_auto_user
(require)
Is it important to load a CMS user? (require, optional, ignore)
Example: Relax all settings to permit testing

The default settings are fairly restrictive. If you will be doing testing or development, then you might relax a number of settings:

cv ev 'Civi::settings()->set("authx_guards", []);'
cv ev 'Civi::settings()->set("authx_param_cred", ["jwt", "api_key", "pass"]);'
cv ev 'Civi::settings()->set("authx_header_cred", ["jwt", "api_key", "pass"]);'
cv ev 'Civi::settings()->set("authx_xheader_cred", ["jwt", "api_key", "pass"]);'
cv ev 'Civi::settings()->set("authx_login_cred", ["jwt", "api_key", "pass"]);'
cv ev 'Civi::settings()->set("authx_auto_cred", ["jwt", "api_key", "pass"]);'