# API authentication

Control who calls your APIs and how your services authenticate to external systems.

Authentication controls who can access your APIs and how your services authenticate to external systems. You configure security definitions once in the Dashboard and attach them to [channels](https://zato.io/docs/dev/rest/channels.html) or [outgoing connections](https://zato.io/docs/dev/rest/outconns.html). One definition serves several purposes at once - it authenticates callers, selects their [quota tier](https://zato.io/docs/admin/guide/quota-tiers.html), signs them in to the [OpenAPI console](https://zato.io/docs/dev/rest/openapi-console.html) and identifies them in the per-consumer traffic analytics.

For systems that require [custom authentication](https://zato.io/docs/dev/rest/custom-auth.html) schemes, such as SAP or ServiceNow HMAC signatures or session-based flows, you can implement your own validation logic in service code.

## Available security types {#available-security-types}

The table below shows which security types can be used with [channels](https://zato.io/docs/dev/rest/channels.html) and [outgoing connections](https://zato.io/docs/dev/rest/outconns.html).

| Type | Channels | Outgoing | Use when |
| --- | --- | --- | --- |
| **Basic Auth** | Yes | Yes | Username/password authentication |
| **API key** | Yes | Yes | [API keys](https://zato.io/docs/security/api-keys.html) in headers |
| **Bearer token** | --- | Yes | OAuth 2.0 client credentials flow with automatic token refresh |
| **Groups** | Yes | --- | Multiple clients need access to the same channel |
| **NTLM** | --- | Yes | Calling Windows/Active Directory-protected APIs |
| **[mTLS](https://zato.io/docs/security/mtls/index.html)** | Yes | Yes | Client certificates - presenting one to remote APIs and authenticating API clients by theirs |
| **[Kerberos (SPNEGO)](https://zato.io/docs/security/kerberos/index.html)** | --- | Yes | Calling SPNEGO-protected APIs with keytab-based Kerberos credentials |

> **Note:** The same security definitions and groups also protect [MCP gateways](https://zato.io/docs/ai/mcp/index.html), where the clients are AI agents - how a credential maps to an agent's identity is described under [MCP gateway security](https://zato.io/docs/ai/mcp/security.html).

**Channels**

## Secure REST channels {#secure-rest-channels}

A channel with a security definition rejects requests without valid credentials before your service code runs. Create the definition first, then select it on the channel.

**Basic Auth**

To create a [Basic Auth](https://zato.io/docs/security/basic-auth.html) definition, go to **Security** > **Basic Auth** in the Dashboard, click **Create a new definition** and fill in the form:

1. **Name:** API Credentials
2. **Username:** api.client
3. **Password:** the password the client authenticates with
4. Click **OK**

![New Basic Auth definition](https://zatosource-production.b-cdn.net/docs/gfx/rest/security-basic-auth-create.webp?v=1790440706)

Dashboard menu: Security > Basic Auth

**API keys**

Each client application receives its own API key. Each definition also has its own HTTP header name, `X-API-Key` by default, and the client sends the key's value directly in that header.

To create an API key definition, go to **Security** > **API Keys**, click **Create a new definition** and fill in the form:

1. **Name:** Mobile Clients
2. **Header:** the HTTP header the key arrives in, `X-API-Key` by default
3. **API key:** the key the client sends
4. Click **OK**

![New API key definition](https://zatosource-production.b-cdn.net/docs/gfx/rest/security-apikey-create.webp?v=1790440707)

Dashboard menu: Security > API Keys

With the default header, a client call looks like this:

```bash
curl -H "X-API-Key: your-api-key" http://localhost:11223/api/v1/services/my-service
```

And with a custom header configured for the definition:

```bash
curl -H "X-Custom-Token: your-api-key" http://localhost:11223/api/v1/services/my-service
```

When API key definitions are members of [groups](https://zato.io/docs/dev/rest/security-groups.html), all API key definitions used by one channel's groups share one header name - different channels can use different headers.

### Assign security to a channel {#assign-security-to-a-channel}

Create or edit a [REST channel](https://zato.io/docs/dev/rest/channels.html) and select the security definition in its **Security** field. Once attached, Zato rejects requests without valid credentials before your service code runs.

![Channel with Basic Auth](https://zatosource-production.b-cdn.net/docs/gfx/rest/channel-security.webp?v=1791300195)

Dashboard menu: Connections > Channels > REST

If your service implements a [custom authentication](https://zato.io/docs/dev/rest/custom-auth.html) scheme itself, or the channel requires no security, select **No security definition** explicitly.

### API client groups {#api-client-groups}

When multiple clients need access to the same channel, organize their definitions into groups - each client keeps its own credentials and you revoke access for one client without changing the channel. To learn how groups work and how to create them, see [security groups](https://zato.io/docs/dev/rest/security-groups.html).

### Access security information in code {#access-security-information-in-code}

When a request arrives through a secured channel, `self.channel.security` holds the authenticated identity:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SecuredService(Service):
    """ Logs the authenticated caller of a secured channel.
    """
    name = 'demo.rest.secured-service'

    def handle(self) -> 'None':

        # The username of the caller the channel authenticated
        username = self.channel.security.username

        self.logger.info('Request from %s', username)
```

Calls with valid credentials reach the service and everything else is rejected:

```bash
# With Basic Auth credentials
curl -u api.client:api-password http://localhost:11223/api/secure/data

# With an API key in the header
curl -H "X-API-Key: your-api-key" http://localhost:11223/api/secure/data

# Without credentials - the channel returns 401
curl http://localhost:11223/api/secure/data
```

**Outgoing connections**

## Authenticate outgoing calls {#authenticate-outgoing-calls}

Create a Basic Auth, API key or [Bearer token](https://zato.io/docs/security/bearer-tokens/index.html) definition and select it in the outgoing connection's **Security** field. Zato adds the required headers to every request the connection makes, and with Bearer tokens it obtains and refreshes the OAuth tokens too.

### Basic Auth {#basic-auth}

Create a Basic Auth definition under **Security** > **Basic Auth**, entering the username and password the external API expects, and select the definition in your [outgoing REST connection](https://zato.io/docs/dev/rest/outconns.html).

### API key {#api-key}

For APIs that require an API key in an HTTP header, create an API key definition under **Security** > **API Keys** and select it in the connection. The header name is configurable per definition, `X-API-Key` by default.

### Bearer token {#bearer-token}

For APIs that use the OAuth 2.0 client credentials flow, create a Bearer token definition. Go to **Security** > **OAuth** > **Outgoing** > **Client Credentials**, click **Create a new definition** and fill in the form:

1. **Name:** Customer Identity
2. **Username:** your OAuth client ID
3. **Secret:** the client secret
4. **Auth server URL:** the token endpoint, e.g. `https://auth.example.com/oauth2/token`
5. Click **OK**

![New Bearer token definition](https://zatosource-production.b-cdn.net/docs/gfx/rest/security-bearer-token-create.webp?v=1790934258)

Dashboard menu: Security > Bearer tokens

The remaining fields:

| Field | Purpose |
| --- | --- |
| **Scopes** | Newline-separated list of OAuth scopes to request |
| **Client ID field** | Field name for the client ID in the token request (default: `client_id`) |
| **Client secret field** | Field name for the secret in the token request (default: `client_secret`) |
| **Grant type** | OAuth grant type (default: `client_credentials`) |
| **Extra fields** | Additional key=value pairs to include in token requests |
| **Data format** | Request format: JSON or Form data (most auth servers expect Form data) |

When your service makes an outgoing call through a connection with a Bearer token definition attached:

1. Zato checks its internal cache for a valid token
2. If no cached token exists, or it expired, Zato requests a new one from the auth server
3. The token is cached for half its validity period, so refreshes happen well before expiry
4. The `Authorization: Bearer <token>` header is added to your request

For example, if the auth server returns a token valid for 3600 seconds, Zato caches it for 1800 seconds - after 30 minutes, the next API call triggers a fresh token request.

You can also override the scopes for a single request through the `auth_scopes` parameter - without it, the scopes from the security definition are used:

```python
conn = self.rest['My API']
response = conn.get(auth_scopes='read:users write:users')
```

### Custom authentication {#custom-authentication}

For [custom authentication](https://zato.io/docs/dev/rest/custom-auth.html) schemes such as SAP HMAC signatures or session-based login/logout flows, pass custom headers directly to the connection.

## OpenAPI console access {#openapi-console-access}

Every security definition attached to REST channels doubles as a sign-in to the [OpenAPI console](https://zato.io/docs/dev/rest/openapi-console.html) - no separate console accounts exist:

| Definition type | Console username | Console password |
| --- | --- | --- |
| Basic Auth | The username | The password |
| API key | The definition's name | The key |
| Bearer token | The client ID | The client secret |

The console is deny by default. A signed-in caller receives an OpenAPI document containing only the channels their definition is assigned to, directly or through [group](https://zato.io/docs/dev/rest/security-groups.html) membership, and the try-it client enforces the same rules. Credentials matching no channel yield an empty document, so no endpoint the caller cannot invoke is ever disclosed.

## See also {#see-also}

- [Security groups](https://zato.io/docs/dev/rest/security-groups.html) - Many credentials on one channel and the 403 rule
- [Custom authentication](https://zato.io/docs/dev/rest/custom-auth.html) - HMAC signatures, proprietary tokens and session-based flows
- [Error catalog](https://zato.io/docs/dev/rest/error-catalog.html) - The 401 versus 403 rule and every other status a channel returns
- [OpenAPI console](https://zato.io/docs/dev/rest/openapi-console.html) - The console your security definitions sign callers in to

## Learn more {#learn-more}

- [REST integration overview](https://zato.io/integrations/rest/) - Start here to see how teams build REST integrations
- [In-depth API tutorial](https://zato.io/tutorials/main/01.html) - Master architectural patterns for production-ready integrations
- [Modern REST API tutorial](https://zato.io/tutorials/rest-api/python.html) - Design, build and secure REST APIs with Python
