Flow Studio
Authentication
How MCP server credentials are stored, resolved, and injected at call time — using ICredentialResolver so no secret ever appears in workflow configuration.
Credential Pattern
Every MCP server definition carries a credentialId integer. At call time, IMCPClientFactory calls ICredentialResolver.GetPasswordAsync(credentialId) to retrieve the secret and injects it into the transport before the tool call is made. Workflow designers configure a credentialId — never a token or password.
Supported Authentication Modes
| Mode | Transport | Credential Format | Header / Mechanism |
|---|---|---|---|
| API Key | HTTP / SSE | Plain string | Authorization: Bearer <key> or X-Api-Key: <key> |
| OAuth 2.0 Client Credentials | HTTP / SSE | JSON: {"clientId":"...","clientSecret":"...","tokenUrl":"..."} | Token fetched and cached; Authorization: Bearer <token> |
| mTLS | HTTP / SSE | JSON: {"cert":"---PEM---","key":"---PEM---"} | Client certificate presented on TLS handshake |
| None | StdIo | None (credentialId: 0) | Process-level isolation; no network auth needed |
IMCPClientFactory — Credential Injection
public class MCPClientFactory : IMCPClientFactory
{
private readonly ICredentialResolver _credentials;
public MCPClientFactory(ICredentialResolver credentials)
{
_credentials = credentials;
}
public async Task<IMCPClient> CreateClientAsync(
IMCPServerDefinition server,
CancellationToken ct)
{
if (server.TransportType == MCPTransportType.StdIo)
return new StdIoMCPClient(server);
// Resolve secret — never stored in server definition
string secret = await _credentials.GetPasswordAsync(server.CredentialId, ct);
var authMode = DetectAuthMode(secret);
return authMode switch
{
AuthMode.OAuthClientCredentials => await BuildOAuthClientAsync(server, secret, ct),
AuthMode.mTLS => BuildMtlsClient(server, secret),
_ => new HttpMCPClient(server, bearerToken: secret)
};
}
private static AuthMode DetectAuthMode(string secret)
{
if (!secret.TrimStart().StartsWith("{")) return AuthMode.ApiKey;
using var doc = JsonDocument.Parse(secret);
if (doc.RootElement.TryGetProperty("clientSecret", out _)) return AuthMode.OAuthClientCredentials;
if (doc.RootElement.TryGetProperty("cert", out _)) return AuthMode.mTLS;
return AuthMode.ApiKey;
}
}
Registering Credentials
// API Key:
POST /api/credentials
{
"name": "bfai-document-ai-key",
"type": "ApiKey",
"value": "sk-bfai-doc-xxxxxxxxxxxxxxxx"
}
// → returns credentialId: 201
// OAuth 2.0:
POST /api/credentials
{
"name": "bfai-risk-ai-oauth",
"type": "ApiKey",
"value": "{\"clientId\":\"risk-client\",\"clientSecret\":\"...\",\"tokenUrl\":\"https://auth.bfai.internal/token\"}"
}
// → returns credentialId: 202
Credential Rotation
To rotate a credential, update the secret value via PUT /api/credentials/{id}. No workflow changes are needed — all server definitions reference the credential by ID. The next tool call after rotation automatically uses the new secret.
OAuth token caching: OAuth access tokens are cached in-process for their declared
expires_in duration minus a 60-second safety margin. If a token is rotated on the identity provider before it expires, use POST /api/mcp/servers/{serverId}/flush-token to invalidate the in-process cache immediately.