Table of Contents

Module Credentials

Credentials allow you to securely store sensitive information like passwords and API keys without hardcoding them in your module. They are part of the settings but configured in Crosser Control Center.

If you don't need credentials, skip to the next section: >> Receive Messages

Tip

Required namespaces:

  • Crosser.EdgeNode.Flows.Abstractions
  • System.ComponentModel
  • System.ComponentModel.DataAnnotations
  • NJsonSchema.Annotations

Basics

Credentials are created and managed in Crosser Control Center. Once created, you can reference them in your module settings.

Built-in Credential Types

Available credential types are defined in Crosser.EdgeNode.Flows.Abstractions.Credential.Types:

  • UsernameAndPassword - Basic username/password authentication
  • ApiKey - API key authentication
  • ConnectionString - Connection string for databases
  • Certificate - Client certificates for mutual TLS
  • Data - Custom credential data (JSON or binary)

Adding Credential Property

Add a credential property to your settings class with the JsonSchemaExtensionData attribute:

public class MyModuleSettings : FlowModuleSettings
{
    [Display(Name = "Database Credentials")]
    [JsonSchemaExtensionData(Credential.ATTRIBUTE, Credential.Types.ConnectionString)]
    public Guid? CredentialId { get; set; }
}

Accessing Credentials

The best place to retrieve the credentials is in your module's Initialize method using the GetCredentialContentAsync extension method:

public override async Task<IError?> Initialize()
{
    if (!this.Settings.CredentialId.HasValue)
    {
        return new Error("Credential not configured");
    }

    var credential = await this.GetCredentialContentAsync<CredentialWithConnectionString>(
        this.Settings.CredentialId.Value);
    
    if (credential.IsError || credential.Value is null)
    {
        return credential.Error ?? new Error("Failed to get credential");
    }

    this.connection = new MySqlConnection(credential.Value.ConnectionString);
    await this.connection.OpenAsync();

    return await base.Initialize();
}

The above example retrieves a connection string credential and uses it to open a database connection. Below are some common credential types you might use.

Common Credential Types

Username & Password

[Display(Name = "Database Credentials")]
[JsonSchemaExtensionData(Credential.ATTRIBUTE, Credential.Types.UsernameAndPassword)]
public Guid? DatabaseCredential { get; set; }

Accessing the credential:

var credential = await this.GetCredentialContentAsync<CredentialWithUsernamePassword>(
    this.Settings.DatabaseCredential.Value);

var connectionString = $"Server={server};User Id={credential.Value.Username};Password={credential.Value.Password}";

API Key

[Display(Name = "API Key")]
[JsonSchemaExtensionData(Credential.ATTRIBUTE, Credential.Types.ApiKey)]
public Guid? ApiKeyCredential { get; set; }

Accessing the credential:

var apiKey = await this.GetCredentialContentAsync<CredentialWithApiKey>(
    this.Settings.ApiKeyCredential.Value);

this.httpClient.DefaultRequestHeaders.Add("X-API-Key", apiKey.Value.ApiKey);

Certificate

[Display(Name = "Client Certificate")]
[JsonSchemaExtensionData(Credential.ATTRIBUTE, Credential.Types.Certificate)]
public Guid? ClientCertificate { get; set; }

Accessing the credential:

var cert = await this.GetCredentialContentAsync<CredentialWithCertificate>(
    this.Settings.ClientCertificate.Value);

var handler = new HttpClientHandler();
handler.ClientCertificates.Add(cert.Value.X509Certificate2);
this.httpClient = new HttpClient(handler);

Custom Credentials

For unsupported credential types, use Credential.Types.Data with custom JSON or binary data:

[Display(Name = "Custom Credentials")]
[JsonSchemaExtensionData(Credential.ATTRIBUTE, Credential.Types.Data)]
public Guid CustomCredential { get; set; }  // Required (not nullable)

Accessing custom data:

var customData = await this.GetCredentialContentAsync<CredentialWithData>(
    this.Settings.CustomCredential);

// Parse JSON data
var authData = JsonSerializer.Deserialize<MyCustomAuthData>(
    Encoding.UTF8.GetString(customData.Value.Data));

Multiple Credential Types

Allow users to choose between different credential types by specifying them as comma-separated values:

public class FlexibleAuthModuleSettings : FlowModuleSettings
{
    [Display(Name = "Authentication", Description = "Supports Username/Password or API Key")]
    [JsonSchemaExtensionData(Credential.ATTRIBUTE, "UsernamePassword,ApiKey")]
    public Guid? AuthCredential { get; set; }
}

Pattern 1: Type Detection with Fallback

Handle multiple types at runtime by trying each type sequentially:

public override async Task<IError?> Initialize()
{
    if (!this.Settings.AuthCredential.HasValue)
    {
        return new Error("Authentication credential not configured");
    }

    // Try username/password first
    var userPass = await this.GetCredentialContentAsync<CredentialWithUsernamePassword>(
        this.Settings.AuthCredential.Value);

    if (!userPass.IsError && userPass.Value is not null)
    {
        Information("Using username/password authentication");
        await this.AuthenticateWithPassword(
            userPass.Value.Username, 
            userPass.Value.Password);
        return await base.Initialize();
    }

    // Fall back to API key
    var apiKey = await this.GetCredentialContentAsync<CredentialWithApiKey>(
        this.Settings.AuthCredential.Value);

    if (!apiKey.IsError && apiKey.Value is not null)
    {
        Information("Using API key authentication");
        await this.AuthenticateWithApiKey(apiKey.Value.ApiKey);
        return await base.Initialize();
    }

    return new Error("No valid authentication credentials configured. " +
                     "Please configure either Username/Password or API Key.");
}

private async Task AuthenticateWithPassword(string username, string password)
{
    this.httpClient.DefaultRequestHeaders.Authorization = 
        new System.Net.Http.Headers.AuthenticationHeaderValue(
            "Basic", 
            Convert.ToBase64String(
                System.Text.Encoding.UTF8.GetBytes($"{username}:{password}")));
    
    await this.ValidateConnection();
}

private async Task AuthenticateWithApiKey(string apiKey)
{
    this.httpClient.DefaultRequestHeaders.Add("X-API-Key", apiKey);
    await this.ValidateConnection();
}

private async Task ValidateConnection()
{
    var response = await this.httpClient.GetAsync("/api/health");
    if (!response.IsSuccessStatusCode)
    {
        throw new FlowModuleException("Failed to validate authentication");
    }
}

Pattern 2: Multiple Credentials for Different Services

Use multiple credential properties when your module integrates with different services:

public class IntegrationModuleSettings : FlowModuleSettings
{
    [Display(Name = "Primary Service", Description = "Credentials for primary API")]
    [JsonSchemaExtensionData(Credential.ATTRIBUTE, Credential.Types.ApiKey)]
    public Guid? PrimaryApiCredential { get; set; }

    [Display(Name = "Database", Description = "Database connection")]
    [JsonSchemaExtensionData(Credential.ATTRIBUTE, Credential.Types.ConnectionString)]
    public Guid? DatabaseCredential { get; set; }

    [Display(Name = "Secondary Service", Description = "Optional secondary API")]
    [JsonSchemaExtensionData(Credential.ATTRIBUTE, Credential.Types.UsernameAndPassword)]
    public Guid? SecondaryApiCredential { get; set; }
}

Accessing multiple credentials:

public override async Task<IError?> Initialize()
{
    // Primary API (required)
    if (!this.Settings.PrimaryApiCredential.HasValue)
    {
        return new Error("Primary API credential required");
    }

    var primaryKey = await this.GetCredentialContentAsync<CredentialWithApiKey>(
        this.Settings.PrimaryApiCredential.Value);
    
    if (primaryKey.IsError || primaryKey.Value is null)
    {
        return primaryKey.Error ?? new Error("Failed to get primary API credential");
    }

    this.primaryClient = new HttpClient();
    this.primaryClient.DefaultRequestHeaders.Add("X-API-Key", primaryKey.Value.ApiKey);

    // Database (required)
    if (!this.Settings.DatabaseCredential.HasValue)
    {
        return new Error("Database credential required");
    }

    var dbCredential = await this.GetCredentialContentAsync<CredentialWithConnectionString>(
        this.Settings.DatabaseCredential.Value);
    
    if (dbCredential.IsError || dbCredential.Value is null)
    {
        return dbCredential.Error ?? new Error("Failed to get database credential");
    }

    this.dbConnection = new SqlConnection(dbCredential.Value.ConnectionString);
    await this.dbConnection.OpenAsync();

    // Secondary API (optional)
    if (this.Settings.SecondaryApiCredential.HasValue)
    {
        var secondaryCredential = await this.GetCredentialContentAsync<CredentialWithUsernamePassword>(
            this.Settings.SecondaryApiCredential.Value);
        
        if (!secondaryCredential.IsError && secondaryCredential.Value is not null)
        {
            this.secondaryClient = new HttpClient();
            var authValue = Convert.ToBase64String(
                System.Text.Encoding.UTF8.GetBytes(
                    $"{secondaryCredential.Value.Username}:{secondaryCredential.Value.Password}"));
            this.secondaryClient.DefaultRequestHeaders.Authorization = 
                new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authValue);
            
            Information("Secondary API configured");
        }
        else
        {
            Warning("Secondary API credential configured but failed to load");
        }
    }

    return await base.Initialize();
}

Pattern 3: Dynamic Credential Type with Helper Method

Create a helper method to abstract credential type detection:

public class SmartAuthModule : FlowModule<SmartAuthModuleSettings>
{
    private IAuthStrategy? authStrategy;

    public override async Task<IError?> Initialize()
    {
        if (!this.Settings.AuthCredential.HasValue)
        {
            return new Error("Authentication credential required");
        }

        this.authStrategy = await this.CreateAuthStrategy(this.Settings.AuthCredential.Value);
        
        if (this.authStrategy is null)
        {
            return new Error("Failed to create authentication strategy. " +
                           "Credential type not supported or invalid.");
        }

        await this.authStrategy.ConfigureHttpClient(this.httpClient);
        
        return await base.Initialize();
    }

    private async Task<IAuthStrategy?> CreateAuthStrategy(Guid credentialId)
    {
        // Try API Key
        var apiKey = await this.GetCredentialContentAsync<CredentialWithApiKey>(credentialId);
        if (!apiKey.IsError && apiKey.Value is not null)
        {
            Information("Using API Key authentication");
            return new ApiKeyAuthStrategy(apiKey.Value.ApiKey);
        }

        // Try Username/Password
        var userPass = await this.GetCredentialContentAsync<CredentialWithUsernamePassword>(credentialId);
        if (!userPass.IsError && userPass.Value is not null)
        {
            Information("Using Basic authentication");
            return new BasicAuthStrategy(userPass.Value.Username, userPass.Value.Password);
        }

        // Try Certificate
        var cert = await this.GetCredentialContentAsync<CredentialWithCertificate>(credentialId);
        if (!cert.IsError && cert.Value is not null)
        {
            Information("Using Certificate authentication");
            return new CertificateAuthStrategy(cert.Value.X509Certificate2);
        }

        return null;
    }
}

// Strategy interface
public interface IAuthStrategy
{
    Task ConfigureHttpClient(HttpClient client);
}

// API Key strategy
public class ApiKeyAuthStrategy : IAuthStrategy
{
    private readonly string apiKey;

    public ApiKeyAuthStrategy(string apiKey) => this.apiKey = apiKey;

    public Task ConfigureHttpClient(HttpClient client)
    {
        client.DefaultRequestHeaders.Add("X-API-Key", this.apiKey);
        return Task.CompletedTask;
    }
}

// Basic auth strategy
public class BasicAuthStrategy : IAuthStrategy
{
    private readonly string username;
    private readonly string password;

    public BasicAuthStrategy(string username, string password)
    {
        this.username = username;
        this.password = password;
    }

    public Task ConfigureHttpClient(HttpClient client)
    {
        var authValue = Convert.ToBase64String(
            System.Text.Encoding.UTF8.GetBytes($"{this.username}:{this.password}"));
        client.DefaultRequestHeaders.Authorization = 
            new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authValue);
        return Task.CompletedTask;
    }
}

// Certificate auth strategy
public class CertificateAuthStrategy : IAuthStrategy
{
    private readonly X509Certificate2 certificate;

    public CertificateAuthStrategy(X509Certificate2 certificate) => this.certificate = certificate;

    public Task ConfigureHttpClient(HttpClient client)
    {
        // Note: Certificate must be configured on HttpClientHandler before creating HttpClient
        // This is a simplified example
        return Task.CompletedTask;
    }
}

>> Receive Messages