Closed beyondnagatani closed 1 year ago
This sounds very strange, as though your grant is not fully committed to the store before the authorize endpoint redirects back to the client. What implementation of the operational data stores are you using?
Any update here? Should we close this issue?
Sorry for the late reply. I am not sure what you mean by operational data stores, but I will share the actual Startup.cs configuration.
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer(options =>
{
options.Caching.ClientStoreExpiration = TimeSpan.FromMinutes(60);
options.Caching.ResourceStoreExpiration = TimeSpan.FromMinutes(60);
options.Caching.CorsExpiration = TimeSpan.FromMinutes(60);
options.LicenseKey = Configuration.GetConnectionString("identityLicenseKey");
options.KeyManagement.RotationInterval = TimeSpan.FromDays(30);
options.KeyManagement.PropagationTime = TimeSpan.FromDays(2);
options.KeyManagement.RetentionDuration = TimeSpan.FromDays(7);
options.KeyManagement.DeleteRetiredKeys = false;
options.KeyManagement.KeyPath = "/home/shared/keys";
})
.AddRedirectUriValidator<RedirectUriValidator>()
.AddInMemoryCaching()
.AddInMemoryIdentityResources(new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
})
.AddInMemoryApiScopes(new ApiScope[]
{
new ApiScope(IdentityServerConstants.LocalApi.ScopeName),
})
.AddInMemoryPersistedGrants()
.AddInMemoryCaching()
.AddInMemoryClients(Configuration.GetSection("Clients"))
.AddAspNetIdentity<User>()
.AddProfileService<ProfileService>();
services.ConfigureApplicationCookie(config =>
{
config.LoginPath = "/Web/User/Login";
config.LogoutPath = "/Web/User/Logout";
config.ExpireTimeSpan = TimeSpan.FromSeconds(Common.LoginTimeoutSeconds);
});
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = Configuration.GetValue<string>("DomainName");
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Common.TokenCreateKey)),
ClockSkew = TimeSpan.Zero,
};
}).AddGoogle(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = Configuration.GetValue<string>("GoogleClient:ClientId");
options.ClientSecret = Configuration.GetValue<string>("GoogleClient:ClientSecret");
}).AddMicrosoftAccount(options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = Configuration.GetValue<string>("MicrosoftClient:ClientId");
options.ClientSecret = Configuration.GetValue<string>("MicrosoftClient:ClientSecret");
});
services.AddLocalApiAuthentication();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseForwardedHeaders();
app.UseSession();
else
{
app.UseExceptionHandler("/Home/Error");
app.UseStatusCodePagesWithRedirects("/Home/Error?errorCode={0}");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Can you find the cause here?
What I mean by the operational data stores is basically where the persisted grants are stored. This is normally a database table that contains authorization codes, refresh tokens, etc.
You're doing AddInMemoryPersistedGrants, so you're storing the auth codes in memory on the IdentityServer host. What this means is that, if you are in a load balanced environment, every load balanced instance only sees the authorization codes that it sent.
The error is happening because one instance is sending the code, while another receives the token request to exchange the code for tokens. You need to create a data store for auth codes that is shared between the instances and then tell identity server how to use that shared data store. The abstraction for doing this is the IPersistedGrantsStore. We have an entity framework based implementation ready to go that you can read about here, or you can implement the store yourself.
Indeed, the App Service we are using has the number of instances set to 3, so memory may be the cause. I will try using the Entity Framework as described in the document you gave us, as it may solve the problem. Thank you very much.
Is anything further needed on this issue, or should we close it?
Closing, but feel free to reopen if necessary.
Which version of Duende IdentityServer are you using? 6
Which version of .NET are you using? 6
Describe the bug If the interval between /authorize and /connect/token is too short, invalid_grant occurs
To Reproduce
Log output/exception with stacktrace
Additional context
I can solve this problem by doing a retry process for /connect/token here, but is there any other way?