Search code examples
asp.netoauth-2.0msal.jsopeniddict

OpenidDict 4.0 + MSAL.js fails for Code flow + PKCE due to sending scopes


Problem

Openididct v4 started declining the PKCE's Access Token request (RFC 7636, when the client exchanges the authorization code) if the client passes scopes (ref to the line in the code enforcing it). It wasn't the case for v3.x

However, the MSAL.js implementation has what they call "hybrid flow" (docs) that mixes the implicit grant with the authorization code flow, and it sends scopes in when exchanging the authorization code. 😟

Example

Request from MSAL.js:

curl --location 'https://localhost:5003/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded;charset=utf-8' \
--data 'client_id=XXX&redirect_uri=https%3A%2F%2Flocalhost:5003&scope=openid%20profile&code={THE_CODE}&x-client-SKU=msal.js.browser&x-client-VER=2.21.0&x-client-OS=&x-client-CPU=&x-ms-lib-capability=retry-after, h429&x-client-current-telemetry=5|865,0,,,|,&x-client-last-telemetry=5|0|865,33365111-6e2e-4707-946c-e93b095f6c1e|invalid_request|1,0&code_verifier=jJdT8dsU&grant_type=authorization_code&client_info=1&client-request-id=99933333-cdd4-4991-8dcb-1a28f3a0669d'

Response from Openiddict:

{
  "error": "invalid_request",
  "error_description": "The 'scope' parameter is not valid in this context.",
  "error_uri": "https://documentation.openiddict.com/errors/ID2074"
}

Question

Is there a way of relaxing this particular check?

IMHO, the Openiddict rejection, in this case, is too rigid behaviour, where a warning would suffice.


Solution

  • The controversial check for scopes when exchanging the authorization code was added in OpenIdDict v4 (link to the commit). As you can see from the code:

    • all it does is rejecting code requests that contain a "scope" parameter;
    • it's built as an EventHandler that makes it easy to add/remove from the pipeline (and yes, it's added in Exchange.DefaultHandlers, so it's ON by default).

    Hence, it's safe to remove the handler by adding this line to AddServer():

    options.RemoveEventHandler(OpenIddictServerHandlers.Exchange.ValidateScopeParameter.Descriptor)
    

    Here's an example of what the registration of the OpenIddict token server services in the DI container may look like (see more in my GitHub example):

    // Register the OpenIddict services
    services.AddOpenIddict()
            // Register the OpenIddict server components.
            .AddServer(options =>
            {
                // Enable the authorization and token endpoints
                options 
                        .SetTokenEndpointUris("/connect/token")
                        .SetAuthorizationEndpointUris("/connect/authorize")
                        .AllowAuthorizationCodeFlow()
                        .RequireProofKeyForCodeExchange()
                        .AllowRefreshTokenFlow()
    
                    // Remove a default guard that rejects auth code requests that contain a "scope" parameter
                        .RemoveEventHandler(OpenIddictServerHandlers.Exchange.ValidateScopeParameter.Descriptor);
    
                    // Other settings go here
    
                    // Register the ASP.NET Core host and configure the ASP.NET Core-specific options.  
                        .UseAspNetCore();
            })