{"record":{"id":"95abe985b286ca10","repo":"elsa-workflows/elsa-core","slug":"external-authentication-handle-hashing-settings-are-required","errorCode":null,"errorMessage":"External Authentication handle-hashing settings are required.","messagePattern":"External Authentication handle-hashing settings are required\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication/Services/HmacExternalAuthenticationHandleHasher.cs","lineNumber":44,"sourceCode":"    /// </summary>\n    public HmacExternalAuthenticationHandleHasher(IOptions<ExternalAuthenticationOptions> options)\n        : this(GetKey(options.Value.HandleHashing))\n    {\n    }\n\n    private HmacExternalAuthenticationHandleHasher(byte[] key)\n    {\n        _key = key;\n    }\n\n    public string Hash(string value) => Convert.ToHexString(HMACSHA256.HashData(_key, Encoding.UTF8.GetBytes(value)));\n\n    public void Dispose() => CryptographicOperations.ZeroMemory(_key);\n\n    private static byte[] GetKey(ExternalAuthenticationHandleHashingOptions? options)\n    {\n        if (options is null)\n            throw new InvalidOperationException(\"External Authentication handle-hashing settings are required.\");\n\n        if (string.IsNullOrWhiteSpace(options.SharedKeyBase64))\n            return RandomNumberGenerator.GetBytes(32);\n\n        try\n        {\n            var key = Convert.FromBase64String(options.SharedKeyBase64);\n            if (key.Length >= 32)\n                return key;\n\n            CryptographicOperations.ZeroMemory(key);\n        }\n        catch (FormatException)\n        {\n            // The options validator reports the actionable configuration error at startup.\n        }\n\n        throw new InvalidOperationException(\"The External Authentication shared handle-hashing key must be valid base64 containing at least 32 bytes.\");","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication/Services/HmacExternalAuthenticationHandleHasher.cs#L26-L62","documentation":"HmacExternalAuthenticationHandleHasher derives its HMAC-SHA-256 key from Elsa.ExternalAuthenticationOptions.HandleHashing at construction. When the bound HandleHashing section is null — meaning the options object was built without going through options binding/validation — GetKey refuses to run and throws this InvalidOperationException. The library throws instead of silently falling back so handle hashing is never keyed by an unexpected default.","triggerScenarios":"Constructing HmacExternalAuthenticationHandleHasher with IOptions<ExternalAuthenticationOptions> whose Value.HandleHashing is null, i.e. registering the hasher manually (e.g. services.AddSingleton<IExternalAuthenticationHandleHasher, HmacExternalAuthenticationHandleHasher>()) without binding the ExternalAuthentication options section, or calling the IOptions constructor in a test with an empty ExternalAuthenticationOptions instance.","commonSituations":"Hand-wiring the hasher in DI without services.AddOptions<ExternalAuthenticationOptions>().BindConfiguration(...); unit tests newing up IOptionsMock with Options.Create(new ExternalAuthenticationOptions()); a missing or misnamed configuration section so the HandleHashing property is never populated; upgrading Elsa versions where options wiring changed and custom DI registration no longer re-binds the section.","solutions":["Register the hasher through the module's standard service-registration extension (AddExternalAuthenticationCore / feature plumbing) so ExternalAuthenticationOptions is bound and validated by options infrastructure.","Bind the configuration explicitly: services.AddOptions<ExternalAuthenticationOptions>().BindConfiguration(\"ExternalAuthentication\") (or your section name) before the hasher resolves.","In tests, populate the options: Options.Create(new ExternalAuthenticationOptions { HandleHashing = new ExternalAuthenticationHandleHashingOptions { SharedKeyBase64 = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)) } }) — or use the parameterless constructor meant for tests/dev.","Verify the configuration section name matches (\"ExternalAuthentication:HandleHashing\") and the values are loaded in the environment being run."],"exampleFix":"// before\nservices.AddSingleton<IExternalAuthenticationHandleHasher, HmacExternalAuthenticationHandleHasher>();\n\n// after\nservices.AddOptions<ExternalAuthenticationOptions>()\n    .BindConfiguration(\"ExternalAuthentication\");\nservices.AddSingleton<IExternalAuthenticationHandleHasher, HmacExternalAuthenticationHandleHasher>();","handlingStrategy":"validation","validationCode":"// before constructing the hasher\nvar opts = serviceProvider.GetRequiredService<IOptions<ExternalAuthenticationOptions>>().Value;\nif (opts.HandleHashing is null)\n    throw new InvalidOperationException(\"Bind ExternalAuthentication:HandleHashing before registering the handle hasher.\");","typeGuard":"static bool HasHandleHashing(ExternalAuthenticationOptions options) => options?.HandleHashing is not null;","tryCatchPattern":"try\n{\n    var hasher = new HmacExternalAuthenticationHandleHasher(optionsAccessor);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"handle-hashing settings are required\"))\n{\n    logger.LogError(ex, \"ExternalAuthentication:HandleHashing options section is not bound.\");\n    throw; // fail fast — hashing with defaults would break handle lookups across restarts\n}","preventionTips":["Always register the hasher via the module's service-registration extension instead of manual AddSingleton.","Call BindConfiguration/ValidateDataAnnotations on ExternalAuthenticationOptions in every host that uses External Authentication.","In tests, use Options.Create with a fully populated ExternalAuthenticationOptions or the parameterless test constructor.","Add a startup health check that asserts options binding succeeded before serving traffic."],"tags":["configuration","csharp","dependency-injection","options-validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}