Futuristic Enterprise Credential Vault

Secrets in Business Central Extensions: SecretText, IsolatedStorage, and Credential Leaks

Your Business Central extension successfully connects to an external API. Authentication works, requests are processed, and everything looks fine.

But that doesn’t necessarily mean the API key is secure.

Where is the credential stored? Can someone see it while debugging? Could another extension access it? And what happens when your code sends it to an external service?

Microsoft provides features such as SecretText, IsolatedStorage, and Azure Key Vault to help protect sensitive information.

The important part is knowing what each feature protects and when to use it.

Let’s explore them through a simple Business Central integration scenario.

Why Credentials Need Special Handling

Imagine you’re developing a Business Central extension that connects to a shipping provider.

The provider gives you an API key, which the extension must use whenever it sends a request.

A common approach is to create a setup table containing the API URL and API key.

Storing configuration in a setup table is normal. However, storing credentials in an ordinary Text field creates potential exposure through application data, AL code, or debugging, depending on permissions and implementation.

Masking a field on a page doesn’t solve the storage problem.

For example:

ExtendedDatatype = Masked;

This displays the value as dots, but does not encrypt the underlying data.

Business Central also provides MaskType = Concealed starting with runtime 16.0 (BC 27). This offers UI masking with a reveal button, but Microsoft describes it as UI-level protection because the underlying value still reaches the browser.

The key distinction: credentials require additional protection beyond how they are displayed.

What Is SecretText in Business Central?

SecretText is an AL data type introduced in Business Central 23 (2023 release wave 2).

It protects sensitive values from exposure through regular and snapshot debugging.

Consider this example:

var
    ApiKey: SecretText;
    NormalText: Text;
begin
    ApiKey := NormalText; // Allowed
    NormalText := ApiKey; // Not allowed
end;

AL allows an ordinary Text value to be assigned to SecretText, but prevents assigning a SecretText value back to ordinary Text.

Why?

Because converting protected values into ordinary text would make them easier to expose through debugging.

The compiler also prevents directly assigning a hardcoded text literal to SecretText. However, that doesn’t protect a credential previously stored in an ordinary variable or committed to source code.

When Should You Use SecretText?

Use it when handling sensitive values such as API keys, passwords, and authentication tokens.

For example, when constructing an HTTP authorization header:

Headers.Add(
    'Authorization',
    SecretStrSubstNo('Bearer %1', AccessToken));

Here, AccessToken is a SecretText variable.

SecretStrSubstNo constructs the header without converting the token into ordinary text.

Another useful feature is [NonDebuggable], which prevents sensitive procedures or variables from being inspected through the AL debugger. This is particularly useful when a credential initially arrives as ordinary text.

Remember: SecretText protects sensitive information during AL processing. It is not a storage mechanism.

What Is IsolatedStorage?

IsolatedStorage provides key-value storage isolated between Business Central extensions.

Imagine two extensions installed in the same environment:

  • Extension A connects to a shipping provider.
  • Extension B connects to a payment gateway.

If Extension A stores a value using IsolatedStorage, Extension B cannot directly retrieve that value through its own IsolatedStorage context.

This makes it useful for extension-specific settings and credentials.

Set vs. SetEncrypted

Business Central provides different methods for storing values.

MethodPurpose
IsolatedStorage.SetStores a key-value pair
IsolatedStorage.SetEncryptedEncrypts and stores a key-value pair

For credentials supported by the method, prefer SetEncrypted.

For example:

procedure SaveApiKey(ApiKey: SecretText)
var
    SaveFailedErr: Label 'Unable to save the API key.';
begin
    if not IsolatedStorage.SetEncrypted(
        'ShippingApiKey',
        ApiKey,
        DataScope::Company)
    then
        Error(SaveFailedErr);
end;

The example stores the API key encrypted within the current company’s extension context.

It assumes the credential has already been entered through a protected setup process accessible only to authorized users.

There is an important limitation: SetEncrypted supports a maximum of 215 plain characters, and some special characters consume additional space.

Therefore, it may not be suitable for long tokens, certificates, or private keys.

Both the SetEncrypted and Get overloads accepting SecretText require AL runtime 13.0 (Business Central 24).

Understanding DataScope

DataScope determines the context in which IsolatedStorage values are available.

DataScopeMeaningExample
ModuleExtension-level scopeOne API key shared across companies
CompanyExtension and companyDifferent API keys per company
UserExtension and userPersonal credentials
CompanyAndUserExtension, company, and userPersonal credentials within a company

For company-specific integrations, DataScope::Company is often appropriate.

But consider Job Queue processing. If a credential is saved with DataScope::User, a background session running under another user may not be able to retrieve it.

Choosing the correct scope is important for both security and integration functionality.

How SecretText and IsolatedStorage Work Together

Let’s return to our shipping integration.

The process can be understood in four stages:

StageWhat happens
ConfigurationAn authorized user enters the API key through a protected, write-only setup field
StorageThe extension saves it using IsolatedStorage.SetEncrypted
RetrievalThe extension retrieves it into SecretText
API requestThe extension sends it to the approved provider using a secret-aware HTTP method

For example, retrieving the credential:

procedure GetApiKey(): SecretText
var
    ApiKey: SecretText;
    MissingKeyErr: Label 'The shipping API key is not configured.';
begin
    if not IsolatedStorage.Get(
        'ShippingApiKey',
        DataScope::Company,
        ApiKey)
    then
        Error(MissingKeyErr);

    exit(ApiKey);
end;

The credential is returned as SecretText, so the calling procedure can pass it to a supported HTTP authentication method without converting it into ordinary text.

These examples demonstrate the documented methods, not a complete integration. A production implementation must also control who can configure credentials and which destination receives them.

Where Can Credentials Still Leak?

Using SecretText and encrypted IsolatedStorage improves credential handling, but other implementation decisions remain important.

SituationPotential problemRecommended approach
Hardcoded credentialsAPI keys appear in source code or Git historyKeep real credentials outside source code
Ordinary setup fieldsCredentials are retained as application dataUse appropriate protected storage
DebuggingSensitive Text variables may be inspectedUse SecretText and [NonDebuggable]
HTTP requestsCredentials reach unintended destinationsRestrict requests to trusted endpoints
LoggingSecrets appear in errors or diagnosticsLog non-sensitive information

Can Another Extension Access the Credential?

IsolatedStorage separates stored values between extensions, but developers must also review what their public procedures allow other extensions to do.

Imagine exposing a procedure that sends a stored API key to any URL supplied by its caller.

Even though the key is encrypted in storage, another extension could potentially invoke that procedure with an unintended destination.

Declaring a procedure internal, or setting a codeunit’s Access = Internal, reduces what other extensions can reference at compile time.

However, Microsoft states that access modifiers are not runtime security boundaries. For example, an internal codeunit’s OnRun can still be invoked using Codeunit.Run, while reflection-based mechanisms such as RecordRef can access internal tables.

Avoid placing credential-sending logic in OnRun, and don’t allow untrusted callers to choose the destination URL.

What Happens When Production Is Copied to a Sandbox?

Imagine copying a production environment to a sandbox to investigate an integration issue.

Business Central takes several precautions during environment copying, including stopping scheduled tasks and blocking outbound HTTP calls from extensions by default.

To re-enable those requests, Allow HttpClient Requests must be selected in Extension Settings for each extension involved, including library apps.

However, developers must still consider their own integration credentials.

If a production credential remains available after copying, re-enabling outgoing requests could allow the sandbox to communicate with a live external service.

Microsoft’s documented cleanup list doesn’t establish that every custom extension credential is removed. Developers should therefore review their own credential handling rather than assume everything has been cleared.

The System Application provides codeunit 1886 “Environment Cleanup”, including two events:

  • OnClearCompanyConfig — for cleaning company-specific configuration.
  • OnClearDatabaseConfig — for cleaning environment-wide configuration.

Extensions can subscribe to these events to handle their configuration when environments are copied.

What About Azure Key Vault?

Azure Key Vault is a Microsoft service for centrally managing secrets, keys, and certificates.

Business Central provides a built-in App Key Vault integration that allows supported extensions to retrieve secrets.

However, there is an important limitation:

In Business Central Online, the built-in App Key Vault feature supports Marketplace extensions, not per-tenant or development extensions.

For per-tenant extensions handling short customer-provided API keys, encrypted IsolatedStorage may be suitable.

For more complex requirements, such as centrally managed secrets or long private keys, a separately designed external secret-management service may be appropriate.

Quick Reference: Which Feature Should You Use?

RequirementRecommended approach
Protect credentials during AL processingSecretText
Store a short credential encryptedIsolatedStorage.SetEncrypted
Maintain separate credentials for each companyDataScope::Company
Protect sensitive retrieval code from debugging[NonDebuggable]
Send credentials through supported HTTP headersSecret-aware HttpHeaders.Add
Reduce the publicly exposed AL APIinternal procedures or Access = Internal, alongside runtime controls
Centrally manage Marketplace extension secretsBuilt-in App Key Vault
Handle large private keys or certificatesEvaluate dedicated secret-management or cryptographic services
Protect copied environmentsReview credentials and use environment-cleanup mechanisms

Final Thoughts

The most useful distinction is simple:

SecretText protects how AL handles sensitive values. IsolatedStorage.SetEncrypted protects how supported credentials are stored.

DataScope determines where those stored values are available, while Azure Key Vault provides additional options for supported architectures.

Together, these features help Business Central developers handle credentials more securely. The remaining responsibility is to design the integration carefully, especially its configuration, exposed procedures, external requests, and diagnostic information.

Before building your next API integration, ask yourself three questions:

Where is the credential stored? How does AL handle it? Where can the extension send it?

Those questions provide a practical starting point for preventing credential leaks in Business Central extensions.

Leave a Reply

Your email address will not be published. Required fields are marked *