Install
$ agentstack add skill-emgreppi-business-central-ai-skill-authenticating-with-oauth ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Skill: AL OAuth Integration
Validation Gates
- After Step 4:
GetAccessToken()returns valid token in sandbox - After Step 5: API call succeeds, 401 triggers auto-retry
- Final: Token caching works (check
Token Expiryfield), telemetry logs operations
Note: SecretText.Unwrap() blocked in SaaS. Use Text + [NonDebuggable].
Procedure
Step 1: Identify OAuth Flow
Ask user:
- Is this service-to-service (no user context)? → Client Credentials
- Does it require user consent/context? → Authorization Code
Step 2: Create Setup Table
Create a setup table with these fields:
table " OAuth Setup"
{
DataClassification = CustomerContent;
fields
{
field(1; "Primary Key"; Code[10]) { }
field(10; "Client ID"; Text[100]) { }
field(11; "Tenant ID"; Text[100]) { }
field(12; "Token Endpoint"; Text[250]) { }
field(13; Scope; Text[250]) { }
field(20; "Token Expiry"; DateTime) { Editable = false; }
field(21; Enabled; Boolean) { }
}
keys
{
key(PK; "Primary Key") { Clustered = true; }
}
}
Step 3: Implement Secret Storage
Use Isolated Storage for client_secret. Pattern works for SaaS and OnPrem:
codeunit " OAuth Secrets Mgt"
{
Access = Internal;
var
SecretKeyLbl: Label 'OAuthClientSecret', Locked = true;
[NonDebuggable]
procedure SetClientSecret(SecretValue: Text)
begin
if SecretValue = '' then
IsolatedStorage.Delete(SecretKeyLbl, DataScope::Company)
else
IsolatedStorage.Set(SecretKeyLbl, SecretValue, DataScope::Company);
end;
[NonDebuggable]
procedure GetClientSecret(): Text
var
SecretValue: Text;
begin
if IsolatedStorage.Get(SecretKeyLbl, DataScope::Company, SecretValue) then
exit(SecretValue);
exit('');
end;
procedure HasClientSecret(): Boolean
begin
exit(IsolatedStorage.Contains(SecretKeyLbl, DataScope::Company));
end;
}
Step 4: Implement Token Acquisition
Use Codeunit 501 "OAuth2" (system codeunit):
codeunit " OAuth Token Mgt"
{
Access = Internal;
var
Setup: Record " OAuth Setup";
SecretsMgt: Codeunit " OAuth Secrets Mgt";
[NonDebuggable]
procedure GetAccessToken(): SecretText
var
OAuth2: Codeunit OAuth2;
AccessToken: SecretText;
Scopes: List of [Text];
begin
Setup.Get();
Setup.TestField(Enabled);
Setup.TestField("Client ID");
Setup.TestField("Token Endpoint");
if IsTokenValid() then
exit(GetCachedToken());
Scopes.Add(Setup.Scope);
if not OAuth2.AcquireTokenWithClientCredentials(
Setup."Client ID",
SecretsMgt.GetClientSecret(),
Setup."Token Endpoint",
'',
Scopes,
AccessToken)
then
Error('Failed to acquire OAuth token: %1', GetLastErrorText());
CacheToken(AccessToken);
exit(AccessToken);
end;
local procedure IsTokenValid(): Boolean
begin
exit((Setup."Token Expiry" <> 0DT) and (Setup."Token Expiry" > CurrentDateTime()));
end;
local procedure CacheToken(Token: SecretText)
begin
Setup."Token Expiry" := CurrentDateTime() + (3540 * 1000);
Setup.Modify();
end;
local procedure GetCachedToken(): SecretText
begin
// Implement: Isolated Storage or Session variable
end;
}
Step 5: Implement HTTP Client with OAuth
Create HTTP client that automatically adds Bearer token:
codeunit " OAuth HTTP Client"
{
Access = Internal;
var
TokenMgt: Codeunit " OAuth Token Mgt";
[NonDebuggable]
procedure SendRequest(Method: Text; Url: Text; RequestBody: Text; var ResponseBody: Text; var HttpStatusCode: Integer): Boolean
var
Client: HttpClient;
Request: HttpRequestMessage;
Response: HttpResponseMessage;
Headers: HttpHeaders;
Content: HttpContent;
AccessToken: SecretText;
begin
AccessToken := TokenMgt.GetAccessToken();
Request.Method := Method;
Request.SetRequestUri(Url);
Request.GetHeaders(Headers);
Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', AccessToken));
Headers.Add('Content-Type', 'application/json');
if RequestBody <> '' then begin
Content.WriteFrom(RequestBody);
Request.Content := Content;
end;
if not Client.Send(Request, Response) then begin
HttpStatusCode := 0;
exit(false);
end;
HttpStatusCode := Response.HttpStatusCode();
Response.Content.ReadAs(ResponseBody);
// Auto-retry on 401 (token expired)
if HttpStatusCode = 401 then begin
AccessToken := TokenMgt.GetAccessToken();
Headers.Remove('Authorization');
Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', AccessToken));
if Client.Send(Request, Response) then begin
HttpStatusCode := Response.HttpStatusCode();
Response.Content.ReadAs(ResponseBody);
end;
end;
exit(Response.IsSuccessStatusCode());
end;
}
Security Essentials
[NonDebuggable]on all procedures handling secretsAccess = Internalon secret management codeunitsDataScope::Companyfor OAuth credentials- Cache token for
(expires_in - 60s)to avoid mid-request expiry
Troubleshooting OAuth Errors
| Error | Fix | |-------|-----| | Token request fails | Verify Client ID, Secret, Token Endpoint, Scope | | 401 on API call | Token expired → auto-retry should refresh | | 403 Forbidden | Check Azure AD permissions/API scopes | | AADSTS700016 | App not found in tenant → verify Tenant ID |
Feedback loop: Fix credentials → Re-run Step 3 checkpoint → Confirm token acquired before API calls.
References
See references/ folder for:
oauth-patterns.md- Complete code patternstoken-management.md- Token caching strategiesazure-setup.md- Azure AD App Registration guidetroubleshooting.md- Common errors and solutions
External Documentation
- Microsoft: OAuth authentication for BC Web Services
- Microsoft: S2S Authentication
- Codeunit 501 OAuth2
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: emgreppi
- Source: emgreppi/Business-Central-AI-Skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.