{"record":{"id":"35cdce0cc9fd721f","repo":"JeffreySu/WeiXinMPSDK","slug":"appsecret-accesstokencontainer","errorCode":null,"errorMessage":"凭据提供器返回了空 AppSecret。","messagePattern":"凭据提供器返回了空 AppSecret。","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"critical","filePath":"src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Containers/AccessTokenContainer.cs","lineNumber":217,"sourceCode":"            string name = null,\n            CancellationToken cancellationToken = default)\n        {\n            if (string.IsNullOrWhiteSpace(wxOpenAppId))\n            {\n                throw new ArgumentException(\"AppId 不能为空。\", nameof(wxOpenAppId));\n            }\n\n            if (credentialProvider == null)\n            {\n                throw new ArgumentNullException(nameof(credentialProvider));\n            }\n\n            async Task<AccessTokenBag> RegisterCoreAsync(CancellationToken token)\n            {\n                var secret = await credentialProvider.GetSecretAsync(wxOpenAppId, token).ConfigureAwait(false);\n                if (string.IsNullOrWhiteSpace(secret))\n                {\n                    throw new InvalidOperationException(\"凭据提供器返回了空 AppSecret。\");\n                }\n\n                var bag = new AccessTokenBag\n                {\n                    Name = name,\n                    WxOpenAppId = wxOpenAppId,\n                    WxOpenAppSecret = secret,\n                    AccessTokenExpireTime = DateTimeOffset.MinValue,\n                    AccessTokenResult = new AccessTokenResult()\n                };\n                await UpdateAsync(wxOpenAppId, bag, null).ConfigureAwait(false);\n                return bag;\n            }\n\n            SetRegistrationCallback(wxOpenAppId, () => RegisterCoreAsync(CancellationToken.None));\n            cancellationToken.ThrowIfCancellationRequested();\n            await RegisterCoreAsync(cancellationToken).ConfigureAwait(false);\n        }","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Containers/AccessTokenContainer.cs#L199-L235","documentation":"Thrown by RegisterWithCredentialProviderAsync when the registered ICredentialProvider's GetSecretAsync returns a null/whitespace AppSecret for the given wxOpenAppId. The container cannot obtain an access token without a secret, so registration aborts with InvalidOperationException instead of silently caching a broken bag. This indicates the credential provider is misconfigured or does not hold a secret for that AppId.","triggerScenarios":"Calling AccessTokenContainer.RegisterWithCredentialProviderAsync (via Register when a credential provider is configured) where credentialProvider.GetSecretAsync(appId) returns null, empty, or whitespace — e.g. the provider's backing store lacks an entry for the wxOpenAppId.","commonSituations":"AppId typo or AppSecret never added to the credential provider store (config file, key vault, database); secret removed/rotated out of the provider before app start; environment-specific config where staging store has no production AppId.","solutions":["Verify the credential provider actually contains a non-empty AppSecret for the exact wxOpenAppId passed to Register","Fix the provider's data source / configuration so GetSecretAsync returns the real secret","Log the appId inside GetSecretAsync to confirm which key is being looked up and correct the caller","Fall back to Register(appId, appSecret) direct registration if a credential provider is not required"],"exampleFix":"// before\nawait AccessTokenContainer.RegisterWithCredentialProviderAsync(serviceProvider, appId, name);\n// throws: credential provider has no secret for appId\n// after\n// ensure the provider returns a secret, or register directly:\nawait AccessTokenContainer.RegisterAsync(serviceProvider, appId, actualAppSecret, name);","handlingStrategy":"validation","validationCode":"// before registering\nvar secret = await credentialProvider.GetSecretAsync(wxOpenAppId);\nif (string.IsNullOrWhiteSpace(secret))\n    throw new InvalidOperationException($\"Credential provider has no AppSecret for {wxOpenAppId}; fix provider config first.\");","typeGuard":"bool HasSecret(string secret) => !string.IsNullOrWhiteSpace(secret);","tryCatchPattern":"try {\n    await AccessTokenContainer.RegisterWithCredentialProviderAsync(sp, appId, name);\n} catch (InvalidOperationException ex) when (ex.Message.Contains(\"AppSecret\")) {\n    logger.LogError(ex, \"Credential provider returned empty AppSecret for {AppId}\", appId);\n    throw; // fail fast at startup\n}","preventionTips":["Store every wxOpenAppId/AppSecret pair in the credential provider before deployment","Add a startup health check that resolves each AppSecret from the provider","Keep AppId/secret config in environment-specific files and validate on boot","Prefer direct Register(appId, appSecret) when no provider store is needed"],"tags":["weixin","wxopen","credentials","configuration","appsecret"],"backgroundTag":"missing-credentials","analyzedSha":"be573f6f94bdbf718dd5f6cdecb137fbc7ff651e","analyzedAt":"2026-09-12T10:01:50.733Z","contentChangedAt":"2026-09-12T10:01:50.733Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}