.NET MVC5集成AWS Cognito动态配置后出现Invalid_Client错误
我正在把AWS Cognito用户认证集成到.NET MVC5前端应用里。应用能正常重定向到Cognito域名完成登录,也能进入AuthorizationCodeReceived环节,但执行完该环节的Task.CompletedTask后,出现invalid_client错误,error_description为null。
只有在RedirectToIdentityProvider中动态分配ClientId和Authority时才会出问题,用默认ClientId和Authority时完全正常。
相关代码如下:
var userpoolId = "default-userpoolId"; var authority = $"https://cognito-idp.eu-west-1.amazonaws.com/{userpoolId}"; var authMetadata = $"{authority}/.well-known/openid-configuration"; var cognitoAuthority = "Cognito-Client-Domain"; app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { // 信任的认证源 Authority = authority, ClientId = clientId, MetadataAddress = authMetadata, // 暂时可能不需要 RedirectUri = "example.com", PostLogoutRedirectUri = "example.com", // PKCE流程,获取用户信息和引用令牌 ResponseType = OpenIdConnectResponseType.Code, ResponseMode = "query", // 所需权限:登录、用户角色、API访问 Scope = "openid profile email",// aws.cognito.signin.user.admin", // 将加密信息存储在登录Cookie中 SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType, // 禁用令牌生命周期自动处理,手动管理过期 UseTokenLifetime = false, SaveTokens = true, RedeemCode = true, ProtocolValidator = new OpenIdConnectProtocolValidator() { RequireNonce = false, RequireStateValidation = false, RequireState = false }, AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType, AuthenticationMode = AuthenticationMode.Active, TokenValidationParameters = new TokenValidationParameters { // 不使用默认的单一发行者验证,注入多租户验证逻辑 ValidateIssuer = false, }, Notifications = new OpenIdConnectAuthenticationNotifications { RedirectToIdentityProvider = async n => { var serverPathBase = $"https://{n.Request.Host}{GetPathBase(n.Request)}"; var instanceConnection = await InstanceCache.MapUrlToInstance(n.Request.Uri); cognitoAuthority = instanceConnection.CognitoAppClientDomain; authority = $"https://cognito-idp.eu-west-1.amazonaws.com/{instanceConnection.UserpoolId}"; authMetadata = $"{authority}/.well-known/openid-configuration"; Logging.Logger.Info($"Instance Connections are:{instanceConnection.UserpoolId} , {instanceConnection.CognitoAppClientDomain}, {instanceConnection.CognitoClientId}"); n.Options.Authority = authority; n.Options.ClientId = instanceConnection.CognitoClientId; n.ProtocolMessage.ClientId = instanceConnection.CognitoClientId; n.Options.MetadataAddress = authMetadata; if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout) { var postLogoutUri = $"{serverPathBase}"; n.ProtocolMessage.IssuerAddress = $"{cognitoAuthority}/logout?logout_uri={postLogoutUri}&client_id={clientId}"; n.OwinContext.Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType, OpenIdConnectAuthenticationDefaults.AuthenticationType); await Task.FromResult(0); } if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication) { n.ProtocolMessage.IssuerAddress = $"{cognitoAuthority}/login"; n.Options.MetadataAddress = $"{authority}/.well-known/openid-configuration"; (var startupId, var startupCookie) = LookForStarterInformation(n.Request); Logging.Logger.Info($"Mapped {n.Request.Uri} to {instanceConnection.InstanceName} for login"); n.ProtocolMessage.AcrValues = $"tenant:{instanceConnection.InstanceName}"; if (startupId.HasValue && !String.IsNullOrWhiteSpace(startupCookie)) { n.ProtocolMessage.AcrValues += $" startupId:{startupId} startupCookie:{startupCookie}"; } var redirectUri = $"{serverPathBase}/signin-oidc"; n.ProtocolMessage.RedirectUri = redirectUri; n.ProtocolMessage.PostLogoutRedirectUri = $"{serverPathBase}"; n.ProtocolMessage.ResponseMode = "query"; // PKCE code challenge生成 var codeVerifier = CryptoRandom.CreateUniqueId(32); string codeChallenge; using (var sha256 = SHA256.Create()) { var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier)); codeChallenge = Base64Url.Encode(challengeBytes); } n.ProtocolMessage.SetParameter("code_challenge", codeChallenge); n.ProtocolMessage.SetParameter("code_challenge_method", "S256"); Logging.Logger.Info("Code verifier is: " + codeVerifier); RememberCodeVerifier(n, codeVerifier); await Task.CompletedTask; } }, AuthorizationCodeReceived = async n => { Logging.Logger.Info("Authorization Code Received"); var instanceConnection = await InstanceCache.MapUrlToInstance(n.Request.Uri); var codeVerifier = RetrieveCodeVerifier(n); Logging.Logger.Info("Code verifier is: " + codeVerifier); var serverPathBase = $"https://{n.Request.Host}{GetPathBase(n.Request)}"; n.Options.ClientId = instanceConnection.CognitoClientId; n.ProtocolMessage.ClientId = instanceConnection.CognitoClientId; n.TokenEndpointRequest.RedirectUri = $"{serverPathBase}/signin-oidc"; n.ProtocolMessage.RedirectUri = $"{serverPathBase}/signin-oidc"; n.TokenEndpointRequest.SetParameter("code_verifier", codeVerifier); n.TokenEndpointRequest.SetParameter("client_id", instanceConnection.CognitoClientId); await Task.CompletedTask; } } });
问题排查与修复建议
统一ClientId传递
在AuthorizationCodeReceived中,确保n.Options.ClientId、n.ProtocolMessage.ClientId、n.TokenEndpointRequest中的client_id参数完全一致,且与Cognito用户池的应用客户端ID匹配。同步更新令牌请求的Authority配置
在AuthorizationCodeReceived中补充更新Authority和Metadata地址,避免令牌请求使用旧的配置:// 在AuthorizationCodeReceived方法内添加 var userPoolAuthority = $"https://cognito-idp.eu-west-1.amazonaws.com/{instanceConnection.UserpoolId}"; n.Options.Authority = userPoolAuthority; n.Options.MetadataAddress = $"{userPoolAuthority}/.well-known/openid-configuration";验证Cognito应用客户端配置
- 确认动态分配的ClientId对应的应用客户端已启用授权码流(Authorization Code Grant)。
- 检查该应用客户端的回调URL列表是否包含动态生成的
https://{你的域名}/signin-oidc,Cognito会拒绝不在列表内的回调请求。 - 如果应用客户端设置了客户端密钥,必须在令牌请求中添加
client_secret参数:n.TokenEndpointRequest.SetParameter("client_secret", instanceConnection.CognitoClientSecret);
避免全局变量并发覆盖
代码中使用的全局authority、cognitoAuthority变量在多请求并发时可能被覆盖,建议改用请求上下文存储这些值,或直接在通知方法内使用局部变量。日志排查参数正确性
在AuthorizationCodeReceived中添加日志,输出n.TokenEndpointRequest的所有参数,确认client_id、redirect_uri、code_verifier等参数是否正确传递给Cognito令牌端点。
内容的提问来源于stack exchange,提问作者Rutuja

