> ## Documentation Index
> Fetch the complete documentation index at: https://docs.curator.interworks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> API authentication methods and user verification processes for external applications integrating with Curator.

## Introduction

Occasionally, various external applications need to rely on Curator to authenticate users for them.
Curator provides a simple interface to determine which user is currently authenticated to Curator,
and provide information to your external application about that user.

This is particularly helpful for applications that need to keep user authentication in sync with Curator,
such as custom applications embedded within Curator.

Retrieving information about the currently authenticated user requires two API calls to Curator:

1. First, your application must forward the user to Curator's /fetchUser endpoint,
   with a `redirect_url` parameter. Curator returns the user to the redirect\_url with a `payload`
   GET parameter containing a JSON wad containing an identifier token.
2. Next, use the `token` value from this JSON wad to call Curator's `/user/getUser` endpoint to retrieve the user's information.

## Important Setup

In order for the redirect to work, you must whitelist your domain in the Curator Portal Settings.
Navigate to the **Settings** > **Curator** > **Portal Settings** section from the left-hand menu.
Under the **General** tab, expand the **Security** section.

### Whitelisted Redirect Hosts

The **Whitelisted Redirect Hosts** setting lists the external hosts Curator is allowed to send users to.
Curator compares the target host against that list before issuing a redirect, and refuses any host the
list does not cover.

Each row of the repeater holds one **Host** value, entered as the host portion of the URL, for example
`https://www.example.com`. Add a row for every domain you send users back to.

> [!WARNING]
>
> Delete any repeater row you are not using. A saved row with a blank **Host** permits every host, which
> turns the whitelist off.

Curator permits some hosts without any configuration:

* The host of the current request.
* The **Forced Curator Domain** setting, when one is set.
* The root URL Curator has stored for itself.
* `localhost` and `127.0.0.1`.
* The `APP_URL` value from the server environment. On-Prem administrators set this value, and InterWorks
  manages it on SaaS.
* Tableau Server URLs and their alternate URLs, from each active Tableau connection.
* `https://login.microsoftonline.com`, when Power BI is configured.
* The SAML sign-on, alternate sign-on, logout, and ACS URLs, when SAML authentication is in use.
* The OAuth domain, when OAuth authentication is in use. Curator also permits the identity provider's
  end-session endpoint while signing a user out.

Matching follows these rules:

* Both `http://` and `https://` are allowed for every row, so one row covers both protocols.
* The `www` form and the bare domain are paired in both directions. Adding `example.com` also permits
  `www.example.com`, and adding `www.example.com` also permits `example.com`.
* Subdomains are not included. `example.com` does not permit `reports.example.com`, which needs a row of
  its own.
* Only the host is compared. A port in a row is ignored, so `example.com` is permitted on any port.

A blocked redirect raises an error instead of sending the user on, and Curator records a
`Blocked redirect to <URL>` warning. Read it in the backend
[Event Log](https://docs.curator.interworks.com/site_administration/logging/database_logs.md#event-log), or on disk in the System Log at
`storage/logs/system-YYYY-MM-DD.log` *(On-Prem only)*. See
[Server Logs](https://docs.curator.interworks.com/site_administration/logging/file_based_logs.md) for the path on each operating system.

## /fetchUser

`HTTP REDIRECT [your_domain]/fetchUser?redirect_url=[group_name_here]`

## /user/getUser

Returns the currently logged in user's information.
`POST [your_domain]/api/v1/User/getUser?apikey=[your_api_key_here]&token=[token_here]`

**Returns:**
array

## Example Authentication Script

```PHP theme={null}
<?php
/**
 * This is a basic example of how to authenticate a user with Curator.
 *
 * From a flow perspective, this is what happens:
 *     1. The user is redirected to the Curator authentication system. (/fetchUser)
 *     2. The authenticated user is redirected back to this page with a JSON wad in the "payload" GET variable.
 *     3. The JSON wad contains a "token".
 *     4. The token is used against the Curator REST API to get the user's details.
 *
 * NOTE: VERY IMPORTANT:
 *     In order for the redirect to work, you must whitelist your domain in the Curator Portal Settings.
 *     Go to Settings->Curator->Portal Settings to add your domain. (Look under "security")
 *
 **/

/**
 * API Key: You can get this from the Curator backend.
 * Go to Settings->Curator->API Keys to retrieve and/or create a key.
 */
$api_key = 'YOUR_API_KEY';

/**
 * Curator Domain. This is the domain of your Curator instance.
 * For example: https://curator.example.com
 */
$curator_domain = 'YOUR_CURATOR_DOMAIN';

/**
 * This part is just a very basic session example.
 * You will likely want to customize it.
 */
session_start();

/**
 * If the user is returning from the Curator system, process their token.
 **/
if (!empty($_GET['payload'])) {
    processSessionPayload($_GET['payload'], $api_key, $curator_domain);

/**
 * Otherwise, if we need to start a NEW session, redirect to Curator.
 **/
} elseif (!isset($_SESSION['username'])) {
    // Where to return the user after the redirect.
    $returnURL = 'https://' . $_SERVER['HTTP_HOST'];

    // Redirect the unauthenticated user to the authentication system.
    header('Location: ' . $curator_domain . '/fetchUser?redirect_url=' . $returnURL);
    exit;
}

/**
 * If the user is authenticated, show them their information.
 **/
echo 'Hello, ' . $_SESSION['display_name'] . '!</br><pre>';
print_r($_SESSION);
echo '</pre>';

/**
 * Exchange the redirect session payload for user information.
 *
 * @param string $payload The JSON wad from the redirect.
 * @param string $api_key The API key for the Curator instance.
 * @param string $curator_domain The domain of the Curator instance.
 *
 * @return void
 */
function processSessionPayload($payload, $api_key, $curator_domain)
{
    $payload = json_decode($payload);
    if (isset($payload->token)) {
        // This is a 1-time use token returned from the /fetchUser endpoint.
        $token = $payload->token;

        // This is the REST API endpoint to exchange the token for user information.
        $url = $curator_domain . '/api/v1/user/getUser?apikey=' . $api_key . '&token=' . $token;

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        curl_close($ch);

        $response = json_decode($response);
        $user = $response->user;

        /**
         * Uncomment the code below to see the user information available.
         *
         * echo "<pre>";
         * print_r($user);
         * echo "</pre>";
        */

        // Store the user information in the session.
        $_SESSION['username']     = $user->name;
        $_SESSION['email']        = $user->email;
        $_SESSION['display_name'] = $user->full_name;

        // The "SAML Attributes" are stored in the "custom_attributes" field.
        if (!empty($user->custom_attributes)) {
            $_SESSION['custom_attributes'] = $user->custom_attributes;
        }
    }
}
```

## Managing API keys

API keys are managed in the backend under **Settings** > **Curator** > **API Keys**.

The list shows each key's owner, the last four characters of the key, its access, who created it, when it was
created, and when it expires. The full key never appears in the list.

### Create a key

1. Click **New API Key**.
2. Enter an **Owner** that says who or what uses the key.
3. Choose when the key expires in **Key expires**.
4. Click **Create**.

Curator generates the key itself when you create it. The edit page opens next, where you can show or copy the key.

### Show or copy a key

The edit page shows the key masked, with only its last four characters visible. Click **Show** to see the full key
and **Hide** to mask it again. Click **Copy** to put the full key on your clipboard without showing it. The key is
masked again whenever you leave the page.

In the **Try the API** section, the example URL shows `[apikey]` in place of the key until you click **Show**.
**Copy URL** and **Open** always use the real key.

### Regenerate a key

Click **Regenerate key** on the edit page, type the key's owner name to confirm, and click **Regenerate key** in the
dialog. Curator replaces the key with a new one straight away. Anything still using the old key stops working, so
update those integrations with the new key.

### Key expiration

**Key expires** sets how long a key keeps working:

| Choice | Effect |
| - | - |
| **90 days** | The key expires 90 days after you save. This is the default for a new key made in the backend |
| **180 days** | The key expires 180 days after you save |
| **365 days** | The key expires 365 days after you save |
| **Never** | The key does not expire |

When you edit an existing key, **Key expires** starts on **Keep current expiration**, and the line below it shows
the current expiration date. Choosing a period sets a new date counted from the day you save, which is how you
extend a key.

Keys that existed before this setting was added never expire. Keys made outside the backend form, for example by the
`portal:addapikey` command or an import, never expire either.

A request made with an expired key is refused with HTTP `401` and this body:

```json theme={null}
{
    "result": "Failure",
    "msg": "API key expired"
}
```

## API Rate Limiting

Curator rate limits every API request. Requests that include an `apikey` parameter count against the limits for
API keys, and requests that do not send one, such as site search from signed-in users, count against a separate
limit for each client IP address. Requests without a key from a signed-in user are not limited.

When a request is refused, Curator responds with HTTP `429 Too Many Requests` and this body:

```json theme={null}
{
    "result": "Failure",
    "msg": "API rate limit exceeded. Please try again later."
}
```

The same message is returned no matter which limit was reached, so the response never reveals whether the
API key that was supplied is a valid one.

### Four limits apply

Curator checks the API key first. A request with a valid key must pass two counters. A request with an invalid
key is counted against a third, separate counter, and a request without a key against a fourth:

| Counter | What it counts |
| - | - |
| **Global** | Every request made with a valid API key, added together across all API keys |
| **Per-API key** | Only the requests made with one specific API key |
| **Invalid key** | Requests from one client IP address that send an API key that does not exist |
| **No key** | Requests from one client IP address that do not send an `apikey` parameter and come from no signed-in user |

The invalid key limit is 60 requests per minute for each client IP address, and it cannot be changed. Up to
that limit, a request with an invalid key gets HTTP `401` with an `Invalid API Key` message, or `API key expired`
when the key exists but has expired. Past it, the client
gets the same `429` response shown above until the minute has passed. Requests with an invalid key never count
toward the global or per-API key limits, so a caller sending bad keys cannot use up the allowance of valid ones.

The no key limit is 1000 requests per minute for each client IP address by default, and its one-minute window does
not follow the timeframe setting. Users behind one office network often share an IP address, so this limit is higher
than the one for API keys. Requests without a key never count toward the other limits, and requests with a key never
count toward this one.

> [!NOTE]
>
> The global counter is shared by the whole site rather than tracked separately per caller. A single busy
> integration with a valid key can therefore use up the allowance that every other API consumer depends on.

### Global settings

To customize the global limit, navigate to **Backend** > **Settings** > **Security** >
**Authentication Settings** > **Security & Access** tab > **API Rate Limits** section.

| Setting | Description | Default |
| - | - | - |
| **API Rate Limit - Max Calls** | How many API calls are allowed within the timeframe | 500 |
| **API Rate Limit - Timeframe (minutes)** | The length of the window used to track those calls | 1 |
| **API Rate Limit - Max Calls Per Minute Without an API Key** | How many calls per minute without an API key each client IP address may make | 1000 |

If everyone who is not signed in gets `429` at once, check that your proxy or load balancer forwards the client address.

With the default values, Curator allows 500 API calls per minute across the whole site.

### Per-API key override

An individual API key can use its own limits instead of the global ones. Navigate to **Settings** >
**Curator** > **API Keys**, edit a key, and find the **Rate limit** section:

* **Max calls**
* **Per (minutes)**

Leave either field blank and that key falls back to the matching global value. Because both counters are
enforced, the lower of the two limits is the one a caller reaches first. Raising a key's limit above the
global limit does not allow it to exceed the global allowance.

### When a counter resets

A counter resets once a full timeframe passes with no calls counted against it. Continuous traffic does not
roll the count back, so a caller that keeps making requests keeps accumulating until it reaches the limit.

Once a limit is reached, further calls are refused until the timeframe has elapsed since the limit was hit.

The no key counter works differently. It counts in fixed windows that start with a client's first request, and
each window starts again from zero, so steady traffic below the limit is never refused.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.