DuendeSoftware / Support

Support for Duende Software products
21 stars 0 forks source link

.net 8 / Duende version 7 upgrade #1472

Open thatdotnetguy opened 6 days ago

thatdotnetguy commented 6 days ago

Which version of Duende IdentityServer are you using? 7.0.8

Which version of .NET are you using? .NET 8

Describe the bug We are upgrading from .net 6 to .net 8 and as part of that we are being forced to upgrade to Duende Identity Server v7.

The architecture is an all in one process react SPA / identity server template from some years back.

The current site (.net 6 / Duende 6.3.10) is running out on develop... I'm trying to do an upgrade to .net 8 / v7 Duende

So running locally.... after successful login... the LocalRedirect fails.... the return URL is set to

https://localhost:44343/connect/authorize/callback?client_id=our.project&redirect_uri=https%3A%2F%2Flocalhost%3A44343%2Fauthentication%2Flogin-callback&response_type=code&scope=ourproject.webAPI%20openid%20profile&state=c6da12c4c9d44452a02f3a37c71e9e42&code_challenge=KwFozpZndP54eJIbNp4wlkDF4lYkMGSYUIGPiEA1xIE&code_challenge_method=S256&response_mode=query

Image

The error displayed is below

Image

Log output/exception with stacktrace

[12:38:00 Information] AuthorizeRequestValidationLog { ClientId: "our.project", ClientName: "our.project", RedirectUri: "https://localhost:44343/silent-refresh.html", AllowedRedirectUris: ["https://localhost:44343/authentication/login-callback", "/connect/authorize/callback", "~/connect/authorize/callback", "https://localhost:44343/silent-refresh.html"], SubjectId: "anonymous", ResponseType: "code", ResponseMode: "query", GrantType: "authorization_code", RequestedScopes: "our.projectAPI openid profile", State: "8e04ad1fc167456280793eb34503c2db", UiLocales: null, Nonce: null, AuthenticationContextReferenceClasses: null, DisplayMode: null, PromptMode: "none", MaxAge: null, LoginHint: null, SessionId: "", Raw: [("client_id": "our.project"), ("redirect_uri": "https://localhost:44343/silent-refresh.html"), ("response_type": "code"), ("scope": "our.projectAPI openid profile"), ("state": "8e04ad1fc167456280793eb34503c2db"), ("code_challenge": "j0jN0NDhb89WS1ofhZFR_XVlSHncMr_RgGYLs5m1V7g"), ("code_challenge_method": "S256"), ("prompt": "none"), ("response_mode": "query")] } (Duende.IdentityServer.Endpoints.AuthorizeEndpoint)
[12:38:00 Information] Invoking IdentityServer endpoint: "Duende.IdentityServer.Endpoints.AuthorizeEndpoint" for "/connect/authorize" (Duende.IdentityServer.Hosting.IdentityServerMiddleware)
[12:38:00 Information] Showing login: User is not authenticated (Duende.IdentityServer.ResponseHandling.AuthorizeInteractionResponseGenerator)
[12:38:00 Information] AuthenticationScheme: "Identity.External" signed out. (Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler)
[12:38:25 Information] AuthenticationScheme: "Identity.Application" signed in. (Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler)
[12:38:31 Information] User logged in. (our.project.Areas.Identity.Pages.Account.LoginModel)
[12:41:22 Error] An unhandled exception has occurred while executing the request. (Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware)
System.InvalidOperationException: The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local.
   at Microsoft.AspNetCore.Mvc.Infrastructure.LocalRedirectResultExecutor.ExecuteAsync(ActionContext context, LocalRedirectResult result)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultAsync>g__Logged|22_0(ResourceInvoker invoker, IActionResult result)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at our.project.Startup.<>c.<<Configure>b__7_4>d.MoveNext() in C:\Dev\ourproject\our.project\Startup.cs:line 602
--- End of stack trace from previous location ---
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at StackExchange.Profiling.MiniProfilerMiddleware.Invoke(HttpContext context) in C:\projects\dotnet\src\MiniProfiler.AspNetCore\MiniProfilerMiddleware.cs:line 104
   at our.project.Middleware.RequestResponseLoggingMiddleware.Invoke(HttpContext context) in C:\Dev\ourproject\our.project\Middleware\RequestResponseLoggingMiddleware.cs:line 33
   at our.project.Middleware.UserSessionMiddleware.InvokeAsync(HttpContext httpContext, ILogger`1 logger, IUserSession userSession) in C:\Dev\ourproject\our.project\Middleware\UserSessionMiddleware.cs:line 55
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Duende.IdentityServer.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IdentityServerOptions options, IEndpointRouter router, IUserSession userSession, IEventService events, IIssuerNameService issuerNameService, ISessionCoordinationService sessionCoordinationService) in C:\Users\AndrewDuffy\Downloads\IdentityServer-7.0.8\IdentityServer-7.0.8\src\IdentityServer\Hosting\IdentityServerMiddleware.cs:line 131
   at Duende.IdentityServer.Hosting.MutualTlsEndpointMiddleware.Invoke(HttpContext context, IAuthenticationSchemeProvider schemes) in C:\Users\AndrewDuffy\Downloads\IdentityServer-7.0.8\IdentityServer-7.0.8\src\IdentityServer\Hosting\MutualTlsEndpointMiddleware.cs:line 95
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Duende.IdentityServer.Hosting.DynamicProviders.DynamicSchemeAuthenticationMiddleware.Invoke(HttpContext context) in C:\Users\AndrewDuffy\Downloads\IdentityServer-7.0.8\IdentityServer-7.0.8\src\IdentityServer\Hosting\DynamicProviders\DynamicSchemes\DynamicSchemeAuthenticationMiddleware.cs:line 51
   at Duende.IdentityServer.Hosting.BaseUrlMiddleware.Invoke(HttpContext context) in C:\Users\AndrewDuffy\Downloads\IdentityServer-7.0.8\IdentityServer-7.0.8\src\IdentityServer\Hosting\BaseUrlMiddleware.cs:line 27
   at AspNetCoreRateLimit.RateLimitMiddleware`1.Invoke(HttpContext context) in C:\Users\User\Documents\Github\AspNetCoreRateLimit\src\AspNetCoreRateLimit\Middleware\RateLimitMiddleware.cs:line 123
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Additional context

My client set up looks like this

var mySPAClient = new Client()
            {
                AccessTokenLifetime = accessTokenLifetime,
                RedirectUris =
                    {
                $"{host}/authentication/login-callback",
                        $"/connect/authorize/callback",  //added these locally out of desperation
                        $"~/connect/authorize/callback"   //added these locally out of desperation                       ,
            $"{host}/silent-refresh.html"
            },
                PostLogoutRedirectUris =
                    {
                        $"{host}/authentication/logout-callback"
                    },
                ClientId = "our.project",
                AllowedScopes =
                    {
                        "ourproject.webAPI",
                        "openid",
                        "profile"
                    },
                ClientName = "our.project",
                RequireConsent = false,
                RequireClientSecret = false,
                AllowedGrantTypes = GrantTypes.Code,
                RequirePkce = true,
                AllowAccessTokensViaBrowser = true
            };

Relevant identity server set up in Startup.cs

            var licenseKey = Configuration.GetSection("IdentityServer:LicenseKey").Value;

        var identityServiceBuild = services.AddIdentityServer(options =>
            {
                options.LicenseKey = licenseKey;
                options.KeyManagement.Enabled = false;
                options.UserInteraction.LoginUrl = $"{host}/Identity/Account/Login";
        options.UserInteraction.LoginReturnUrlParameter = "ReturnUrl";
                options.UserInteraction.LogoutUrl = $"{host}/Identity/Account/Logout";
                options.IssuerUri = host;
            })
            .AddInMemoryClients(clients)
            .AddInMemoryApiScopes(scopes)
            .AddAspNetIdentity<ApplicationUser>();
thatdotnetguy commented 6 days ago

when I do manipulate the return URL to be ~/connect/authorize/callback?client_id=blahblah to satisfy the error message "The supplied URL is not local..." the SPA doesn't navigate to anywhere

AndersAbel commented 1 day ago

Looks like some kind of mixup with the addresses/hosts.

Would you mind providing the following?

thatdotnetguy commented 1 day ago

Hi @AndersAbel seems to be all working now... however i did need to manipulate the returnURL's like below:

Uri uri = new Uri(returnUrl); return LocalRedirect(uri.AbsolutePath + uri.Query);

Which I assume is a .NET 8 difference in how LocalRedirect works?

The URL of your SPA: https://localhost:44343/

The address of the IdentityServer login page: https://localhost:44343/Identity/Account/Login

The authority/issuer configured in the OpenID Connect configuration in your SPA:

var identityServiceBuild = services.AddIdentityServer(options =>
            {
                options.LicenseKey = licenseKey;
                options.KeyManagement.Enabled = false;
                options.UserInteraction.LoginUrl = $"{host}/Identity/Account/Login";
                options.UserInteraction.LoginReturnUrlParameter = "ReturnUrl";
                options.UserInteraction.LogoutUrl = $"{host}/Identity/Account/Logout";
                options.IssuerUri = https://localhost:44343;
                options.UserInteraction.AllowOriginInReturnUrl = true;
                options.UserInteraction.ErrorUrl = $"/Identity/Error";
                options.Authentication.CookieAuthenticationScheme = IdentityConstants.ApplicationScheme; // which equates to "Identity.Application"
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
                options.Endpoints.EnableEndSessionEndpoint = true;
            })
            .AddDeveloperSigningCredential()
            .AddInMemoryApiResources(apiResources)
            .AddInMemoryClients(clients)
            .AddInMemoryApiScopes(scopes)
            .AddAspNetIdentity<ApplicationUser>();

The URL of your IdentityServer instance:

{
  "issuer": "https://localhost:44343",
  "jwks_uri": "https://localhost:44343/.well-known/openid-configuration/jwks",
  "authorization_endpoint": "https://localhost:44343/connect/authorize",
  "token_endpoint": "https://localhost:44343/connect/token",
  "userinfo_endpoint": "https://localhost:44343/connect/userinfo",
  "end_session_endpoint": "https://localhost:44343/connect/endsession",
  "check_session_iframe": "https://localhost:44343/connect/checksession",
  "revocation_endpoint": "https://localhost:44343/connect/revocation",
  "introspection_endpoint": "https://localhost:44343/connect/introspect",
  "device_authorization_endpoint": "https://localhost:44343/connect/deviceauthorization",
  "backchannel_authentication_endpoint": "https://localhost:44343/connect/ciba",
  "pushed_authorization_request_endpoint": "https://localhost:44343/connect/par",
  "require_pushed_authorization_requests": false,
  "frontchannel_logout_supported": true,
  "frontchannel_logout_session_supported": true,
  "backchannel_logout_supported": true,
  "backchannel_logout_session_supported": true,
  "scopes_supported": [
    "ourproject.webAPI",
    "openid",
    "profile",
    "offline_access"
  ],
  "claims_supported": [],
  "grant_types_supported": [
    "authorization_code",
    "client_credentials",
    "refresh_token",
    "implicit",
    "password",
    "urn:ietf:params:oauth:grant-type:device_code",
    "urn:openid:params:grant-type:ciba"
  ],
  "response_types_supported": [
    "code",
    "token",
    "id_token",
    "id_token token",
    "code id_token",
    "code token",
    "code id_token token"
  ],
  "response_modes_supported": [
    "form_post",
    "query",
    "fragment"
  ],
  "token_endpoint_auth_methods_supported": [
    "client_secret_basic",
    "client_secret_post"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "subject_types_supported": [
    "public"
  ],
  "code_challenge_methods_supported": [
    "plain",
    "S256"
  ],
  "request_parameter_supported": true,
  "request_object_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "PS256",
    "PS384",
    "PS512",
    "ES256",
    "ES384",
    "ES512",
    "HS256",
    "HS384",
    "HS512"
  ],
  "prompt_values_supported": [
    "none",
    "login",
    "consent",
    "select_account"
  ],
  "authorization_response_iss_parameter_supported": true,
  "backchannel_token_delivery_modes_supported": [
    "poll"
  ],
  "backchannel_user_code_parameter_supported": true,
  "dpop_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "PS256",
    "PS384",
    "PS512",
    "ES256",
    "ES384",
    "ES512"
  ]
}