Credentialsmodel vs. legacy singleauthThis page has been updated with the latest SDK principles, mainly concerning the usage of the
credentialsmodel for using multiple credentials inside a connector. For the old and still supportedauthproperty (single credential) scroll to the bottom of the page.
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
- Declare every credential up front: A connector lists all the credentials it accepts in a dedicated
auth.pyfile. This is the default for new connectors and is what the scaffold generates. - OAuth2 Preference: OAuth2 is the preferred authentication method for Lumos connectors due to its security and flexibility.
- 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.
- Standardized Implementation: Credentials are turned into an authenticated HTTP client inside
client.pyduring 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:
- The user initiates the OAuth flow with the third-party service, using a
Connectbutton in Lumos. - After successful authentication, the service provides an access token.
- This access token, along with any other required credentials, is saved and later on passed to the Lumos connector.
- The connector uses these credentials for all subsequent API requests.
Implementing Authentication
Declaring credentials (auth.py)
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:
# 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:
# 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:
# 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:
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:
# 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 belowThe 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:
# 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:
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.
# 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:
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
# 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
TokenCredentialfor token-based authentication. - Uses
AnnotatedFieldto ensure secure handling of the token, using thesecretargument.
Basic Authentication
# 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
BasicCredentialfor basic authentication. - Uses
AnnotatedFieldto ensure secure handling of the password, using thesecretargument.
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.
# 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
- Security: Use appropriate field types (e.g.
AnnotatedFieldwith arguments likehidden— hidden from the end user, for example when not needed — andsecretfor sensitive values) for sensitive information. - Write instructions for a person: The
descriptionon each credential is what a user follows during setup and how your connector describes itself in its OAS. - Split by credential, not by branch: One
CredentialConfigand one client class per credential keeps auth concerns isolated, if possible. - Be honest about optionality: Mark a credential
optional=Trueonly if the connector genuinely functions without it, and raiseMissingParameterErrorwith ahintwhen a capability needs it. - Validate fast up front, properly later: Single credential checks in
validate_credential_config, full credential combination API checks invalidate_credentials. - Extensibility: Design your authentication and settings models to be easily extensible as API requirements change.
- 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.
Legacy: Single-Credential Connectors
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):
# 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.pyrather than appending extra secrets to the Settings object.