In the process of converting from .NET 4.8 to .NET 6. Ran into an issue when attempting to authenticate sign-in with Identity Server 4.
I get to the log-in page, I log-in to the auth, but don't get redirected correctly it seems.
Everything seems to work fine until AFTER the OnAuthorizationCodeReceived is finished while configuring SetOpenIdConnectOptions.
The Exception:
OpenIdConnectProtocolException: Message contains error: 'invalid_grant', error_description: 'error_description is null', error_uri: 'error_uri is null'. Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest)
Here is the LOG:
2022-09-26 14:33:56.602 -08:00 [DBG] local found in database: true
2022-09-26 14:33:56.602 -08:00 [DBG] client configuration validation for client local succeeded.
2022-09-26 14:33:56.602 -08:00 [DBG] Secret validator success: HashedSharedSecretValidator
2022-09-26 14:33:56.602 -08:00 [DBG] Client validation success
2022-09-26 14:33:56.602 -08:00 [INF] {"ClientId":"local","AuthenticationMethod":"SharedSecret","Category":"Authentication","Name":"Client Authentication Success","EventType":"Success","Id":1010,"Message":null,"ActivityId":"8000022c-0000-fc00-b63f-84710c7967bb","TimeStamp":"2022-09-26T22:33:56.0000000Z","ProcessId":16860,"LocalIpAddress":"::1:44318","RemoteIpAddress":"::1","$type":"ClientAuthenticationSuccessEvent"}
2022-09-26 14:33:56.602 -08:00 [DBG] Start token request validation
2022-09-26 14:33:56.603 -08:00 [DBG] Start client credentials token request validation
2022-09-26 14:33:56.604 -08:00 [DBG] Found [] identity scopes in database
2022-09-26 14:33:56.606 -08:00 [DBG] Found ["Api"] API resources in database
2022-09-26 14:33:56.608 -08:00 [DBG] Found ["MY.API"] scopes in database
2022-09-26 14:33:56.608 -08:00 [DBG] local credentials token request validation success
2022-09-26 14:33:56.608 -08:00 [INF] Token request validation success, {"ClientId":"local","ClientName":"(Local)","GrantType":"client_credentials","Scopes":"MY.API","AuthorizationCode":"*******","RefreshToken":"*******","UserName":null,"AuthenticationContextReferenceClasses":null,"Tenant":null,"IdP":null,"Raw":{"grant_type":"client_credentials","scope":"MY.API","client_id":"local","client_secret":"***REDACTED***"},"$type":"TokenRequestValidationLog"}
2022-09-26 14:33:56.608 -08:00 [DBG] Getting claims for access token for client: local
2022-09-26 14:33:56.609 -08:00 [INF] {"ClientId":"local","ClientName":"Local","RedirectUri":null,"Endpoint":"Token","SubjectId":null,"Scopes":"MY.API","GrantType":"client_credentials","Tokens":[{"TokenType":"access_token","TokenValue":"****KMHw","$type":"Token"}],"Category":"Token","Name":"Token Issued Success","EventType":"Success","Id":2000,"Message":null,"ActivityId":"8000022c-0000-fc00-b63f-84710c7967bb","TimeStamp":"2022-09-26T22:33:56.0000000Z","ProcessId":16860,"LocalIpAddress":"::1:44318","RemoteIpAddress":"::1","$type":"TokenIssuedSuccessEvent"}
2022-09-26 14:33:56.609 -08:00 [DBG] Token request success.
2022-09-26 14:33:57.683 -08:00 [DBG] Request path /connect/token matched to endpoint type Token
2022-09-26 14:33:57.683 -08:00 [DBG] Endpoint enabled: Token, successfully created handler: IdentityServer4.Endpoints.TokenEndpoint
2022-09-26 14:33:57.683 -08:00 [INF] Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token
2022-09-26 14:33:57.683 -08:00 [DBG] Start token request.
2022-09-26 14:33:57.683 -08:00 [DBG] Start client validation
2022-09-26 14:33:57.683 -08:00 [DBG] Start parsing Basic Authentication secret
2022-09-26 14:33:57.683 -08:00 [DBG] Start parsing for secret in post body
2022-09-26 14:33:57.683 -08:00 [DBG] Parser found secret: PostBodySecretParser
2022-09-26 14:33:57.683 -08:00 [DBG] Secret id found: local
2022-09-26 14:33:57.693 -08:00 [DBG] local found in database: true
2022-09-26 14:33:57.693 -08:00 [DBG] client configuration validation for client local succeeded.
2022-09-26 14:33:57.693 -08:00 [DBG] Secret validator success: HashedSharedSecretValidator
2022-09-26 14:33:57.693 -08:00 [DBG] Client validation success
2022-09-26 14:33:57.693 -08:00 [INF] {"ClientId":"local","AuthenticationMethod":"SharedSecret","Category":"Authentication","Name":"Client Authentication Success","EventType":"Success","Id":1010,"Message":null,"ActivityId":"80000989-0000-ee00-b63f-84710c7967bb","TimeStamp":"2022-09-26T22:33:57.0000000Z","ProcessId":16860,"LocalIpAddress":"::1:44318","RemoteIpAddress":"::1","$type":"ClientAuthenticationSuccessEvent"}
2022-09-26 14:33:57.693 -08:00 [DBG] Start token request validation
2022-09-26 14:33:57.693 -08:00 [DBG] Start validation of authorization code token request
2022-09-26 14:33:57.694 -08:00 [DBG] pfmbWJpdPbS0PunlzI8nAcGvays25N/W0z/Rlg5idOU= found in database: false
2022-09-26 14:33:57.695 -08:00 [DBG] authorization_code grant with value: 7EBFC1FBBB972962CACE378660D3E258C812B170DB496347289E19960C400FA9 not found in store.
2022-09-26 14:33:57.695 -08:00 [ERR] Invalid authorization code{"code":"7EBFC1FBBB972962CACE378660D3E258C812B170DB496347289E19960C400FA9"}, details: {"ClientId":"local","ClientName":"Local","GrantType":"authorization_code","Scopes":null,"AuthorizationCode":"****0FA9","RefreshToken":"********","UserName":null,"AuthenticationContextReferenceClasses":null,"Tenant":null,"IdP":null,"Raw":{"client_id":"local","client_secret":"***REDACTED***","code":"7EBFC1FBBB972962CACE378660D3E258C812B170DB496347289E19960C400FA9","grant_type":"authorization_code","redirect_uri":"https://localhost:49611/signin-oidc"},"$type":"TokenRequestValidationLog"}
2022-09-26 14:33:57.695 -08:00 [INF] {"ClientId":"local","ClientName":"Loca","RedirectUri":null,"Endpoint":"Token","SubjectId":null,"Scopes":null,"GrantType":"authorization_code","Error":"invalid_grant","ErrorDescription":null,"Category":"Token","Name":"Token Issued Failure","EventType":"Failure","Id":2001,"Message":null,"ActivityId":"80000989-0000-ee00-b63f-84710c7967bb","TimeStamp":"2022-09-26T22:33:57.0000000Z","ProcessId":16860,"LocalIpAddress":"::1:44318","RemoteIpAddress":"::1","$type":"TokenIssuedFailureEvent"}
My StartUp class:
namespace Test
{
public class Startup
{
public IConfiguration Configuration { get; }
public string MembershipRole1 { get; private set; }
public string MembershipRole2 { get; private set; }
public string MembershipRole3 { get; private set; }
public string MembershipRoleDenyLogin { get; private set; }
private string Authority { get; set; }
private string ClientId { get; set; }
private string ClientSecret { get; set; }
private string SignedOutRedirectUri { get; set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
MembershipRole1 = Configuration["MembershipRole1"]?.ToString() ?? "";
MembershipRole2 = Configuration["MembershipRole2"]?.ToString() ?? "";
MembershipRole3 = Configuration["MembershipRole3"]?.ToString() ?? "";
MembershipRoleDenyLogin = Configuration["MembershipRoleDenyLogin"]?.ToString() ?? "";
Authority = Configuration["ServerUrl"]?.ToString() ?? "";
ClientId = Configuration["ClientId"]?.ToString() ?? ""; ;
ClientSecret = Configuration["ClientSecret"]?.ToString() ?? ""; ;
SignedOutRedirectUri = Configuration["ReturnUrl"]?.ToString() ?? "";
}
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimFilter.Clear();
services.AddMemoryCache();
services.AddRazorPages();
services.AddHttpClient();
services.AddSession();
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(configureOptions =>
{
configureOptions.ExpireTimeSpan = TimeSpan.FromMinutes(15);
configureOptions.SlidingExpiration = true;
})
.AddOpenIdConnect("oidc", options => SetOpenIdConnectOptions(options));
services.AddControllersWithViews(ConfigureMvcOptions);
services.AddHttpContextAccessor(); //Maybe needs this?
}
private void SetOpenIdConnectOptions(OpenIdConnectOptions options)
{
options.SaveTokens = true;
options.Authority = Authority;
options.ClientId = ClientId;
options.ClientSecret = ClientSecret;
options.SignedOutRedirectUri = SignedOutRedirectUri;
options.ResponseType = "code id_token";
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = ClaimTypes.Name,
RoleClaimType = ClaimTypes.Role
};
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("MY.API");
options.Scope.Add("offline_access");
options.Scope.Add("profile");
options.RequireHttpsMetadata = true;
options.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async n =>
{
TokenClientOptions myOptions = new()
{
ClientId = "local",
ClientSecret = "******",
Address = "https://localhost:44318/connect/token"
};
var client = new HttpClient();
var tokenClient = new TokenClient(client, myOptions);
var tokenResponse = await tokenClient.RequestAuthorizationCodeTokenAsync(n.TokenEndpointRequest.Code, n.TokenEndpointRequest.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var userInfoResponse = await client.GetUserInfoAsync(new UserInfoRequest
{
Address = "https://localhost:44318/connect/userinfo", //UserInfoEndPoint ToDo: Access via appsettings
Token = tokenResponse.AccessToken
});
if(userInfoResponse.IsError)
{
throw new Exception("Problem while fetching data from the UserInfo endpoint", userInfoResponse.Exception);
}
Claim subject = userInfoResponse.Claims.FirstOrDefault(x => x.Type.Equals("sub"));
var id = new ClaimsIdentity(n.Principal.Identity.AuthenticationType);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.Principal.FindFirst("sid").Value));
id.AddClaim(new Claim("session_guid", string.Empty));
ClaimsIdentity claimsIdentity = new ClaimsIdentity(id.Claims, n.Principal.Identity.AuthenticationType, ClaimTypes.Name, ClaimTypes.Role);
n.Principal.AddIdentity(claimsIdentity);
},
OnRedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
}
else if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var idTokenHint = n.HttpContext.User.FindFirst("id_token");//n.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
};
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseCookiePolicy(new CookiePolicyOptions()
{
MinimumSameSitePolicy = SameSiteMode.Lax,
Secure = CookieSecurePolicy.Always
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
string showPII = Configuration["ShowPII"]?.ToLower() ?? "";
if (showPII == "true")
{
IdentityModelEventSource.ShowPII = true;
}
JwtSecurityTokenHandler.DefaultInboundClaimFilter.Clear();
app.UseSession();
AppDomain.CurrentDomain.SetData("CurrentRootPath", env.ContentRootPath);
AppDomain.CurrentDomain.SetData("WebRootPath", env.WebRootPath);
}
}
I don't understand why the final 'authorization_code" GrantType is being called prior to the OnAuthorizationCodeReceived piece being called, I am, not calling any other request auths in my code either. All of the previous grants work just fine. For example, the log for var tokenResponse = await tokenClient.RequestAuthorizationCodeTokenAsync(n.TokenEndpointRequest.Code, n.TokenEndpointRequest.RedirectUri);
shows it completing successfully.
UPDATE: Further up on the logs, I see where the same validation code that was not found in the database, was found earlier and then removed from persisted grant from database:
Start validation of authorization code token request
2022-09-26 14:16:53.855 -08:00 [DBG] 7EBFC1FBBB972962CACE378660D3E258C812B170DB496347289E19960C400FA9 found in database: true
2022-09-26 14:16:53.857 -08:00 [DBG] removing 7EBFC1FBBB972962CACE378660D3E258C812B170DB496347289E19960C400FA9 persisted grant from database
2022-09-26 14:16:53.885 -08:00 [DBG] Request path /.well-known/openid-configuration matched to endpoint type Discovery
2022-09-26 14:16:53.885 -08:00 [DBG] Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryEndpoint
2022-09-26 14:16:53.885 -08:00 [INF] Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for /.well-known/openid-configuration
CodePudding user response:
This particular error was occurring because I needed to call n.HandleCodeRedemption(access_token,id_token)
at the end of OnAuthorizationCodeReceived. Once that was called, it no longer needed to request authorization, as I had already handled that with the RequestAuthorizationCodeTokenAsync
code above.
On another note for anyone reading this with a similar issue, I also added a OnTokenValidated
OpenIdConnectEvent, which basically handles the RequestAuthorizationCodeTokenAsync
call and the adding of the claims.