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¶
Setup¶
The CiviCRM installer stores randomly-generated private keys in civicrm.settings.php
. On older sites that predated this install step, the keys might be missing, resulting in an error message like:
Failed to find key by ID or tag (SIGN)
To fix this, copy and past the code generated by this key generater helper into your civicrm.settings.php
file.
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> ) |
Stateless | Legacy REST ( legacyrest ) |
The credential is submitted with a pair of HTTP parameters (?key=MY_SITE_KEY&api_key=MY_API_KEY ). This is only valid for the route civicrm/ajax/rest . |
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
orAuthorization: Bearer ZYXW9876
- X-Header:
X-Civi-Auth: Basic dXNlcjpwYXNz
orX-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:
How can I send the site key?
The site key can be passed with either:
- X-Header:
X-Civi-Key: 1234abc
- Parameter:
?_authxSiteKey=1234abc
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 | Version |
---|---|---|
authx_guards ( ['site_key', 'perm'] ) |
List of guards that permit authentication. If blank, then no guards are required. | 5.36 |
authx_param_cred ( ['jwt'] ) |
List of credential-types that will be accepted via ?_authx parameters |
5.36 |
authx_param_user ( optional ) |
Is it important to load a CMS user? (require , optional , ignore ) |
5.36 |
authx_header_cred ( ['jwt'] ) |
List of credential-types that will be accepted via Authorization: header |
5.36 |
authx_header_user ( optional ) |
Is it important to load a CMS user? (require , optional , ignore ) |
5.36 |
authx_xheader_cred ( ['jwt'] ) |
List of credential-types that will be accepted via X-Civi-Auth: header |
5.36 |
authx_xheader_user ( optional ) |
Is it important to load a CMS user? (require , optional , ignore ) |
5.36 |
authx_legacyrest_cred ( ['jwt', 'api_key'] ) |
List of credential-types that will be accepted via ?key=&api_key= parameters |
5.47 |
authx_legacyrest_user ( require ) |
Is it important to load a CMS user? (require , optional , ignore ) |
5.47 |
authx_login_cred ( ['jwt'] ) |
List of credential-types that will be accepted on POST /civicrm/authx/login |
5.36 |
authx_login_user ( require ) |
Is it important to load a CMS user? (require , optional , ignore ) |
5.36 |
authx_auto_cred ( ['jwt'] ) |
List of credential-types that will be accepted via ?_authx=...&_authxSes=1 parameters |
5.36 |
authx_auto_user ( require ) |
Is it important to load a CMS user? (require , optional , ignore ) |
5.36 |
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"]);'