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

# Authorization and Authentication

#### Learn how to use the SDK to build integrations with different authorization styles.

<Info>
  *`Credentials` model vs. legacy single `auth`*\*

  This page has been updated with the latest SDK principles, mainly concerning the usage of the `credentials` model for using multiple credentials inside a connector. For the old and still supported `auth` property (single credential) scroll to the bottom of the page.
</Info>

Authentication is a crucial aspect of Lumos Integration connectors, ensuring secure and authorized access to third-party APIs. This section outlines the recommended authentication approach used in Lumos connectors.

### Recommended Principles

1. **Declare every credential up front**: A connector lists all the credentials it accepts in a dedicated `auth.py` file. This is the default for new connectors and is what the scaffold generates.
2. **OAuth2 Preference**: OAuth2 is the preferred authentication method for Lumos connectors due to its security and flexibility.
3. **Credential Readiness**: Connectors expect authentication credentials to be ready for use. The connector SDK has a fully fledged OAuth module which handles the specific authorization capabilities.
4. **Standardized Implementation**: Credentials are turned into an authenticated HTTP client inside `client.py` during its initialization, and the SDK offers pre-defined models for the most common authentication schemes.

### Why Multiple Credentials

Apps frequently need more than one credential for a single connection. A common shape is OAuth for the app's REST API plus a separate SCIM token for user provisioning, sometimes with a third credential unlocking an optional area of the API such as license management.

Rather than packing extra secrets into the settings object, a connector declares each credential explicitly. Each one gets its own ID, its own setup instructions, and its own client. Lumos then knows which combinations of credentials form a valid connection, which fields are secret, and how to validate each one as the customer enters it.

### OAuth2 Authentication Flow

The general process is as follows:

1. The user initiates the OAuth flow with the third-party service, using a `Connect` button in Lumos.
2. After successful authentication, the service provides an access token.
3. This access token, along with any other required credentials, is saved and later on passed to the Lumos connector.
4. The connector uses these credentials for all subsequent API requests.

### Implementing Authentication

#### Declaring credentials (`auth.py`)

Each credential is a `CredentialConfig`, or an `OAuthConfig` when it uses an OAuth flow. The list is the connector's public authentication contract:

```python theme={null}
# Example: abc/auth.py
from connector_sdk_types import AuthModel, CredentialConfig, OAuthConfig
from connector.oai.capability import StandardCapabilityName
from connector.oai.modules.oauth_module_types import OAuthSettings

from abc.constants import AbcCredentialId

OAUTH_DESCRIPTION = """To set up OAuth authentication with Abc:

1. Log in to Abc as an administrator and open the developer settings
2. Create a new OAuth application
3. Add `https://app.lumosidentity.com/integrations/ics_oauth2_callback` as a redirect URI
4. Copy the Client ID and Client Secret into the fields below
"""

SCIM_TOKEN_DESCRIPTION = """SCIM API token, used for user provisioning.

1. Log in to Abc as an account admin
2. Open Admin -> App integration and enable SCIM
3. Click Generate token and copy it into the field below
"""

AbcCredentialsConfig = [
    OAuthConfig(
        id=AbcCredentialId.OAUTH,
        type=AuthModel.OAUTH,
        description=OAUTH_DESCRIPTION,
        oauth_settings=OAuthSettings(
            authorization_url="https://abc.com/oauth/authorize",
            token_url="https://abc.com/oauth/token",
            # Scopes are requested per capability - list only what each one needs
            scopes={
                StandardCapabilityName.VALIDATE_CREDENTIALS: "offline_access account.info",
                StandardCapabilityName.LIST_ACCOUNTS: "account.users.read",
            },
        ),
    ),
    CredentialConfig(
        id=AbcCredentialId.SCIM,
        name="SCIM Token",
        type=AuthModel.TOKEN,
        description=SCIM_TOKEN_DESCRIPTION,
    ),
]
```

Credential IDs are part of the connector's public contract — renaming one invalidates existing connections. Keep them in one place, usually an enum in `constants.py`:

```python theme={null}
# Example: abc/constants.py
from enum import Enum

class AbcCredentialId(str, Enum):
    OAUTH = "abc_oauth"
    SCIM = "abc_scim"
```

`CredentialConfig` accepts:

| Field | Purpose |
| - | - |
| `id` | Stable, app-unique credential ID. Sent back on every request. |
| `type` | The `AuthModel` describing the credential's shape. |
| `description` | Markdown setup instructions rendered for the customer. |
| `name` | Display name. Defaults to the auth model's name (e.g. "OAuth 2.0"). |
| `optional` | `True` if the connector still works without this credential. |
| `input_model` | Override the default payload model to add or annotate fields. |
| `validation` | A per-credential validator (see below). |
| `oauth_settings` | OAuth flow configuration (on `OAuthConfig`). |

Available authentication models:

| Target API supports | `AuthModel` | Payload model |
| - | - | - |
| OAuth2 authorization code | `AuthModel.OAUTH` | `OAuthCredential` |
| OAuth2 client credentials | `AuthModel.OAUTH_CLIENT_CREDENTIALS` | `OAuthClientCredential` |
| OAuth 1.0a | `AuthModel.OAUTH1` | `OAuth1Credential` |
| API key / bearer token | `AuthModel.TOKEN` | `TokenCredential` |
| Username + password | `AuthModel.BASIC` | `BasicCredential` |
| JWT | `AuthModel.JWT` | `JWTCredential` |
| Service account | `AuthModel.SERVICE_ACCOUNT` | `ServiceAccountCredential` |
| Key pair (e.g. Snowflake) | `AuthModel.KEY_PAIR` | `KeyPairCredential` |

#### Integration Configuration

In `integration.py`, pass the credential list as `credentials` and declare which combinations form a valid connection:

```python theme={null}
# Example: abc/integration.py
from connector_sdk_types import CredentialsSettings
from connector.oai.integration import Integration

from abc.auth import AbcCredentialsConfig
from abc.constants import AbcCredentialId
from abc.settings import AbcSettings

integration = Integration(
    app_id="abc",
    settings_model=AbcSettings,
    credentials=AbcCredentialsConfig,
    credentials_settings=CredentialsSettings(
        allowed_credentials=[
            (AbcCredentialId.OAUTH, AbcCredentialId.SCIM),
        ],
    ),
)
```

Each tuple in `allowed_credentials` is one combination a single connection may use. To offer OAuth alone *or* OAuth plus SCIM, list both:

```python theme={null}
allowed_credentials=[
    (AbcCredentialId.OAUTH,),
    (AbcCredentialId.OAUTH, AbcCredentialId.SCIM),
]
```

If you omit `allowed_credentials`, the combinations are derived from the `optional` flag on each credential: required credentials are always requested together, and each optional credential is additionally offered on its own. Being explicit is clearer whenever more than one credential is involved.

The `OAuthConfig` entries drive the OAuth module, so `get_authorization_url`, `handle_authorization_callback`, `handle_client_credentials_request` and `refresh_access_token` are registered automatically. OAuth settings now live on each credential, so the top-level `oauth_settings` argument on the `Integration` is not used by connectors on the credentials model. When a connector declares more than one OAuth credential, those capabilities accept a `credential_id` so the right settings are selected.

#### Client class

Each connector implements a client class, extending the `BaseIntegrationClient` SDK class, in a dedicated `client.py` file. `get_credential` pulls a specific credential out of the request by ID:

```python theme={null}
# Example: abc/client.py
import typing as t

from connector.generated import OAuthCredential
from connector.oai.base_clients import BaseIntegrationClient
from connector.oai.capability import Request, get_credential
from connector.utils.httpx_auth import BearerAuth

from abc.constants import BASE_URL, AbcCredentialId

class AbcClient(BaseIntegrationClient):
    requires_response_body = True

    @classmethod
    def prepare_client_args(cls, args: Request) -> dict[str, t.Any]:
        oauth = get_credential(args, AbcCredentialId.OAUTH, OAuthCredential)
        return {
            "auth": BearerAuth(token=oauth.access_token),
            "base_url": BASE_URL,
        }

    # Implement your API calls / client methods below
```

The `BaseIntegrationClient` class serves as the minimal abstraction layer for writing a `httpx AsyncClient` implementation. The `prepare_client_args` class method then provides arguments for `httpx` to initialize.

Each credential often has its own auth scheme and its own base URL, so you can add a second class rather than branching inside `prepare_client_args`:

```python theme={null}
# Example: abc/client.py (continued)
from connector.generated import TokenCredential

class AbcSCIMClient(BaseIntegrationClient):
    @classmethod
    def prepare_client_args(cls, args: Request) -> dict[str, t.Any]:
        scim = get_credential(args, AbcCredentialId.SCIM, TokenCredential)
        return {
            "auth": BearerAuth(token=scim.token),
            "base_url": SCIM_BASE_URL,
        }
```

For a credential declared `optional=True`, pass `strict=False` and handle its absence with an actionable error:

```python theme={null}
from connector.oai.errors import MissingParameterError

api_key = get_credential(args, AbcCredentialId.API_KEY, TokenCredential, strict=False)
if not api_key:
    raise MissingParameterError(
        message="No credentials available for Abc API key authentication",
        hint=(
            "To manage licenses, add the Abc API key credential in your integration "
            "settings. See the setup guide for how to generate one."
        ),
    )
```

Without `strict=False`, a missing credential raises — which is the behavior you want for credentials that are always required.

#### Validating a credential as it is entered

Attaching `validation=` to a `CredentialConfig` registers a validator for the `validate_credential_config` capability. It runs against the single credential the user just entered, before the connection is saved, and anything it returns in `validation_errors` is shown to them verbatim.

```python theme={null}
# Example: abc/auth.py (continued)
import logging

from connector.oai.capability import get_credential
from connector_sdk_types import (
    ValidateCredentialConfigRequest,
    ValidateCredentialConfigResponse,
    ValidatedCredentialConfig,
)
from connector_sdk_types.generated import TokenCredential

logger = logging.getLogger(__name__)

def validate_api_key_credential(
    args: ValidateCredentialConfigRequest,
) -> ValidateCredentialConfigResponse:
    credential = get_credential(args, AbcCredentialId.API_KEY, TokenCredential)
    validation_errors: list[str] = []

    # Abc API keys are prefixed with `key-`
    if not credential.token.startswith("key-"):
        validation_errors.append("Invalid API key format. Abc API keys must start with 'key-'.")
        
		# Any other vital checks, like scope allowance on access_token, etc.

    return ValidateCredentialConfigResponse(
        response=ValidatedCredentialConfig(
            valid=not validation_errors,
            validation_errors=validation_errors,
        ),
    )
```

Then reference it from the credential:

```python theme={null}
CredentialConfig(
    id=AbcCredentialId.API_KEY,
    name="API Key",
    type=AuthModel.TOKEN,
    description=API_KEY_DESCRIPTION,
    validation=validate_api_key_credential,
)
```

The SDK already performs base validation on every credential before your validator runs, so you only need to add app-specific rules. It, by default, rejects:

* credentials missing required fields
* required fields submitted as empty strings
* values with leading or trailing whitespace
* payloads that do not match the declared authentication model

Validators may be synchronous or `async`. The `validate_credential_config` capability itself is registered automatically for you; set `CredentialsSettings(register_validation_capability=False)` only if you need to implement it by hand.

#### Customizing credential fields

Use `input_model` when the default payload model does not carry the fields the app needs, or when you want to add customer-facing titles and descriptions to them.

#### Token Based Authentication

```python theme={null}
# Example: abc/auth.py
from connector.generated import TokenCredential
from connector.serializers.request import AnnotatedField

class AbcTokenAuth(TokenCredential):
    token: str = AnnotatedField(
        title="API Token",
        description="To create an API Token, navigate to the Abc dashboard",
        secret=True,
    )

# Single config entry
CredentialConfig(
    id=AbcCredentialId.TOKEN,
    type=AuthModel.TOKEN,
    description=TOKEN_DESCRIPTION,
    input_model=AbcTokenAuth,
)
```

This custom model:

* Extends `TokenCredential` for token-based authentication.
* Uses `AnnotatedField` to ensure secure handling of the token, using the `secret` argument.

#### Basic Authentication

```python theme={null}
# Example: abc/auth.py
from connector.generated import BasicCredential
from connector.serializers.request import AnnotatedField

class AbcBasicAuth(BasicCredential):
    username: str = AnnotatedField(
        title="Username",
        description="Your username",
    )
    password: str = AnnotatedField(
        title="Password",
        description="Your password",
        secret=True,
    )
    
# Single config entry
CredentialConfig(
    id=AbcCredentialId.BASIC,
    type=AuthModel.BASIC,
    description=BASIC_DESCRIPTION,
    input_model=AbcBasicAuth,
)
```

This custom model:

* Extends `BasicCredential` for basic authentication.
* Uses `AnnotatedField` to ensure secure handling of the password, using the `secret` argument.

#### OAuth Client Credentials Flow

> Sometimes called machine to machine flow, this OAuth flow is widely used for server-side communication. It requires the caller to authorize and obtain an access\_token in a single request-response flow.

```python theme={null}
# Example: abc/auth.py
from connector.generated import OAuthClientCredential
from connector.serializers.request import AnnotatedField, SecretField
from connector.oai.modules.oauth_module_types import (
    OAuthCapabilities,
    OAuthFlowType,
    OAuthSettings,
)
from pydantic import Field

class AbcOAuthClientCredentials(OAuthClientCredential):
    scopes: list[str] = AnnotatedField(title="Scopes", hidden=True)
    access_token: str = AnnotatedField(title="Access Token", hidden=True)
    client_id: str = Field(
        title="Client ID",
        description="Specific instructions to obtain a Client ID.",
    )
    client_secret: str = SecretField(
        title="Client Secret",
        description="Specific instructions to obtain a Client Secret",
    )
    
# Single config entry
OAuthConfig(
    id=AbcCredentialId.CLIENT_CREDENTIALS,
    type=AuthModel.OAUTH_CLIENT_CREDENTIALS,
    description=CLIENT_CREDENTIALS_DESCRIPTION,
    input_model=AbcOAuthClientCredentials,
    oauth_settings=OAuthSettings(
        capabilities=OAuthCapabilities(
            # Client Credentials Flow often does not support refreshing.
            # Disable the capability of this module if that is the case.
            refresh_access_token=False,
        ),
        token_url="https://abc.com/oauth/token",
        flow_type=OAuthFlowType.CLIENT_CREDENTIALS,
        scopes={
            StandardCapabilityName.LIST_ACCOUNTS: "some required scopes separated with space",
            # ...
        },
    ),
)
```

This example shows how you can extend the standard `OAuthClientCredential` model and use the SDK provided OAuth module.

#### Best Practices

1. **Security**: Use appropriate field types (e.g. `AnnotatedField` with arguments like `hidden` — hidden from the end user, for example when not needed — and `secret` for sensitive values) for sensitive information.
2. **Write instructions for a person**: The `description` on each credential is what a user follows during setup and how your connector describes itself in its OAS.
3. **Split by credential, not by branch**: One `CredentialConfig` and one client class per credential keeps auth concerns isolated, if possible.
4. **Be honest about optionality**: Mark a credential `optional=True` only if the connector genuinely functions without it, and raise `MissingParameterError` with a `hint` when a capability needs it.
5. **Validate fast up front, properly later**: Single credential checks in `validate_credential_config`, full credential combination API checks in `validate_credentials`.
6. **Extensibility**: Design your authentication and settings models to be extensible as API requirements change.
7. **Validation**: Leverage Pydantic's validation capabilities to ensure that provided credentials and settings meet the required format and constraints.

### Connector Specific Considerations

* While the general structure remains consistent, each connector may have unique authentication requirements.
* Always refer to the specific API documentation of the service you're integrating with for the most accurate authentication requirements.
* If you require multiple authentication credentials, it is recommended to append the extra credentials to the Settings object using appropriate secret arguments.

<Accordion title="Legacy: Single-Credential Connectors" icon="fa-info-circle">
  Connectors written before the credentials model accept exactly one credential. They pass a single payload model as `auth`, configure OAuth with a top-level `oauth_settings`, and read the credential with `get_oauth(args)`, `get_token_auth(args)` or `get_basic_auth(args)`:

  ```python theme={null}
  # Example: abc/integration.py (legacy shape)
  from connector.generated import OAuthCredential
  from connector.oai.modules.oauth_module_types import OAuthSettings

  integration = Integration(
      app_id="abc",
      settings_model=AbcSettings,
      auth=OAuthCredential,
      oauth_settings=OAuthSettings(
          authorization_url="https://abc/oauth/authorize",
          token_url="https://abc/oauth/token",
      ),
  )
  ```

  In this shape, custom credential models are conventionally defined in `settings.py` rather than `auth.py`, and requests carry the credential on a single `auth` object instead of a `credentials` array.

  This shape remains supported, and the SDK scaffold can still generate it with `connector scaffold <name> <directory> --single-auth`. **New connectors should prefer the credentials model**: it is the only shape that supports more than one credential, and `validate_credential_config`.

  ### Connector Specific Considerations

  * While the general structure remains consistent, each connector may have unique authentication requirements.
  * Always refer to the specific API documentation of the service you're integrating with for the most accurate authentication requirements.
  * If you require multiple authentication credentials, declare each one in `auth.py` rather than appending extra secrets to the Settings object.
</Accordion>
