Disable Greenfield Basic Auth by default after 5 min of user creation (#7492)
What changed, and why it matters
This change makes BTCPay Server's Greenfield API stop accepting username-and-password 'Basic' authentication by default for existing users. New accounts can still use it for only the first five minutes after creation to set up an API key. After that window, Basic auth is blocked unless the user explicitly turns it on in their profile. The goal is to reduce the risk of password-based attacks on API access.
Review whether the 5-minute grace period and the opt-in flag provide sufficient protection for your deployment. Ensure API consumers that relied on persistent Basic auth are migrated to API keys, since existing users will lose Basic auth access after upgrading unless they explicitly enable it. Monitor for any bypass of the Created-date check or flag enforcement.
Security signals we found
Disables Basic authentication by default after a 5-minute onboarding window
Adds per-user opt-in flag for Basic auth in data model, API, and UI
Tightens rate-limit bypass to require a recently created account
Updates Swagger documentation to describe the new behavior
Adds unit tests verifying the flag toggles Basic auth access
Evidence from the diff
The commit adds an AllowGreenfieldBasicAuth flag to the user blob/model and exposes it in the UI, Greenfield users API, and Swagger docs. BasicAuthenticationHandler now checks the flag (plus a 5-minute new-account grace period) before allowing Basic auth. Rate-limiting logic is also tightened: throttling is skipped only for accounts created within the last 5 minutes, rather than for any account with a missing Created date. The change is a hardening/default-change patch, not a fix for a specific reported vulnerability.
Changed components
BTCPayServer.Security.Greenfield.BasicAuthenticationHandlerBTCPayServer.Controllers.GreenField.GreenfieldUsersControllerBTCPayServer.Controllers.UIManageControllerBTCPayServer.Services.UserServiceBTCPayServer.Client.Models.ApplicationUserDataBTCPayServer.Client.Models.UpdateApplicationUserRequestBTCPayServer.Data.Data.ApplicationUserBTCPayServer.Views.UIManage.Index.cshtmlGreenfield API authentication flowInspect captured patch +74 / −6
diff --git a/BTCPayServer.Client/Models/ApplicationUserData.cs b/BTCPayServer.Client/Models/ApplicationUserData.cs
index f1d3164..0b92cc3 100644
--- a/BTCPayServer.Client/Models/ApplicationUserData.cs
+++ b/BTCPayServer.Client/Models/ApplicationUserData.cs
@@ -70,6 +70,11 @@ namespace BTCPayServer.Client.Models
/// </summary>
public int? StoreQuota { get; set; }
+ /// <summary>
+ /// whether Basic authentication is allowed for the Greenfield API
+ /// </summary>
+ public bool AllowGreenfieldBasicAuth { get; set; }
+
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
}
diff --git a/BTCPayServer.Client/Models/UpdateApplicationUserRequest.cs b/BTCPayServer.Client/Models/UpdateApplicationUserRequest.cs
index 0a48b7e..fe063fb 100644
--- a/BTCPayServer.Client/Models/UpdateApplicationUserRequest.cs
+++ b/BTCPayServer.Client/Models/UpdateApplicationUserRequest.cs
@@ -26,5 +26,9 @@ public class UpdateApplicationUserRequest
/// new password of the user
/// </summary>
public string NewPassword { get; set; }
-}
+ /// <summary>
+ /// whether to allow Basic authentication for the Greenfield API
+ /// </summary>
+ public bool? AllowGreenfieldBasicAuth { get; set; }
+}
diff --git a/BTCPayServer.Data/Data/ApplicationUser.cs b/BTCPayServer.Data/Data/ApplicationUser.cs
index 90b12a7..578a19b 100644
--- a/BTCPayServer.Data/Data/ApplicationUser.cs
+++ b/BTCPayServer.Data/Data/ApplicationUser.cs
@@ -62,5 +62,6 @@ namespace BTCPayServer.Data
public string Name { get; set; }
public string InvitationToken { get; set; }
public int? StoreQuota { get; set; }
+ public bool AllowGreenfieldBasicAuth { get; set; }
}
}
diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs
index cf5958c..400fe2e 100644
--- a/BTCPayServer.Tests/GreenfieldAPITests.cs
+++ b/BTCPayServer.Tests/GreenfieldAPITests.cs
@@ -1587,6 +1587,27 @@ namespace BTCPayServer.Tests
WHERE "Id"=@id
""", new{ id = user.UserId });
+ var basicAuthDisabled = await AssertEx.AssertApiError(401, "unauthenticated",
+ () => clientBasic.GetCurrentUser());
+ Assert.Contains("not enabled", basicAuthDisabled.Message);
+
+ var updatedUser = await clientProfile.UpdateCurrentUser(new UpdateApplicationUserRequest
+ {
+ AllowGreenfieldBasicAuth = true
+ });
+ Assert.True(updatedUser.AllowGreenfieldBasicAuth);
+ Assert.True((await clientBasic.GetCurrentUser()).AllowGreenfieldBasicAuth);
+
+ updatedUser = await clientProfile.UpdateCurrentUser(new UpdateApplicationUserRequest
+ {
+ AllowGreenfieldBasicAuth = false
+ });
+ Assert.False(updatedUser.AllowGreenfieldBasicAuth);
+ await clientProfile.UpdateCurrentUser(new UpdateApplicationUserRequest
+ {
+ AllowGreenfieldBasicAuth = true
+ });
+
var err = await AssertEx.AssertApiError(401, "unauthenticated", async () =>
{
for (var i = 0; i < 10; i++)
diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs
index 5809a55..02a5735 100644
--- a/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs
+++ b/BTCPayServer/Controllers/GreenField/GreenfieldUsersController.cs
@@ -204,6 +204,13 @@ namespace BTCPayServer.Controllers.Greenfield
}
var blob = user.GetBlob() ?? new();
+ if (request.AllowGreenfieldBasicAuth is { } allowGreenfieldBasicAuth &&
+ allowGreenfieldBasicAuth != blob.AllowGreenfieldBasicAuth)
+ {
+ blob.AllowGreenfieldBasicAuth = allowGreenfieldBasicAuth;
+ needUpdate = true;
+ }
+
if (request.Name is not null && request.Name != blob.Name)
{
blob.Name = request.Name;
diff --git a/BTCPayServer/Controllers/UIManageController.cs b/BTCPayServer/Controllers/UIManageController.cs
index 9f66e32..a7de06f 100644
--- a/BTCPayServer/Controllers/UIManageController.cs
+++ b/BTCPayServer/Controllers/UIManageController.cs
@@ -95,7 +95,8 @@ namespace BTCPayServer.Controllers
Name = blob.Name,
ImageUrl = string.IsNullOrEmpty(blob.ImageUrl) ? null : await _uriResolver.Resolve(Request.GetAbsoluteRootUri(), UnresolvedUri.Create(blob.ImageUrl)),
EmailConfirmed = user.EmailConfirmed,
- RequiresEmailConfirmation = user.RequiresEmailConfirmation
+ RequiresEmailConfirmation = user.RequiresEmailConfirmation,
+ AllowGreenfieldBasicAuth = blob.AllowGreenfieldBasicAuth
};
return View(model);
}
@@ -174,6 +175,12 @@ namespace BTCPayServer.Controllers
}
var blob = user.GetBlob() ?? new();
+ if (blob.AllowGreenfieldBasicAuth != model.AllowGreenfieldBasicAuth)
+ {
+ blob.AllowGreenfieldBasicAuth = model.AllowGreenfieldBasicAuth;
+ needUpdate = true;
+ }
+
if (blob.Name != model.Name)
{
blob.Name = model.Name;
diff --git a/BTCPayServer/Models/ManageViewModels/IndexViewModel.cs b/BTCPayServer/Models/ManageViewModels/IndexViewModel.cs
index dcadaf4..10a8255 100644
--- a/BTCPayServer/Models/ManageViewModels/IndexViewModel.cs
+++ b/BTCPayServer/Models/ManageViewModels/IndexViewModel.cs
@@ -18,5 +18,8 @@ namespace BTCPayServer.Models.ManageViewModels
[Display(Name = "Profile Picture")]
public IFormFile ImageFile { get; set; }
public string ImageUrl { get; set; }
+
+ [Display(Name = "Allow Basic authentication for Greenfield API")]
+ public bool AllowGreenfieldBasicAuth { get; set; }
}
}
diff --git a/BTCPayServer/Security/GreenField/BasicAuthenticationHandler.cs b/BTCPayServer/Security/GreenField/BasicAuthenticationHandler.cs
index b5bbddd..a70def3 100644
--- a/BTCPayServer/Security/GreenField/BasicAuthenticationHandler.cs
+++ b/BTCPayServer/Security/GreenField/BasicAuthenticationHandler.cs
@@ -59,9 +59,10 @@ namespace BTCPayServer.Security.Greenfield
.FirstOrDefaultAsync(applicationUser =>
applicationUser.NormalizedUserName == userManager.NormalizeName(username));
- // We disable throttling for new accounts to give time to create API keys via greenfield API.
- if (user?.Created is not {} created ||
- (DateTimeOffset.UtcNow - created) > TimeSpan.FromMinutes(5))
+ // Give new accounts time to create API keys via the Greenfield API.
+ TimeSpan? accountAge = user?.Created is { } created ? DateTimeOffset.UtcNow - created : null;
+ var isNewAccount = accountAge >= TimeSpan.Zero && accountAge <= TimeSpan.FromMinutes(5);
+ if (!isNewAccount)
{
if (Context.Connection.RemoteIpAddress?.ToString() is string ip)
if (!await rateLimitService.Throttle(ZoneLimits.Login, ip))
@@ -75,6 +76,8 @@ namespace BTCPayServer.Security.Greenfield
}
if (user is null)
return Fail($"Basic authentication failed");
+ if (!isNewAccount && user.GetBlob()?.AllowGreenfieldBasicAuth is not true)
+ return Fail("Basic authentication for the Greenfield API is not enabled for this user.");
if (await signInManager.IsTwoFactorEnabledAsync(user))
{
return Fail("Cannot use Basic authentication when multi-factor is enabled.");
diff --git a/BTCPayServer/Services/UserService.cs b/BTCPayServer/Services/UserService.cs
index fb780d8..824b534 100644
--- a/BTCPayServer/Services/UserService.cs
+++ b/BTCPayServer/Services/UserService.cs
@@ -86,6 +86,7 @@ namespace BTCPayServer.Services
Roles = roles,
Disabled = data.IsDisabled,
StoreQuota = blob.StoreQuota,
+ AllowGreenfieldBasicAuth = blob.AllowGreenfieldBasicAuth,
ImageUrl = string.IsNullOrEmpty(blob.ImageUrl)
? null
: await uriResolver.Resolve(request.GetAbsoluteRootUri(), UnresolvedUri.Create(blob.ImageUrl)),
diff --git a/BTCPayServer/Views/UIManage/Index.cshtml b/BTCPayServer/Views/UIManage/Index.cshtml
index 57aa82a..ae8ea88 100644
--- a/BTCPayServer/Views/UIManage/Index.cshtml
+++ b/BTCPayServer/Views/UIManage/Index.cshtml
@@ -67,6 +67,13 @@
<span asp-validation-for="ImageFile" class="text-danger"></span>
</div>
}
+ <div class="form-group">
+ <div class="form-check">
+ <input asp-for="AllowGreenfieldBasicAuth" class="form-check-input" />
+ <label asp-for="AllowGreenfieldBasicAuth" class="form-check-label"></label>
+ </div>
+ <small class="text-muted" text-translate="true">Basic authentication is less secure than API keys. New users can use it for five minutes to create an API key without enabling this setting.</small>
+ </div>
<h3 class="mt-5 mb-4" text-translate="true">Delete Account</h3>
<div id="danger-zone">
<a id="delete-user" asp-action="DeleteUserPost" class="btn btn-outline-danger mb-5" data-confirm-input="@StringLocalizer["Delete"]" data-bs-toggle="modal" data-bs-target="#ConfirmModal" text-translate="true">Delete Account</a>
diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.json
index 03c0ef8..d8b7249 100644
--- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.json
+++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.json
@@ -222,7 +222,7 @@
},
"Basic": {
"type": "http",
- "description": "BTCPay Server supports authenticating and authorizing users through the Basic HTTP authentication scheme. Send the user and password encoded in base64 with the format `Basic {base64(username:password)}`. Using this authentication method implicitly provides you with the `unrestricted` permission",
+ "description": "BTCPay Server supports authenticating and authorizing users through the Basic HTTP authentication scheme during the first five minutes after account creation or when the user explicitly enables it. Send the user and password encoded in base64 with the format `Basic {base64(username:password)}`. Using this authentication method implicitly provides you with the `unrestricted` permission",
"scheme": "Basic"
}
}
diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.users.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.users.json
index 6486cc4..35fe1c1 100644
--- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.users.json
+++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.users.json
@@ -70,6 +70,11 @@
"type": "string",
"description": "The new password of the user",
"nullable": true
+ },
+ "allowGreenfieldBasicAuth": {
+ "type": "boolean",
+ "description": "Whether to allow Basic authentication for the Greenfield API",
+ "nullable": true
}
}
}
@@ -566,6 +571,10 @@
"type": "boolean",
"description": "True if an admin has disabled the user"
},
+ "allowGreenfieldBasicAuth": {
+ "type": "boolean",
+ "description": "Whether Basic authentication is allowed for the Greenfield API"
+ },
"roles": {
"type": "array",
"nullable": false,
Why this scored 62/100
Evidence and disclosure record
Verified links used to place this patch in context. External claims remain attributed to their publishers.
Greenfield: Disable Basic authentication by default
Vendor-authored defense-in-depth follow-up that disables Basic authentication by default five minutes after account creation to reduce the impact of bugs such as the TOTP bypass fixed in PR #7491.
BTCPay Server 2.4.2
Vendor release notes ship this hardening change alongside the critical, actively exploited TOTP-bypass fix and urge immediate upgrades.
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.