Identity Server4 404 при редиректе после connect/authorize

Я пытаюсь сделать успешный коллбек после connect/authorize, но получаю ошибку при редиректе 404 (приложу скрин). введите сюда описание изображения У меня есть мой Web API это на порту 7142 и приложение на Blazor это на порту 5001. IdentityServer на 7142 живет. Вот как настроен мой клнфиг клиента:

/// <summary>
/// Класс факторки конфигурации IdentityServer.
/// </summary>
public static class IdentityConfigurationFactory
{
    /// <summary>
    /// Метод инициализирует области API.
    /// </summary>
    /// <returns>Спиок областей.</returns>
    public static IEnumerable<ApiScope> CreateApiScopes()
    {
        return new List<ApiScope>
        {
            new ("api", "Supernova.Identity API"),
        };
    }

    /// <summary>
    /// Метод инициализирует клиентов.
    /// </summary>
    /// <returns>Список клиентов.</returns>
    public static IEnumerable<Client> CreateClients()
    {
        return new List<Client>
        {
            new ()
            {
                ClientId = "identity_client",
                AllowedGrantTypes = GrantTypes.Code,
                ClientSecrets =
                {
                    new Secret("identity_secret".Sha256()),
                },
                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "api",
                },
                RedirectUris = { "https://localhost:7142/signin-oidc" },
                AccessTokenLifetime = 600, // 10 мин.,
                AllowOfflineAccess = true,
                Enabled = true,
                // RequireConsent = true,
                // AllowRememberConsent = false,
                // RedirectUris = new List<string> { "https://localhost:7142/signin-oidc" },
                PostLogoutRedirectUris = { "https://localhost:7142/signout-callback-oidc" },
                AccessTokenType = AccessTokenType.Jwt,
                // RequirePkce = true,
            },
        };
    }

    /// <summary>
    /// Метод инициализирует ресурсы API.
    /// </summary>
    /// <returns>Список ресурсов API.</returns>
    public static IEnumerable<IdentityResource> CreateGetApiResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };
    }
}

Вот весь мой код авторизации пользователя:

var context = await _interaction.GetAuthorizationContextAsync(authorizeInput.ReturnUrl);

        // validate username/password against in-memory store
        if (_users.ValidateCredentials(authorizeInput.UserName, authorizeInput.Password))
        {
            var user = _users.FindByUsername(authorizeInput.UserName);

            AuthenticationProperties? props = null;
            var isuser = new IdentityServerUser(user.SubjectId)
            {
                DisplayName = user.Username,
            };

            await HttpContext.SignInAsync(isuser, props);
        }

        return Redirect(authorizeInput.ReturnUrl);

Вот мой код приложения Blazor:

private async Task AuthorizeAsync()
    {
        var currentUrl = NavManager.Uri;
        const string clientId = "identity_client";
        const string responseType = "code";
        const string redirectUri = "https://localhost:7142/signin-oidc";
        const string scope = "api";
        var state = HttpUtility.ParseQueryString(currentUrl).Get("state");
        var codeChallenge = HttpUtility.ParseQueryString(currentUrl).Get("code_challenge");
        var codeChallengeMethod = HttpUtility.ParseQueryString(currentUrl).Get("code_challenge_method");
        
        var returnUrlBuilder = new StringBuilder();
        returnUrlBuilder.Append("/connect/authorize/callback?");
        returnUrlBuilder.Append($"client_id={clientId}&");
        returnUrlBuilder.Append($"response_type={responseType}&");
        returnUrlBuilder.Append($"redirect_uri={redirectUri}&");
        returnUrlBuilder.Append($"state={state}&");
        returnUrlBuilder.Append($"code_challenge={codeChallenge}&");
        returnUrlBuilder.Append($"code_challenge_method={codeChallengeMethod}&");
        returnUrlBuilder.Append($"scope={scope}");

        var authorizeInput = new AuthorizeInput
        {
            UserName = userName,
            Password = password,
            ReturnUrl = returnUrlBuilder.ToString()
        };

        await HttpClient.PostAsJsonAsync("https://localhost:7142/api/Identity/Authorize", authorizeInput);
    }

Вот как настроен файл Program. У меня .NET 6.

 // Добавляем IdentityServer.
builder.Services.AddIdentityServer()
    .AddDeveloperSigningCredential()
    .AddInMemoryIdentityResources(IdentityConfigurationFactory.CreateGetApiResources())
    .AddInMemoryApiScopes(IdentityConfigurationFactory.CreateApiScopes())
    .AddInMemoryClients(IdentityConfigurationFactory.CreateClients())
    .AddTestUsers(TestUsers.Users);

builder.Services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "Supernova.Identity.API",
        Version = "v1",
    });
    opt.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows
        {
            AuthorizationCode = new OpenApiOAuthFlow
            {
                AuthorizationUrl = new Uri(Environment.GetEnvironmentVariable("Authority") !),
                TokenUrl = new Uri(Environment.GetEnvironmentVariable("TokenUrl") !),
            },
        },
    });
    opt.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer",
                },
            },
            Array.Empty<string>()
        },
    });
});

// Добавляем и настраиваем Bearer.
builder.Services.AddAuthentication()
    .AddJwtBearer(options =>
    {
        options.Authority = Environment.GetEnvironmentVariable("Authority");
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidateLifetime = true,
        };
        options.RequireHttpsMetadata = false;
    })
    .AddOpenIdConnect("oidc", "Supernova.Identity IdentityServer", options =>
    {
        options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
        options.SignOutScheme = IdentityServerConstants.SignoutScheme;
        options.Authority = Environment.GetEnvironmentVariable("Authority");
        // options.RequireHttpsMetadata = false; //false for development only
        // options.ClientId = "identity_client";
        options.ClientId = "interactive.confidential";
        options.ClientSecret = "secret";
        options.ResponseType = "code";
        // options.UsePkce = true;

        // options.Scope.Add("profile");
        // options.Scope.Add("offline_access");

        options.SaveTokens = true;
        // options.ResponseMode = "form_post";

        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name",
            RoleClaimType = "role",
        };
    });

builder.Services.AddAutoMapper(typeof(MappingProfile).Assembly);
builder.Services.Register();
builder.Services.AddHttpContextAccessor();

var app = builder.Build();

app.UseRouting();
// app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseCors("ApiCorsPolicy");
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
app.UseStaticFiles();

if (builder.Environment.IsDevelopment() || builder.Environment.IsStaging())
{
    app.UseSwagger();
    app.UseSwaggerUI(opt =>
    {
        opt.InjectStylesheet("/swagger-custom-ui/custom.css");

        var client = IdentityConfigurationFactory.CreateClients().First();

        opt.OAuthClientId(client.ClientId);
        opt.OAuthClientSecret(client.ClientSecrets.First().Value);
        opt.OAuthUsePkce();
    });
}

Пожалуйста, прошу помочь разобраться, я в тупике! Любая помощь будет оценена! Спасибо!


Ответы (1 шт):

Автор решения: Антон Т.

Если кому то это еще поможет, то эта проблема была из-за того, что token/authorize ожидает, что у вас на веб-странице есть форма (html-тег form), в которую он вернет код. То есть он работает через FormData и ему нужна любая форма, например скрытая просто. Без нее у вас будет эта ошибка. Вот этой штуки не хватало в моей ситуации. введите сюда описание изображения

→ Ссылка