How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Authenticate a server-to-server integration with an API key

For an integration that uses an API key, the older option: the server calls the API with Authorization: Bearer and a key belonging to a backend user made for the integration. Choose that user's permissions carefully, limit the key by IP address, keep it in one module, and read the 403's message when it fails. A new integration uses OAuth instead.

Context

Context. API keys are the older way for a server to call a Core dna site, and they still work: many sync jobs, headless front ends and CI tasks use one. A new integration should use OAuth instead, as in Connect a server integration with OAuth and keep its token fresh; this how-to is for setting up, checking and rotating a key where one is used. The key goes in Authorization: Bearer <key> on every request and acts as the backend user it belongs to, with that user's permissions, so the security of the integration rests on choosing that user well. Keys do not expire; they work until deleted. With the header present, the CSRF check does not apply and no session is involved.

Steps

  1. In the admin, create a backend user for the integration, named for it ("ERP sync"), with only the permissions it needs.
  2. On that user's record, add an API key under API Keys. If the server has fixed IP addresses, list them on the key.
  3. Store the key in the server's secret store or environment. Never in the repository, a front-end bundle or a job's fields.
  4. Send it as Authorization: Bearer <key>, exactly: the scheme word is case-sensitive, bearer is refused.
  5. Test once with the key and check the response, not just the status: a 403 names the reason in message.
  6. To rotate, add a second key, deploy it, wait for the old key's last used date to stop moving, then delete the old key.

Code

Shell
# check the key from the server that will use it
curl -s -H "Authorization: Bearer $COREDNA_API_KEY" \
  "$COREDNA_URL/cdna-api/users/account?properties=%7B%20id,%20username%20%7D"
JavaScript
// coredna.js: the one module that holds the key (Node 18 or later)
const SITE = process.env.COREDNA_URL;       // https://www.example.com
const KEY  = process.env.COREDNA_API_KEY;
if (!SITE || !KEY) throw new Error('COREDNA_URL and COREDNA_API_KEY must be set');

export async function coredna(method, path, { query, body } = {}) {
  const url = new URL(SITE + '/cdna-api/' + path);
  for (const [k, v] of Object.entries(query ?? {})) url.searchParams.set(k, v);
  const response = await fetch(url, {
    method,
    headers: {
      Authorization: 'Bearer ' + KEY,
      Accept: 'application/json',
      ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
    },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  const data = await response.json().catch(() => ({}));
  if (!response.ok) {
    throw new Error(method + ' ' + path + ': ' + response.status + ' ' + (data.message ?? response.headers.get('x-reason') ?? ''));
  }
  return data;
}

What the failures look like, so the log line says which one it was:

ResponseMeaning
401, x-reason: not logged inNo Authorization header reached the site: a proxy stripped it, or the variable is empty
403, Authentication failedThe key is wrong or deleted
403, IP … not permitted for this API keyThe request came from an address not on the key's list
403, Authorization method not supportedThe scheme is not exactly Bearer
403 on one endpoint onlyThe integration user lacks permission for that module

Why this and not that

Not a person's account: when they leave, the integration breaks, and until then it can do everything they can. Not Basic credentials: a password in a job's configuration is a password in its logs and its history, and it cannot be restricted by IP address. Not a key for a new integration: OAuth is preferred, because the token it sends expires in four hours where a key works until someone deletes it; keep keys for the integrations that already use them, and move each to OAuth when it is next reworked. And not the same key for development and production: give each environment its own user and key, so a test run can never write to production.

Go deeper

Was this page helpful?

Your answer goes to the docs team.