Fix passkey RPID mismatch across environments
Derive ServerDomain and Origin from the incoming HTTP request instead of appsettings.json, so WebAuthn works on localhost, dev, and production without any environment-specific configuration. Removed IFido2 from DI and the Fido2 appsettings block — PasskeyController instantiates Fido2 per-request via BuildFido2(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,11 +15,14 @@ namespace PowderCoating.Web.Controllers;
|
||||
/// Registration requires an authenticated session (user logs in once with password,
|
||||
/// then enrolls a passkey for future logins). Authentication is anonymous — the
|
||||
/// browser sends the credential before any session exists.
|
||||
///
|
||||
/// Fido2 is constructed per-request from the incoming Host header so the RPID
|
||||
/// matches automatically on localhost, dev, staging, and production without any
|
||||
/// environment-specific configuration.
|
||||
/// </summary>
|
||||
[Route("[controller]/[action]")]
|
||||
public class PasskeyController : Controller
|
||||
{
|
||||
private readonly IFido2 _fido2;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly ApplicationDbContext _db;
|
||||
@@ -29,19 +32,41 @@ public class PasskeyController : Controller
|
||||
private const string AuthChallengeKey = "passkey:auth:challenge";
|
||||
|
||||
public PasskeyController(
|
||||
IFido2 fido2,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
ApplicationDbContext db,
|
||||
ILogger<PasskeyController> logger)
|
||||
{
|
||||
_fido2 = fido2;
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_db = db;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a Fido2 instance whose RPID and origin are derived from the current
|
||||
/// request, so the same code works on localhost, dev, and production unchanged.
|
||||
/// </summary>
|
||||
private IFido2 BuildFido2()
|
||||
{
|
||||
var req = HttpContext.Request;
|
||||
var host = req.Host.Host; // "localhost" or "myapp.azurewebsites.net"
|
||||
var port = req.Host.Port;
|
||||
var origin = port.HasValue
|
||||
? $"{req.Scheme}://{host}:{port}"
|
||||
: $"{req.Scheme}://{host}";
|
||||
|
||||
var config = new Fido2Configuration
|
||||
{
|
||||
ServerDomain = host,
|
||||
ServerName = "Powder Coating Logix",
|
||||
Origins = new HashSet<string> { origin },
|
||||
TimestampDriftTolerance = 300
|
||||
};
|
||||
|
||||
return new Fido2(config);
|
||||
}
|
||||
|
||||
// ─── Registration ────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
@@ -77,7 +102,7 @@ public class PasskeyController : Controller
|
||||
UserVerification = UserVerificationRequirement.Required
|
||||
};
|
||||
|
||||
var options = _fido2.RequestNewCredential(new RequestNewCredentialParams
|
||||
var options = BuildFido2().RequestNewCredential(new RequestNewCredentialParams
|
||||
{
|
||||
User = fidoUser,
|
||||
ExcludeCredentials = excludeCredentials,
|
||||
@@ -112,7 +137,7 @@ public class PasskeyController : Controller
|
||||
try
|
||||
{
|
||||
var options = CredentialCreateOptions.FromJson(optionsJson);
|
||||
credential = await _fido2.MakeNewCredentialAsync(new MakeNewCredentialParams
|
||||
credential = await BuildFido2().MakeNewCredentialAsync(new MakeNewCredentialParams
|
||||
{
|
||||
AttestationResponse = attestationResponse,
|
||||
OriginalOptions = options,
|
||||
@@ -155,7 +180,7 @@ public class PasskeyController : Controller
|
||||
[AllowAnonymous]
|
||||
public IActionResult LoginOptions()
|
||||
{
|
||||
var options = _fido2.GetAssertionOptions(new GetAssertionOptionsParams
|
||||
var options = BuildFido2().GetAssertionOptions(new GetAssertionOptionsParams
|
||||
{
|
||||
AllowedCredentials = [],
|
||||
UserVerification = UserVerificationRequirement.Required
|
||||
@@ -199,7 +224,7 @@ public class PasskeyController : Controller
|
||||
try
|
||||
{
|
||||
var options = AssertionOptions.FromJson(optionsJson);
|
||||
verifyResult = await _fido2.MakeAssertionAsync(new MakeAssertionParams
|
||||
verifyResult = await BuildFido2().MakeAssertionAsync(new MakeAssertionParams
|
||||
{
|
||||
AssertionResponse = assertionResponse,
|
||||
OriginalOptions = options,
|
||||
|
||||
@@ -290,16 +290,9 @@ builder.Services.AddSession(options =>
|
||||
// Add memory cache
|
||||
builder.Services.AddMemoryCache();
|
||||
|
||||
// Register Fido2/WebAuthn for passkey (biometric) login
|
||||
builder.Services.AddFido2(options =>
|
||||
{
|
||||
options.ServerDomain = builder.Configuration["Fido2:ServerDomain"] ?? "localhost";
|
||||
options.ServerName = builder.Configuration["Fido2:ServerName"] ?? "Powder Coating Logix";
|
||||
var origins = builder.Configuration.GetSection("Fido2:Origins").Get<HashSet<string>>();
|
||||
if (origins?.Count > 0) options.Origins = origins;
|
||||
options.TimestampDriftTolerance = int.Parse(
|
||||
builder.Configuration["Fido2:TimestampDriftTolerance"] ?? "300");
|
||||
});
|
||||
// Fido2/WebAuthn: no DI registration needed — PasskeyController builds a
|
||||
// per-request Fido2 instance from the incoming Host header so the RPID matches
|
||||
// automatically on every environment without config changes.
|
||||
|
||||
// Configure authorization policies for multi-tenancy
|
||||
builder.Services.AddAuthorization(options =>
|
||||
|
||||
@@ -68,12 +68,6 @@
|
||||
"Enterprise": "price_enterprise_monthly_id_here"
|
||||
}
|
||||
},
|
||||
"Fido2": {
|
||||
"ServerDomain": "localhost",
|
||||
"ServerName": "Powder Coating Logix",
|
||||
"Origins": [ "https://localhost:58461", "http://localhost:58462" ],
|
||||
"TimestampDriftTolerance": 300
|
||||
},
|
||||
"Storage": {
|
||||
"ConnectionString": "DefaultEndpointsProtocol=https;AccountName=powdercoatingappdev;AccountKey=DN3eVfhytXb7aBC0md9h/6jE0Uzg6FJ+PK6MFc772qyqpf0kgTeXH0C2VCBBun9PiuItPd9CDKTP+ASthFCuCg==;EndpointSuffix=core.windows.net",
|
||||
"Containers": {
|
||||
|
||||
Reference in New Issue
Block a user