Isolated Storage

Isolated storage in Business Central is a secure key/value store for small, sensitive data such as API keys. It keeps secrets out of tables, supports encryption when it is enabled, and scopes data with DataScope (for example, Module or Company) via the IsolatedStorage and SecretText.

Use case

Full use case can be found here: ui-translations-bc

Example help functions

internal procedure SetApiKey(NewApiKey: SecretText)
var
    OverwriteApiKeyQst: Label 'The Api key is already set. Do you want to overwrite it?';
begin
    if this.IsApiKeySet() and GuiAllowed() then
        if not Confirm(OverwriteApiKeyQst) then
            exit;

    if EncryptionEnabled() then
        IsolatedStorage.SetEncrypted(this.GetStorageKey(), NewApiKey, DataScope::Module)
    else
        IsolatedStorage.Set(this.GetStorageKey(), NewApiKey, DataScope::Module);
end;

internal procedure GetApiKey(): SecretText
var
    ApiKey: SecretText;
begin
    IsolatedStorage.Get(this.GetStorageKey(), DataScope::Module, ApiKey);
    exit(ApiKey);
end;

internal procedure IsApiKeySet(): Boolean
begin
    exit(IsolatedStorage.Contains(this.GetStorageKey(), DataScope::Module));
end;

local procedure GetStorageKey(): Text
begin
    exit('whatever-describes-your-key-or-guid'); //i.e. deepl-api-key
end;

Example setup page

Warning

It is not possible to use secured text for variable displayed as a field on the page. Therefore the variable should be marked as NonDebuggable.

page 50104 "AMC DeepL Setup"
{
    ApplicationArea = All;
    Caption = 'DeepL Setup';
    DeleteAllowed = false;
    InsertAllowed = false;
    PageType = Card;
    SourceTable = "AMC DeepL Setup";
    UsageCategory = Administration;

    layout
    {
        area(Content)
        {
            group(General)
            {
                Caption = 'General';

                field("Base Url"; Rec."Base Url")
                {
                    ApplicationArea = Basic, Suite;
                    ToolTip = 'Specifies the value of the Base Url field.', Comment = '%';
                }
                field("API Key"; this.ApiKey)
                {
                    ApplicationArea = Basic, Suite;
                    Caption = 'API Key';
                    ExtendedDatatype = Masked;
                    ToolTip = 'Specifies the value of the API Key.', Comment = '%';

                    trigger OnValidate()
                    begin
                        Rec.SetApiKey(this.ApiKey);
                    end;
                }
            }
        }
    }

    trigger OnOpenPage()
    begin
        if not Rec.Get() then begin
            Rec.Init();
            Rec.Insert();
        end;
        if Rec.IsApiKeySet() then
            this.ApiKey := '****';
    end;

    var
        [NonDebuggable]
        ApiKey: Text;
}

Example usage

local procedure PostDeepL(var Response: HttpResponseMessage; BodyText: Text)
var
    DeepLSetup: Record "AMC DeepL Setup";
    Client: HttpClient;
    Content: HttpContent;
    ContentHeaders: HttpHeaders;
    ApiKeyPrefixTxt: Label 'DeepL-Auth-Key %1', Locked = true;
    DeepLSetupNotFoundErr: Label 'DeepL Setup is not configured. Open the DeepL Setup page and enter the API key and base URL.';
    PostErr: Label 'DeepL error (%1 %2): %3', Comment = '%1 is Response Http Status Code, %2 is Response Reason Phrase, %3 is Response body';
    Url: Text;
begin
    if not DeepLSetup.Get() then
        Error(DeepLSetupNotFoundErr);
    Url := DeepLSetup."Base Url" + '/translate';

    Content.GetHeaders(ContentHeaders);
    Content.WriteFrom(BodyText);
    ContentHeaders.Remove('Content-Type');
    ContentHeaders.Add('Content-Type', 'application/json');
    Client.Clear();
    Client.DefaultRequestHeaders().Add('Authorization', SecretStrSubstNo(ApiKeyPrefixTxt, DeepLSetup.GetApiKey()));
    Client.DefaultRequestHeaders().Add('Accept', 'application/json');
    Client.Post(Url, Content, Response);

    if not Response.IsSuccessStatusCode() then begin
        Response.Content.ReadAs(BodyText);
        Error(PostErr, Response.HttpStatusCode, Response.ReasonPhrase, BodyText);
    end;
end;