Authentication

Every messaging provider requires some form of authentication — an API key, a bearer token, a username and password pair, or an OAuth 2.0 client credentials flow. If every connector handled auth internally, each would duplicate logic for token refresh, credential caching, and error handling. Authentication would be inconsistent across connectors, and adding a new auth scheme would require modifying every connector.

The framework separates authentication from connector logic:

  • Connectors declare what authentication they support in their schema via AuthenticationConfiguration

  • Providers (implementing IAuthenticationProvider) handle credential acquisition and management

  • ChannelConnectorBase delegates to a central IAuthenticationManager and auto-authenticates during initialization

  • New auth schemes can be added without modifying existing connectors

Core types

AuthenticationScheme

AuthenticationScheme is an extensible record type that identifies an authentication mechanism. It replaces the old AuthenticationType sealed enum.

public record AuthenticationScheme(string Name)
{
    public static AuthenticationScheme None { get; }
    public static AuthenticationScheme ApiKey { get; }
    public static AuthenticationScheme Bearer { get; }
    public static AuthenticationScheme Basic { get; }
    public static AuthenticationScheme OAuthClientCredentials { get; }
    public static AuthenticationScheme Certificate { get; }
    public static AuthenticationScheme Digest { get; }
    public static AuthenticationScheme Custom { get; }

    // Create custom schemes:
    public static AuthenticationScheme Of(string name) => new(name);
}

Built-in schemes cover the most common authentication patterns:

Scheme
Typical use

None

Public endpoints, no auth required

ApiKey

SendGrid, generic REST APIs

Bearer

Facebook (Page Access Token), Telegram (Bot Token)

Basic

Twilio (AccountSid + AuthToken), SMTP

OAuthClientCredentials

Server-to-server OAuth 2.0

Certificate

Firebase (service account key)

Digest

RFC 7616 digest authentication

Custom

Provider-specific schemes

Custom schemes are created with AuthenticationScheme.Of("my-scheme") — no enum modification needed.

AuthenticationField

Describes a single field that an authentication configuration expects in ConnectionSettings. It is purely descriptive — validation is handled by providers.

The AuthenticationRole property tells providers how to interpret the field:

Role
Meaning
Example fields

"principal"

The primary identifier

ApiKey, Token, Username, AccountSid, Certificate

"credential"

The corresponding secret

Password, AuthToken, ClientSecret

(other)

Auxiliary, treated as optional

ProjectId, CertificatePassword

AuthenticationConfiguration

Describes the set of fields required by a particular scheme. It is purely descriptive — providers use the field definitions to extract values from ConnectionSettings.

IsSatisfiedBy

The IsSatisfiedBy method checks whether a ConnectionSettings instance has enough values to satisfy this configuration, using role-aware logic:

  • If the config has both "principal" and "credential" role fields, at least one of each must be present

  • If the config has only "principal" fields, at least one must be present

  • All non-principal, non-credential fields must be present

This is used by the schema validation and by ChannelConnectorBase.AuthenticateAsync() to find the correct auth configuration for a given set of connection settings.

AuthenticationCredential

Holds the result of a successful authentication.

AuthenticationResult

Wraps the outcome of an authentication operation.

Architecture

Lifecycle

  1. Schema declares what auth methods the connector supports via AuthenticationConfiguration

  2. Connector initializesChannelConnectorBase.InitializeAsync() automatically calls AuthenticateAsync() if the schema has auth configurations

  3. Config selected — iterates schema configs, picks the first where IsSatisfiedBy(ConnectionSettings) returns true

  4. Provider matchedIAuthenticationManager finds a registered IAuthenticationProvider whose CanHandle(config) returns true

  5. Credential obtained — provider reads fields from ConnectionSettings (using the config's field definitions) and returns an AuthenticationCredential

  6. Credential cached — the manager caches the credential in memory

  7. Connector uses credentialAuthenticationCredential.Value, GetAuthenticationHeader(), or GetApiKey() provide the secret for API calls

  8. Expired credentials refresh — the manager detects expiration and either refreshes via the provider or obtains a new credential

Schema auth configuration

Adding authentication to a schema

Auto-registration of principal fields

When using AddAuthenticationConfiguration() with an explicit config, any field with AuthenticationRole = "principal" is automatically added as an optional ChannelParameter in the schema if not already defined. This ensures auth fields are recognized by strict-mode validation without requiring duplicate AddParameter calls.

AddAuthenticationScheme() (which uses flexible defaults) does NOT auto-register fields, avoiding parameter list pollution from alias fields that the connector does not actually use.

Default field aliases per scheme

When AddAuthenticationScheme(AuthenticationScheme.Xxx) is used, the builder generates sensible default fields:

Scheme
Principal fields
Credential fields

Basic

Username, AccountSid, User, ClientId

Password, AuthToken, Pass, ClientSecret

ApiKey

ApiKey, Key, AccessKey

Bearer

Token, AccessToken, BearerToken, AuthToken

OAuthClientCredentials

ClientId

ClientSecret

Certificate

Certificate, CertificatePath, CertificateThumbprint, PfxFile

PfxPassword, CertificatePassword

Custom

CustomAuth, AuthenticationData, Credentials, AuthConfig, SecretKey, PrivateKey, Signature, Hash

For production connectors, use AddAuthenticationConfiguration() with explicit fields — it avoids ambiguity and provides better documentation.

Built-in providers

ApiKeyAuthenticationProvider

Extracts an API key from connection settings using fields with "principal" role. Returns AuthenticationCredential.ForApiKey(value).

BearerTokenAuthenticationProvider

Extracts a bearer token from connection settings using fields with "principal" role. Supports optional TokenType and ExpiresAt parameters.

BasicAuthenticationProvider

Extracts a username/password pair from connection settings. Looks for fields with "principal" role (e.g. Username, AccountSid) and pairs them with fields with "credential" role (e.g. Password, AuthToken), trying each combination until a complete pair is found.

ClientCredentialsAuthenticationProvider

Implements the OAuth 2.0 Client Credentials flow. Reads ClientId, ClientSecret, and TokenEndpoint from connection settings, POSTs to the token endpoint, and caches the resulting access token.

The provider:

  1. Reads ClientId and ClientSecret from connection settings

  2. POSTs grant_type=client_credentials to the token endpoint

  3. Parses the JSON response for access_token, expires_in, token_type

  4. Supports refresh_token if present in the response

  5. Caches the resulting credential and refreshes when expired

The credential is created with AuthenticationCredential.ForClientCredentials() which uses AuthenticationScheme.Bearer (since a client credentials flow still produces a bearer token), with Properties["GrantType"] = "client_credentials".

Using auth in a connector

Connectors no longer need to call AuthenticateAsync() manually — the base class does it automatically during InitializeAsync():

If auto-authentication fails (e.g., missing connection settings), the base class logs a warning but does not prevent initialization — the connector can still authenticate later or handle the error:

Refresh

Refreshes the current credential if it has expired (or will expire within 5 minutes). Falls back to AuthenticateAsync() if no credential exists.

Credential caching

AuthenticationManager caches credentials by (connectionSettings, authConfiguration) tuple. The cache key is built from the scheme name and parameter values (using hash codes). When AuthenticateAsync is called:

  1. Check cache for a non-expired credential

  2. If found and not about to expire, return directly

  3. If expired, call RefreshCredentialAsync on the provider

  4. If not cached, call ObtainCredentialAsync

Custom authentication provider

Implement IAuthenticationProvider (or extend AuthenticationProviderBase) for custom auth flows:

Schema for custom provider

Registration

Or provide a custom IAuthenticationManager implementation.

Provider matching

When IAuthenticationManager.AuthenticateAsync() is called, it:

  1. Finds the AuthenticationConfiguration in the schema that satisfies the connection settings (IsSatisfiedBy)

  2. Iterates registered providers, calling CanHandle(configuration) on each

  3. Delegates to the first matching provider

The default AuthenticationProviderBase.CanHandle() matches on Scheme equality:

Override CanHandle() for providers that handle multiple schemes or need additional checks:

Schema validation integration

The ChannelSchemaExtensions.ValidateConnectionSettings() method uses IsSatisfiedBy() to check that at least one auth configuration can be fulfilled by the provided settings. If none matches, a ValidationResult is added to the error list.

Auth field names are also added to the "known parameters" set for strict-mode validation, so they do not produce "Unknown parameter" errors even when not declared as explicit schema parameters.

Migration from AuthenticationType

The old AuthenticationType enum and AuthenticationConfigurations static factory class have been removed:

Before
After

AuthenticationType.ApiKey

AuthenticationScheme.ApiKey

AuthenticationType.Token

AuthenticationScheme.Bearer

AuthenticationType.ClientCredentials

AuthenticationScheme.OAuthClientCredentials

AuthenticationConfigurations.ApiKeyAuthentication()

new AuthenticationConfiguration(...).WithField(...)

AuthenticationConfigurations.FlexibleBasicAuthentication()

new AuthenticationConfiguration(...).WithField(...) × N fields

config.RequiredFields

config.Fields

config.OptionalFields

config.Fields

config.IsSatisfiedBy(settings)

config.IsSatisfiedBy(settings) (re-added with role-aware logic)

config.Validate(settings)

removed — providers handle validation

credential.CredentialValue

credential.Value

credential.AuthenticationType

credential.Scheme

AuthenticationCredential.CreateToken(...)

AuthenticationCredential.ForBearerToken(...)

AuthenticationCredential.CreateApiKey(...)

AuthenticationCredential.ForApiKey(...)

AuthenticationCredential.CreateBasic(...)

AuthenticationCredential.ForBasic(...)

DirectCredentialAuthenticationProvider

ApiKeyAuthenticationProvider, BearerTokenAuthenticationProvider, BasicAuthenticationProvider

Last updated