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.AbstractionsSystem.ComponentModelSystem.ComponentModel.DataAnnotationsNJsonSchema.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 authenticationApiKey- API key authenticationConnectionString- Connection string for databasesCertificate- Client certificates for mutual TLSData- 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;
}
}
Related Topics
- Module Settings - Settings validation and patterns
- Security Best Practices - Secure credential handling
- External Integrations - Using credentials with external services