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

Microsoft Entra: ResponseMode "query" is not working good #2066

Closed
1 task
alexstooky opened this issue May 7, 2024 · 12 comments
Closed
1 task

Microsoft Entra: ResponseMode "query" is not working good #2066

alexstooky opened this issue May 7, 2024 · 12 comments

Comments

@alexstooky
Copy link

Personal contribution

  • I'm not interested in submitting a pull request and understand that this provider may not be fixed in the near future without my contribution.

Version

5.5.0

Provider name

Microsoft

Describe the bug

In some cases with very long idtoken(s) the ResponseType "code" creates too long returnUrl which is blocked by Url Request Filtering (IIS Server - https://learn.microsoft.com/en-us/iis/configuration/system.webserver/security/requestfiltering/requestlimits/#attributes) default of 2048 bytes.

The Microsoft doc referenced by the WebProvider (https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc) suggests to use "form_post" as ResponseMode and "idtoken" as per ResponseType but the WebProvider currently implemented uses "query" as ResponseMode and "code" as ResponseType

I would love to contribute to fix this or (better to me) expose an extension method on order to override the defautl Response Mode and Response Type.

To reproduce

Configure WebProvider Microsoft and add to Entra some guests users with external providers eg. Google. The issue is not present when logging in with the common Entra login flow

Exceptions (if any)

No response

@kevinchalet
Copy link
Member

kevinchalet commented May 7, 2024

In some cases with very long idtoken(s) the ResponseType "code" creates too long returnUrl which is blocked by Url Request Filtering (IIS Server - https://learn.microsoft.com/en-us/iis/configuration/system.webserver/security/requestfiltering/requestlimits/#attributes) default of 2048 bytes.

It's definitely not normal: you're not expected to get back an identity token in the authorization response when using response_type=code. Could you please share a Fiddler trace?

The Microsoft doc referenced by the WebProvider (https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc) suggests to use "form_post" as ResponseMode and "idtoken" as per ResponseType but the WebProvider currently implemented uses "query" as ResponseMode and "code" as ResponseType

response_mode=form_post is not something I'd recommend using in 2024 as it doesn't play nice with same-site cookies (you'll need to use none for your main authentication cookie for that to work correctly).

The OpenIddict client fully supports both response_mode=form_post and the hybrid/implicit flows but the good old authorization code flow is always preferred for the reasons explained here:

// In OAuth 2.0/OpenID Connect, the concept of "flow" is actually a quite complex combination
// of a grant type and a response type (that can include multiple, space-separated values).
//
// While the authorization code flow has a unique grant type/response type combination, more
// complex flows like the hybrid flow have many valid grant type/response types combinations.
//
// To evaluate whether a specific flow can be used, both the grant types and response types
// MUST be analyzed to find standard combinations that are supported by the both the client
// and the authorization server.
(context.GrantType, context.ResponseType) = (
Client: (
// Note: if grant types are explicitly listed in the client registration, only use
// the grant types that are both listed and enabled in the global client options.
// Otherwise, always default to the grant types that have been enabled globally.
GrantTypes: context.Registration.GrantTypes.Count switch
{
0 => context.Options.GrantTypes as ICollection<string>,
_ => context.Options.GrantTypes.Intersect(context.Registration.GrantTypes, StringComparer.Ordinal).ToList()
},
// Note: if response types are explicitly listed in the client registration, only use
// the response types that are both listed and enabled in the global client options.
// Otherwise, always default to the response types that have been enabled globally.
ResponseTypes: context.Registration.ResponseTypes.Count switch
{
0 => context.Options.ResponseTypes.Select(static types => types
.Split(Separators.Space, StringSplitOptions.None)
.ToHashSet(StringComparer.Ordinal))
.ToList(),
_ => context.Options.ResponseTypes.Select(static types => types
.Split(Separators.Space, StringSplitOptions.None)
.ToHashSet(StringComparer.Ordinal))
.Where(types => context.Registration.ResponseTypes.Any(value => value
.Split(Separators.Space, StringSplitOptions.None)
.ToHashSet(StringComparer.Ordinal)
.SetEquals(types)))
.ToList()
}),
Server: (
GrantTypes: context.Configuration.GrantTypesSupported,
ResponseTypes: context.Configuration.ResponseTypesSupported
.Select(static types => types
.Split(Separators.Space, StringSplitOptions.None)
.ToHashSet(StringComparer.Ordinal))
.ToList())) switch
{
// Note: if no grant type was explicitly returned as part of the server configuration,
// the identity provider is assumed to implicitly support both the authorization code
// and the implicit grants, as stated by the OAuth 2.0/OIDC discovery specifications.
//
// See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
// and https://datatracker.ietf.org/doc/html/rfc8414#section-2 for more information.
// Note: response_type=code is always tested first as it doesn't require using
// response_mode=form_post or response_mode=fragment: fragment doesn't natively work with
// server-side clients and form_post is impacted by the same-site cookies restrictions
// that are now enforced by most browser vendors, which requires using SameSite=None for
// response_mode=form_post to work correctly. While it doesn't have native protection
// against mix-up attacks (due to the missing id_token in the authorization response),
// the code flow remains the best compromise and thus always comes first in the list.
// Authorization code flow with grant_type=authorization_code and response_type=code:
(var client, var server) when
// Ensure grant_type=authorization_code is supported.
client.GrantTypes.Contains(GrantTypes.AuthorizationCode) &&
(server.GrantTypes.Count is 0 || // If empty, assume the code grant is supported by the server.
server.GrantTypes.Contains(GrantTypes.AuthorizationCode)) &&
// Ensure response_type=code is supported.
client.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.Code)) &&
server.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.Code))
=> (GrantTypes.AuthorizationCode, ResponseTypes.Code),
// Hybrid flow with grant_type=authorization_code/implicit and response_type=code id_token:
(var client, var server) when
// Ensure grant_type=authorization_code and grant_type=implicit are supported.
(client.GrantTypes.Contains(GrantTypes.AuthorizationCode) && client.GrantTypes.Contains(GrantTypes.Implicit)) &&
(server.GrantTypes.Count is 0 || // If empty, assume the code and implicit grants are supported by the server.
(server.GrantTypes.Contains(GrantTypes.AuthorizationCode) && server.GrantTypes.Contains(GrantTypes.Implicit))) &&
// Ensure response_type=code id_token is supported.
client.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.IdToken)) &&
server.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.IdToken))
=> (GrantTypes.AuthorizationCode, ResponseTypes.Code + ' ' + ResponseTypes.IdToken),
// Implicit flow with grant_type=implicit and response_type=id_token:
(var client, var server) when
// Ensure grant_type=implicit is supported.
client.GrantTypes.Contains(GrantTypes.Implicit) &&
(server.GrantTypes.Count is 0 || // If empty, assume the implicit grant is supported by the server.
server.GrantTypes.Contains(GrantTypes.Implicit)) &&
// Ensure response_type=id_token is supported.
client.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.IdToken)) &&
server.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.IdToken))
=> (GrantTypes.Implicit, ResponseTypes.IdToken),
// Note: response types combinations containing "token" are always tested last as some
// authorization servers - like OpenIddict - are known to block authorization requests
// asking for an access token if Proof Key for Code Exchange is used in the same request.
//
// Returning an identity token directly from the authorization endpoint also has privacy
// concerns that code-based flows - that require a backchannel request - typically don't
// have when the client application (confidential or public) is executed on a server.
// Hybrid flow with grant_type=authorization_code/implicit and response_type=code id_token token.
(var client, var server) when
// Ensure grant_type=authorization_code and grant_type=implicit are supported.
(client.GrantTypes.Contains(GrantTypes.AuthorizationCode) && client.GrantTypes.Contains(GrantTypes.Implicit)) &&
(server.GrantTypes.Count is 0 || // If empty, assume the code and implicit grants are supported by the server.
(server.GrantTypes.Contains(GrantTypes.AuthorizationCode) && server.GrantTypes.Contains(GrantTypes.Implicit))) &&
// Ensure response_type=code id_token token is supported.
client.ResponseTypes.Exists(static types => types.Count is 3 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.IdToken) &&
types.Contains(ResponseTypes.Token)) &&
server.ResponseTypes.Exists(static types => types.Count is 3 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.IdToken) &&
types.Contains(ResponseTypes.Token))
=> (GrantTypes.AuthorizationCode, ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token),
// Hybrid flow with grant_type=authorization_code/implicit and response_type=code token.
(var client, var server) when
// Ensure grant_type=authorization_code and grant_type=implicit are supported.
(client.GrantTypes.Contains(GrantTypes.AuthorizationCode) && client.GrantTypes.Contains(GrantTypes.Implicit)) &&
(server.GrantTypes.Count is 0 || // If empty, assume the code and implicit grants are supported by the server.
(server.GrantTypes.Contains(GrantTypes.AuthorizationCode) && server.GrantTypes.Contains(GrantTypes.Implicit))) &&
// Ensure response_type=code token is supported.
client.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.Token)) &&
server.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.Token))
=> (GrantTypes.AuthorizationCode, ResponseTypes.Code + ' ' + ResponseTypes.Token),
// Implicit flow with grant_type=implicit and response_type=id_token token.
(var client, var server) when
// Ensure grant_type=implicit is supported.
client.GrantTypes.Contains(GrantTypes.Implicit) &&
(server.GrantTypes.Count is 0 || // If empty, assume the implicit grant is supported by the server.
server.GrantTypes.Contains(GrantTypes.Implicit)) &&
// Ensure response_type=code token is supported.
client.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.IdToken) &&
types.Contains(ResponseTypes.Token)) &&
server.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.IdToken) &&
types.Contains(ResponseTypes.Token))
=> (GrantTypes.Implicit, ResponseTypes.IdToken + ' ' + ResponseTypes.Token),
// Note: response_type=token is not considered secure enough as it allows malicious
// actors to inject access tokens that were initially issued to a different client.
// As such, while OpenIddict-based servers allow using response_type=token for backward
// compatibility with legacy clients, OpenIddict-based clients are deliberately not
// allowed to negotiate the unsafe and OAuth 2.0-only response_type=token flow.
//
// For more information, see https://datatracker.ietf.org/doc/html/rfc6749#section-10.16 and
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-19#section-2.1.2.
// None flow with response_type=none.
(var client, var server) when
// Ensure response_type=none is supported.
client.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.None)) &&
server.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.None))
=> (null, ResponseTypes.None),
// Note: this check is only enforced after the none flow was excluded as it doesn't use a grant type.
(var client, _) when client.GrantTypes.Count is 0
=> throw new InvalidOperationException(SR.GetResourceString(SR.ID0360)),
(var client, _) when client.ResponseTypes.Count is 0
=> throw new InvalidOperationException(SR.GetResourceString(SR.ID0361)),
(_, var server) when server.ResponseTypes.Count is 0
=> throw new InvalidOperationException(SR.GetResourceString(SR.ID0297)),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0298))
};
return default;

You can override the response modes/types OpenIddict is allowed to negotiate by adding at least one value to OpenIddictClientRegistration.ResponseModes and OpenIddictClientRegistration.ResponseTypes:

options.UseWebProviders()
       .AddMicrosoft(options =>
       {
           options.SetClientId("cce8c58c-df3e-4bde-96e5-1a1954e35cca")
                  .SetClientSecret("2ko8Q~mPuwRhhNoe6VmO3vlYkWtcBU~gxQHfEbCB")
                  .SetRedirectUri("callback/login/microsoft")
                  .AddScopes(Scopes.OfflineAccess);

           options.Registration.ResponseModes.Add(ResponseModes.FormPost);
           options.Registration.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken);
       });

Of course, for the reasons I mentioned earlier, I don't encourage you to do that.

Note: this won't work before a new version including #2067 is released, as the current version doesn't list "implicit" as a supported grant type for the Microsoft provider.

Configure WebProvider Microsoft and add to Entra some guests users with external providers eg. Google. The issue is not present when logging in with the common Entra login flow

Please share a Fiddler trace (you can send it privately by emailing me if you prefer).

@alexstooky
Copy link
Author

Hi Kevin, many thanks for the quick reply.

It's definitely not normal: you're not expected to get back an identity token in the authorization response when using response_type=code. Could you please share a Fiddler trace?

I'm sorry I did not make myself clear. The idtoken is not returned back with returnUrl but code instead. This param get very "long" in the repro case I described in the issue. Sure I will try record and send some Fiddler trace, atm I have got just few screenshot of IIS error (404.15)

response_mode=form_post is not something I'd recommend using in 2024 as it doesn't play nice with same-site cookies (you'll need to use none for your main authentication cookie for that to work correctly).

I totally agree with you. We would going to manage this "issue" by extending the defautl IIS Request limit, as far I know it was due to old Internet Explorer

The OpenIddict client fully supports both response_mode=form_post and the hybrid/implicit flows but the good old authorization code flow is always preferred for the reasons explained here:

// In OAuth 2.0/OpenID Connect, the concept of "flow" is actually a quite complex combination
// of a grant type and a response type (that can include multiple, space-separated values).
//
// While the authorization code flow has a unique grant type/response type combination, more
// complex flows like the hybrid flow have many valid grant type/response types combinations.
//
// To evaluate whether a specific flow can be used, both the grant types and response types
// MUST be analyzed to find standard combinations that are supported by the both the client
// and the authorization server.
(context.GrantType, context.ResponseType) = (
Client: (
// Note: if grant types are explicitly listed in the client registration, only use
// the grant types that are both listed and enabled in the global client options.
// Otherwise, always default to the grant types that have been enabled globally.
GrantTypes: context.Registration.GrantTypes.Count switch
{
0 => context.Options.GrantTypes as ICollection<string>,
_ => context.Options.GrantTypes.Intersect(context.Registration.GrantTypes, StringComparer.Ordinal).ToList()
},
// Note: if response types are explicitly listed in the client registration, only use
// the response types that are both listed and enabled in the global client options.
// Otherwise, always default to the response types that have been enabled globally.
ResponseTypes: context.Registration.ResponseTypes.Count switch
{
0 => context.Options.ResponseTypes.Select(static types => types
.Split(Separators.Space, StringSplitOptions.None)
.ToHashSet(StringComparer.Ordinal))
.ToList(),
_ => context.Options.ResponseTypes.Select(static types => types
.Split(Separators.Space, StringSplitOptions.None)
.ToHashSet(StringComparer.Ordinal))
.Where(types => context.Registration.ResponseTypes.Any(value => value
.Split(Separators.Space, StringSplitOptions.None)
.ToHashSet(StringComparer.Ordinal)
.SetEquals(types)))
.ToList()
}),
Server: (
GrantTypes: context.Configuration.GrantTypesSupported,
ResponseTypes: context.Configuration.ResponseTypesSupported
.Select(static types => types
.Split(Separators.Space, StringSplitOptions.None)
.ToHashSet(StringComparer.Ordinal))
.ToList())) switch
{
// Note: if no grant type was explicitly returned as part of the server configuration,
// the identity provider is assumed to implicitly support both the authorization code
// and the implicit grants, as stated by the OAuth 2.0/OIDC discovery specifications.
//
// See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
// and https://datatracker.ietf.org/doc/html/rfc8414#section-2 for more information.
// Note: response_type=code is always tested first as it doesn't require using
// response_mode=form_post or response_mode=fragment: fragment doesn't natively work with
// server-side clients and form_post is impacted by the same-site cookies restrictions
// that are now enforced by most browser vendors, which requires using SameSite=None for
// response_mode=form_post to work correctly. While it doesn't have native protection
// against mix-up attacks (due to the missing id_token in the authorization response),
// the code flow remains the best compromise and thus always comes first in the list.
// Authorization code flow with grant_type=authorization_code and response_type=code:
(var client, var server) when
// Ensure grant_type=authorization_code is supported.
client.GrantTypes.Contains(GrantTypes.AuthorizationCode) &&
(server.GrantTypes.Count is 0 || // If empty, assume the code grant is supported by the server.
server.GrantTypes.Contains(GrantTypes.AuthorizationCode)) &&
// Ensure response_type=code is supported.
client.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.Code)) &&
server.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.Code))
=> (GrantTypes.AuthorizationCode, ResponseTypes.Code),
// Hybrid flow with grant_type=authorization_code/implicit and response_type=code id_token:
(var client, var server) when
// Ensure grant_type=authorization_code and grant_type=implicit are supported.
(client.GrantTypes.Contains(GrantTypes.AuthorizationCode) && client.GrantTypes.Contains(GrantTypes.Implicit)) &&
(server.GrantTypes.Count is 0 || // If empty, assume the code and implicit grants are supported by the server.
(server.GrantTypes.Contains(GrantTypes.AuthorizationCode) && server.GrantTypes.Contains(GrantTypes.Implicit))) &&
// Ensure response_type=code id_token is supported.
client.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.IdToken)) &&
server.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.IdToken))
=> (GrantTypes.AuthorizationCode, ResponseTypes.Code + ' ' + ResponseTypes.IdToken),
// Implicit flow with grant_type=implicit and response_type=id_token:
(var client, var server) when
// Ensure grant_type=implicit is supported.
client.GrantTypes.Contains(GrantTypes.Implicit) &&
(server.GrantTypes.Count is 0 || // If empty, assume the implicit grant is supported by the server.
server.GrantTypes.Contains(GrantTypes.Implicit)) &&
// Ensure response_type=id_token is supported.
client.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.IdToken)) &&
server.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.IdToken))
=> (GrantTypes.Implicit, ResponseTypes.IdToken),
// Note: response types combinations containing "token" are always tested last as some
// authorization servers - like OpenIddict - are known to block authorization requests
// asking for an access token if Proof Key for Code Exchange is used in the same request.
//
// Returning an identity token directly from the authorization endpoint also has privacy
// concerns that code-based flows - that require a backchannel request - typically don't
// have when the client application (confidential or public) is executed on a server.
// Hybrid flow with grant_type=authorization_code/implicit and response_type=code id_token token.
(var client, var server) when
// Ensure grant_type=authorization_code and grant_type=implicit are supported.
(client.GrantTypes.Contains(GrantTypes.AuthorizationCode) && client.GrantTypes.Contains(GrantTypes.Implicit)) &&
(server.GrantTypes.Count is 0 || // If empty, assume the code and implicit grants are supported by the server.
(server.GrantTypes.Contains(GrantTypes.AuthorizationCode) && server.GrantTypes.Contains(GrantTypes.Implicit))) &&
// Ensure response_type=code id_token token is supported.
client.ResponseTypes.Exists(static types => types.Count is 3 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.IdToken) &&
types.Contains(ResponseTypes.Token)) &&
server.ResponseTypes.Exists(static types => types.Count is 3 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.IdToken) &&
types.Contains(ResponseTypes.Token))
=> (GrantTypes.AuthorizationCode, ResponseTypes.Code + ' ' + ResponseTypes.IdToken + ' ' + ResponseTypes.Token),
// Hybrid flow with grant_type=authorization_code/implicit and response_type=code token.
(var client, var server) when
// Ensure grant_type=authorization_code and grant_type=implicit are supported.
(client.GrantTypes.Contains(GrantTypes.AuthorizationCode) && client.GrantTypes.Contains(GrantTypes.Implicit)) &&
(server.GrantTypes.Count is 0 || // If empty, assume the code and implicit grants are supported by the server.
(server.GrantTypes.Contains(GrantTypes.AuthorizationCode) && server.GrantTypes.Contains(GrantTypes.Implicit))) &&
// Ensure response_type=code token is supported.
client.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.Token)) &&
server.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.Code) &&
types.Contains(ResponseTypes.Token))
=> (GrantTypes.AuthorizationCode, ResponseTypes.Code + ' ' + ResponseTypes.Token),
// Implicit flow with grant_type=implicit and response_type=id_token token.
(var client, var server) when
// Ensure grant_type=implicit is supported.
client.GrantTypes.Contains(GrantTypes.Implicit) &&
(server.GrantTypes.Count is 0 || // If empty, assume the implicit grant is supported by the server.
server.GrantTypes.Contains(GrantTypes.Implicit)) &&
// Ensure response_type=code token is supported.
client.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.IdToken) &&
types.Contains(ResponseTypes.Token)) &&
server.ResponseTypes.Exists(static types => types.Count is 2 && types.Contains(ResponseTypes.IdToken) &&
types.Contains(ResponseTypes.Token))
=> (GrantTypes.Implicit, ResponseTypes.IdToken + ' ' + ResponseTypes.Token),
// Note: response_type=token is not considered secure enough as it allows malicious
// actors to inject access tokens that were initially issued to a different client.
// As such, while OpenIddict-based servers allow using response_type=token for backward
// compatibility with legacy clients, OpenIddict-based clients are deliberately not
// allowed to negotiate the unsafe and OAuth 2.0-only response_type=token flow.
//
// For more information, see https://datatracker.ietf.org/doc/html/rfc6749#section-10.16 and
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-19#section-2.1.2.
// None flow with response_type=none.
(var client, var server) when
// Ensure response_type=none is supported.
client.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.None)) &&
server.ResponseTypes.Exists(static types => types.Count is 1 && types.Contains(ResponseTypes.None))
=> (null, ResponseTypes.None),
// Note: this check is only enforced after the none flow was excluded as it doesn't use a grant type.
(var client, _) when client.GrantTypes.Count is 0
=> throw new InvalidOperationException(SR.GetResourceString(SR.ID0360)),
(var client, _) when client.ResponseTypes.Count is 0
=> throw new InvalidOperationException(SR.GetResourceString(SR.ID0361)),
(_, var server) when server.ResponseTypes.Count is 0
=> throw new InvalidOperationException(SR.GetResourceString(SR.ID0297)),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0298))
};
return default;

Thanks for the suggestion. I am still moving the first steps with OpenIddict and loving it. Thanks for your effort.

You can override the response modes/types OpenIddict is allowed to negotiate by adding at least one value to OpenIddictClientRegistration.ResponseModes and OpenIddictClientRegistration.ResponseTypes:

options.UseWebProviders()
       .AddMicrosoft(options =>
       {
           options.SetClientId("cce8c58c-df3e-4bde-96e5-1a1954e35cca")
                  .SetClientSecret("2ko8Q~mPuwRhhNoe6VmO3vlYkWtcBU~gxQHfEbCB")
                  .SetRedirectUri("callback/login/microsoft")
                  .AddScopes(Scopes.OfflineAccess);

           options.Registration.ResponseModes.Add(ResponseModes.FormPost);
           options.Registration.ResponseTypes.Add(ResponseTypes.Code + ' ' + ResponseTypes.IdToken);
       });

Of course, for the reasons I mentioned earlier, I don't encourage you to do that.

Note: this won't work before a new version including #2067 is released, as the current version doesn't list "implicit" as a supported grant type for the Microsoft provider.

Looking forward to this! Thanks!

Configure WebProvider Microsoft and add to Entra some guests users with external providers eg. Google. The issue is not present when logging in with the common Entra login flow

I will do asap. Thanks.

@alexstooky
Copy link
Author

I sent a Fiddler trace privately. Thank you.

@kevinchalet
Copy link
Member

Hi @alexstooky,

Thanks for the Fiddler trace: it indeed confirms Entra ID returns a fairly long code - 2006 characters, so I guess it is self-contained - during that flow. Everything else seems to be normal 😃

@jennyf19 hey Jenny, hope you're doing well! Do you happen to know whether getting back a quite long Entra ID authorization code after an Entra ID+external provider authorization dance is expected? 😃

@kevinchalet
Copy link
Member

@jennyf19 hey Jenny, hope you're doing well! Do you happen to know whether getting back a quite long Entra ID authorization code after an Entra ID+external provider authorization dance is expected? 😃

@jmprieur hey Jean-Marc. Any idea?

@kevinchalet
Copy link
Member

@alexstooky I decided to introduce new (advanced) APIs to support configuring explicit code challenge methods/grant types/response modes/response types without having to directly set them on the registration in OpenIddict 5.6.0.

E.g to configure explicit response modes/types:

options.UseWebProviders()
       .AddMicrosoft(options =>
       {
           options.SetClientId("cce8c58c-df3e-4bde-96e5-1a1954e35cca")
                  .SetClientSecret("2ko8Q~mPuwRhhNoe6VmO3vlYkWtcBU~gxQHfEbCB")
                  .SetRedirectUri("callback/login/microsoft")
                  .AddScopes(Scopes.OfflineAccess)
                  .AddResponseModes(ResponseModes.FormPost)
                  .AddResponseTypes(ResponseTypes.Code + ' ' + ResponseTypes.IdToken);
       });

See #2075.

@jmprieur
Copy link

@jmprieur hey Jean-Marc. Any idea?

@kevinchalet: maybe a lot of scopes are requested?

@kevinchalet
Copy link
Member

@jmprieur AFAICT by looking at the Fiddler trace @alexstooky sent me, only openid and profile are requested during the Entra ID authorization dance and only email is requested by Entra ID during the Google external provider flow.

@alexstooky do you allow me to share the trace you sent me with @jmprieur (who's a Microsoft employee)?

@alexstooky
Copy link
Author

@alexstooky do you allow me to share the trace you sent me with @jmprieur (who's a Microsoft employee)?

Yes sure, thank you!

@kevinchalet
Copy link
Member

@jmprieur let me know if you'd be interested in investigating and I'll send you the trace file by email 😃

@kevinchalet
Copy link
Member

@alexstooky it looks like there isn't much interest from the Identity division to investigate this issue. I suggest you open an official support ticket via Azure to force them to take a look at it: having to use response_mode=form_post in 2024 isn't really a satisfying answer. Please keep me informed if you decide to do so 😄

Closing as there's nothing I can do from my side.

@alexstooky
Copy link
Author

alexstooky commented May 17, 2024

Thank you anyway @kevinchalet. We will think about the best way to "fix" this, to me is ok/better to extend the default IIS maxQueryStringLength value of 2048 by using web.config or code instead of switching to response_mode=form_post but since OpenIddict v5.6.0 let you override this value, developers can choose. :)

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

3 participants