Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenIdConnectAuthenticationHandler: message.State is null or empty #55910

Open
1 task done
merijndejonge opened this issue May 27, 2024 · 4 comments
Open
1 task done

Comments

@merijndejonge
Copy link
Contributor

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I'm using Microsoft Identity and and Microsoft Entra as external identity server. I'm using AddOpenIdConnect to set it all up. Everything works perfectly fine except when Microsoft is making a callback as a result of granting tenant-wide admin consent for my app.
This callback is not handled correctly and issues the following Exception:
[2024-05-25` 22:22:08 ERR] An unhandled exception has occurred while executing the request.
Microsoft.AspNetCore.Authentication.AuthenticationFailureException: An error was encountered while handling the remote login.

 ---> Microsoft.AspNetCore.Authentication.AuthenticationFailureException: OpenIdConnectAuthenticationHandler: message.State is null or empty.
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
   at Duende.IdentityServer.Hosting.FederatedSignOut.AuthenticationRequestHandlerWrapper.HandleRequestAsync() in /_/src/IdentityServer/Hosting/FederatedSignOut/AuthenticationRequestHandlerWrapper.cs:line 38
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
   at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Tenant-wide admin consent is granted using the URL https://login.microsoftonline.com/{organization}/adminconsent?client_id={client-id} as documented at https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-admin-consent?pivots=portal#construct-the-url-for-granting-tenant-wide-admin-consent.

It triggers the following callback to my identity server:

https://<my-server>/signin-microsoft?admin_consent=True&tenant=<teant-id>

The path /signin-microsoft is correct and according to my setup.

As a result the sysadmin who want to give tenant-wide consent to my app will get an error from my backend as a result. Obviously, this is not desired.

Expected Behavior

The expected behaviour is that the sys admin will get as normal HTTP 200 response to indicate success.
Or else, there should be an option that my backend can handle the request and returns a HTML result page or something.

Steps To Reproduce

Open the URL https://<my-server>/signin-microsoft?admin_consent=True&tenant=<teant-id> in a browser to trigger the exception.

Exceptions (if any)

---> Microsoft.AspNetCore.Authentication.AuthenticationFailureException: OpenIdConnectAuthenticationHandler: message.State is null or empty.
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
at Duende.IdentityServer.Hosting.FederatedSignOut.AuthenticationRequestHandlerWrapper.HandleRequestAsync() in /_/src/IdentityServer/Hosting/FederatedSignOut/AuthenticationRequestHandlerWrapper.cs:line 38
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(HttpContext context)
at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

.NET Version

net8.0

Anything else?

No response

@javiercn javiercn added the Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue label May 28, 2024
@dotnet-policy-service dotnet-policy-service bot added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label May 28, 2024
@merijndejonge
Copy link
Contributor Author

You can use the blazor sample

Which is recommended by MS (https://learn.microsoft.com/en-us/aspnet/core/blazor/security/blazor-web-app-with-oidc?view=aspnetcore-8.0&pivots=without-bff-pattern)

  • In the folder blazor-samples-main/8.0/BlazorWebAppOidc/BlazorWebAppOidc, run dotnet run
  • Then, in a web browser, open the URL https://localhost:5001/signin-oidc?admin_consent=True&tenant=some-tenant-id

This will trigger the following log:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      Microsoft.AspNetCore.Authentication.AuthenticationFailureException: An error was encountered while handling the remote login.
       ---> Microsoft.AspNetCore.Authentication.AuthenticationFailureException: OpenIdConnectAuthenticationHandler: message.State is null or empty.
         --- End of inner exception stack trace ---
         at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
```

@dotnet-policy-service dotnet-policy-service bot added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. labels May 29, 2024
@mkArtakMSFT mkArtakMSFT added investigate and removed Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. Needs: Repro Indicates that the team needs a repro project to continue the investigation on this issue labels May 30, 2024
@mkArtakMSFT mkArtakMSFT modified the milestones: 7.0.x, 9.0-preview7 May 30, 2024
@halter73
Copy link
Member

halter73 commented Jun 3, 2024

I'll see what we can do to improve OpenIdConnectAuthenticationHandler and/or our Blazor documentation to avoid this issue with tenant-wide admin consent.

It's a little surprising that it's reusing the signin redirect URI (e.g. /signin-microsoft or /signin-oidc) URL when redirecting after admin consent rather than a URL that's unique from signin designed to handle admin consent completing for that application. I wonder if it's because you aren't specifying a redirect_uri, and the signin redirect URI is the only one registered in the app registration portal, so Entra just uses that.

Looking at the documentation from https://learn.microsoft.com/en-us/entra/identity-platform/v2-admin-consent#request-the-permissions-from-a-directory-admin, it seems like they show it redirecting back to a unique /permissions endpoint which is specified via a&redirect_uri query param in the original https://login.microsoftonline.com/{tenant}/v2.0/adminconsent?client_id=... URL. The docs claim you can use any redirect_uri registered in the app registration portal, so your best bet might just be able to fix your issue by registering a new /permissions URI in the portal, specifying that as your redirect_uri, and writing a custom endpoint to handle the /permissions request.

Another thing to consider is to try using AddMicrosoftIdentityWebApp instead of using AddOpenIdConnect and AddCookie directly. It adds a bunch of customizations on top of AddOpenIdConnect and AddCookie that would probably provide better integration to non-standard features provided by Entra, although I'm not sure if it specifically accounts for this.

If you have to use the signin redirect URI to handle this the admin consent redirect, you can set OpenIdConnectOptions.SkipUnrecognizedRequests which should avoid this error. If you want to redirect back to a specific page in this case, you can configure an OpenIdConnectOptions.Events.OnMessageReceived callback that calls messageReceivedContext.SkipHandler() after calling httpContext.Response.Redirect(wherever) itself.

You can find the relevant code in the handler that triggers the OnMessageReceived event and checks SkipUnrecognizedRequests.

var messageReceivedContext = await RunMessageReceivedEventAsync(authorizationResponse, properties);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
authorizationResponse = messageReceivedContext.ProtocolMessage;
properties = messageReceivedContext.Properties;
if (properties == null || properties.Items.Count == 0)
{
// Fail if state is missing, it's required for the correlation id.
if (string.IsNullOrEmpty(authorizationResponse.State))
{
// This wasn't a valid OIDC message, it may not have been intended for us.
Logger.NullOrEmptyAuthorizationResponseState();
if (Options.SkipUnrecognizedRequests)
{
return HandleRequestResult.SkipHandler();
}
return HandleRequestResult.Fail(Resources.MessageStateIsNullOrEmpty);
}
properties = ReadPropertiesAndClearState(authorizationResponse);
}

@merijndejonge
Copy link
Contributor Author

Thanks for the clear explanation.
I wasn't aware of the redirect_uri because it wasn't mentioned in the example thatI referred to (https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/grant-admin-consent?pivots=portal#construct-the-url-for-granting-tenant-wide-admin-consent)

Since the grant-admin-consent URL is the be used by my clients it is pretty fragile to also request them to use the redirect_uri. My proposal therefore would be to make redirect_uri optional and do nothing in case this parameter is absent in the grant-admin-consent request. This, in my view, is the most robust and best way.

To make the current approach robust and customer-friendly I must make available an open endpoint just for the redirect_url. This is not only inconvenient and undesired, opening an unauthenticated endpoint is also not optimal from a security point of view because anyone can use this unnecessary endpoint.

I've also tried the SkipUnrecognizedRequests approach. This, indeed, prevents the exception but it gives a general 404 error, which is also not a customer-friendly solution.

In order to combine it with the OnMessageReceived suggestion, I need a bit more help. Do you suggest to check in the OnMessageReceived handler wether the request URL contains something like "signin-oidc?admin_consent=True" and then change this URL to public accessible page, like /permissions as in the example?

@merijndejonge
Copy link
Contributor Author

any news/updates on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants