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

ClientCredentials Flow Doesn't Send client_id and client_secret as Form Data Fields and Throw Auth ErrorTypeError: Failed to fetch #2544

Open
jenergm opened this issue Nov 11, 2022 · 18 comments
Labels
responded Responded with solution or request for more info

Comments

@jenergm
Copy link

jenergm commented Nov 11, 2022

Dear all,

I'm using version Swashbuckle.AspNetCore.6.4.0.
I've set in Startup.cs:

ConfigureServices:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Prosegur.Cash.Local.Toolkit.Mercadona.IntegrateAPI", Version = "v1" });

    c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows()
        {
            ClientCredentials = new OpenApiOAuthFlow()
            {
                AuthorizationUrl = new Uri($"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/oauth2/v2.0/authorize"),
                TokenUrl = new Uri($"https://login.microsoftonline.com/{Configuration["AzureAD:TenantId"]}/oauth2/v2.0/token"),
                Scopes = new Dictionary<string, string>
                        {
                            { $"https://b2c.company.com/{Configuration["AzureAD:ClientId"]}/.default", "Daemon Calls Web API" }
                        }
            }
        }
    });
});

Configure:

app.UseSwagger();
app.UseSwaggerUI(options =>
{
    options.OAuthClientId(appid.FirstOrDefault());
    options.OAuthClientSecret(client_secret);
    options.DisplayRequestDuration();
});

When I click Authorize button, it doesn't submit client_id and client_secret of the form as a form data field.

Swagger

@jenergm jenergm changed the title ClientCredentials Flow Doesn't Send client_id and client_secret as Form Data. ClientCredentials Flow Doesn't Send client_id and client_secret as Form Data Fields and Throw Auth ErrorTypeError: Failed to fetch Nov 12, 2022
@domaindrivendev
Copy link
Owner

You've only defined the scheme - as per the Open API spec, you also need to add security requirements to actually apply the scheme either globally or for specific operation(s). See the related readme section, specifically the following note and snippets that follow:

NOTE: In addition to defining a scheme, you also need to indicate which operations that scheme is applicable to. You can apply schemes globally (i.e. to ALL operations) through the AddSecurityRequirement method. The example below indicates that the scheme called "oauth2" should be applied to all operations, and that the "readAccess" and "writeAccess" scopes are required. When applying schemes of type other than "oauth2", the array of scopes MUST be empty.

@domaindrivendev domaindrivendev added the responded Responded with solution or request for more info label Nov 14, 2022
@jenergm
Copy link
Author

jenergm commented Nov 16, 2022

Hi Richard,
I had set AddSecurityRequirement method, but when I click Authorize, it keeps not sending client_id and client_secret of the oauth2 swagger form as Form Data. As you can see, grant_type and scope are sent.

c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
    {
        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
        },
        new[] { $"https://b2c.company.com/{Configuration["AzureAD:ClientId"]}/.default" }
    }
});

Please, could you tell me what I'm missing if this can work?

@Franklin89
Copy link

Franklin89 commented Nov 16, 2022

+1 having the same issue here.
After having CORS setup correctly I am no seeing a 401. Because the client_id and the client_secret is missing from the request to the token endpoint.

image

@Franklin89
Copy link

According to the specs there are two possibilities. Either send the client_id and client_secret within the request body or as HTTP Basic. With Auth0 I found out that this can be configured per client.

There is still one piece missing though. The audience is currently missing the form data.

@jenergm
Copy link
Author

jenergm commented Dec 13, 2022

You've only defined the scheme - as per the Open API spec, you also need to add security requirements to actually apply the scheme either globally or for specific operation(s). See the related readme section, specifically the following note and snippets that follow:

NOTE: In addition to defining a scheme, you also need to indicate which operations that scheme is applicable to. You can apply schemes globally (i.e. to ALL operations) through the AddSecurityRequirement method. The example below indicates that the scheme called "oauth2" should be applied to all operations, and that the "readAccess" and "writeAccess" scopes are required. When applying schemes of type other than "oauth2", the array of scopes MUST be empty.

Hi Richard, I had set AddSecurityRequirement method, but when I click Authorize, it keeps not sending client_id and client_secret of the oauth2 swagger form as Form Data. As you can see, grant_type and scope are sent.

c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
    {
        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
        },
        new[] { $"https://b2c.company.com/{Configuration["AzureAD:ClientId"]}/.default" }
    }
});

Please, could you tell me what I'm missing if this can work?

Hi @domaindrivendev ,

Have you any solution about this issue? Any workaround anything?

Best regards,

@iRubens
Copy link

iRubens commented Feb 7, 2023

@jenergm we have the same problem!

For example, in our implementation Azure AD/B2C doesn't seem to support the basic authentication mode for client_credentials grant.

We've encountered this problem various times, and this would be very useful.. @domaindrivendev, can you give us some directions such we can evaluate to do a pull request for this? Or directly give a look on it? 😜

@devna13
Copy link

devna13 commented May 9, 2023

Ran into same problem today...
I could replay swagger ui request using fiddler

By removing authorization header basic
And removing origin header
And adding client id and secret to the request body

I was able to get valid token back.

It's the way swagger is posting the request causing problem.

@dtila
Copy link

dtila commented Jun 14, 2023

Same issue here as well!

I have applied both security definition and requirement and the client_id and client_secret is sent thought Authorization header but not in the Form Body. Azure Oauth2 endpoint refuses to give the token in the header.

The behavior I have like here

+1 having the same issue here. After having CORS setup correctly I am no seeing a 401. Because the client_id and the client_secret is missing from the request to the token endpoint.

image

I upgraded to latest packages and the problem still occurs. Others have it as well: https://stackoverflow.com/questions/65231280/swashbuckle-swagger-ui-not-sending-client-secret-and-client-id-to-oauth-endpoint

@fabrizio-cannizzaro
Copy link

I have exactly the same problem
@OpenAPIDefinition(info = @Info(title = "Imv Api Gateway", description = "Some long and useful description", version = "v1")) @SecurityScheme( name = "oauth2", type = SecuritySchemeType.OAUTH2, flows = @OAuthFlows( clientCredentials = @OAuthFlow( tokenUrl = "${springdoc.oAuthFlow.tokenUrl}", scopes = { @OAuthScope(name = "api://2a689d5e-1f7a-44cb-a1a6-d590a5405eca/.default", description = "api scope") } ) ) )
It is creating a request with a basic authentication header, instead of payload Form Data. Please fix it

@wiz-the-engineer
Copy link

wiz-the-engineer commented Nov 8, 2023

I've encountered the same issue. I'm using ClientCredentials in the Security Definition and my security requirement just is pretty basic. Is there a way in the requirement to explicity say to include the client_id or client_secret in the body of the post? It's beginning to look like no one has answer here. I've read the previously referenced link and while I know others don't like spoon feeding answers, but this one is just not going well. I implore one of you to please provide a concrete solution for this. I've been able to use LINQPad to request the token using an HttpClient. This should be very possible from the swagger authorization modal. I did recently notice that the client credentials are being passed along via basic auth in the header. To get it working, I'm certain it needs to be in the body.

---------------TEMPORARY WORKAROUND--------------
It looks like the issue lies within the javascript itself for the swagger to me. Until Swashbuckle catches up to giving an option to include everything in the form body, here's what you can do. You can intercept the request in C# and add onto the body before the request is sent. Within the UseSwaggerUI setupAction (the lambda), do the following:
app.UseSwaggerUI(options => { options.UseRequestInterceptor(@"(req) => { console.log(req); if (req.url.includes('**REPLACEWITHYOURAUTHURL**')) { req.body = req.body + '&client_id=**REPLACEWITHYOURCLIENT**&client_secret=**REPLACEWITHYOURSECRET**&audience=**REPLACEWITHYOURAUDIENCE**'; } console.log(req); return req; }"); });

It's UGLY, but it works. Feel free to remove BOTH "console.log(req);". I hate it too, but I think you all deserve something that works as a solution. I tried to format this, but it's not working.
-------------------------------------------------------------

@Digiman
Copy link

Digiman commented Dec 6, 2023

Same issue happened in the Swashbuckle.AspNetCore 6.5.0. Not sending the client_id and client_secret from the input fields in Swagger UI.

Client credentials flow, token endpoint. Only can send grant_type and scopes fields.

So it looks like more general issue inside the library.

@hoaza
Copy link

hoaza commented Jan 16, 2024

I've encountered the same issue. I'm using ClientCredentials in the Security Definition and my security requirement just is pretty basic. Is there a way in the requirement to explicity say to include the client_id or client_secret in the body of the post? It's beginning to look like no one has answer here. I've read the previously referenced link and while I know others don't like spoon feeding answers, but this one is just not going well. I implore one of you to please provide a concrete solution for this. I've been able to use LINQPad to request the token using an HttpClient. This should be very possible from the swagger authorization modal. I did recently notice that the client credentials are being passed along via basic auth in the header. To get it working, I'm certain it needs to be in the body.

---------------TEMPORARY WORKAROUND-------------- It looks like the issue lies within the javascript itself for the swagger to me. Until Swashbuckle catches up to giving an option to include everything in the form body, here's what you can do. You can intercept the request in C# and add onto the body before the request is sent. Within the UseSwaggerUI setupAction (the lambda), do the following: app.UseSwaggerUI(options => { options.UseRequestInterceptor(@"(req) => { console.log(req); if (req.url.includes('**REPLACEWITHYOURAUTHURL**')) { req.body = req.body + '&client_id=**REPLACEWITHYOURCLIENT**&client_secret=**REPLACEWITHYOURSECRET**&audience=**REPLACEWITHYOURAUDIENCE**'; } console.log(req); return req; }"); });

It's UGLY, but it works. Feel free to remove BOTH "console.log(req);". I hate it too, but I think you all deserve something that works as a solution. I tried to format this, but it's not working. -------------------------------------------------------------

unfortunately didn't work for me. Are there any updates on this topic yet?

@russtrotter
Copy link

Following on what wiz-the-engineer did, I can verify that you can get it to work with the request interceptor. If you expand your JS function to do a little parsing of the basic auth header you can avoid hard-coding those values in your code as well, for example, here's my JS interceptor: (in my C# code i pack this up into a resource and strip out the newlines when configuring the swaggerui options):

function fix(req) { if (req.url.startsWith('https://login.microsoftonline.com/')) { const auth = req.headers['Authorization']; if (auth && auth.startsWith('Basic ')) { const parts = atob(auth.substring(6)).split(':'); const clientId = encodeURIComponent(parts[0]); const secret = encodeURIComponent(parts[1]); req.body = req.body + '&client_id=' + clientId + '&client_secret=' + secret; } } return req; }

@woeterman94
Copy link

woeterman94 commented Feb 8, 2024

Any update on this issue?

This page speaks of a property useBasicAuthenticationWithAccessCodeGrant is this something that is affecting this?

@Havunen
Copy link

Havunen commented Mar 5, 2024

Have you guys tested if this works in DotSwashbuckle? But it sounds like swagger UI issue tho...

@mattfrear
Copy link
Contributor

mattfrear commented Apr 10, 2024

@Havunen it is also an issue in DotSwashbuckle 3.0.8.

My issue is similar to what others have screenshotted above. I get a 400 like OP, others in this thread get a 401.

The issue is that client_id and client_secret aren't being sent in the request body, they are being sent in a basic Authorization header. Microsoft Entra Id (Azure AD) needs them to be sent in the request body.

image

@mattfrear
Copy link
Contributor

mattfrear commented Apr 11, 2024

Pasting my swagger.json into editor.swagger.io shows the same behaviour:
image

Downloading and running latest version of Swagger-UI (v5.15.0) and pointing it at my swagger.json results in the same.

So this is probably a swagger-ui bug. Here's an old issue in that repo: swagger-api/swagger-ui#4533

I'm also getting a CORS error from Azure AD in the above screenshot, response is: AADSTS9002326: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type

So based on this CORS error, I wonder if we're barking up the wrong tree trying to get clientcredentials flow working in Swagger UI, since Swagger UI is kind of a SPA.

I vote that we close this issue, since it is a Swagger UI issue.

@BalintBanyasz
Copy link

I believe this is the same issue as #1344.

jwr3408's comment provides a possible solution to this problem.

Here is my modified version that allows you to keep the original token URL in the OpenAPI specification while proxying the requests through your application:

...
services.AddSwaggerGen(c =>
{
    ...

    c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows
        {
            ClientCredentials = new OpenApiOAuthFlow
            {
                TokenUrl = new Uri(authOptions.TokenUrl, UriKind.Absolute),
                ...
            },
        }
    });
    ...
});
...
services.AddProxies();
  • Configure:
...
const string OAuthProxyUrl = "oauth-proxy";
app.UseProxies(proxies =>
{
    proxies.Map(OAuthProxyUrl, proxy => proxy.UseHttp(authOptions.Value.TokenUrl,
        builder => builder.WithShouldAddForwardedHeaders(false)));
});
...
app.UseSwaggerUI(c =>
{
    ...

    // Redirect request to the OAuth proxy
    c.UseRequestInterceptor($"(req) => {{ if (req.url === '{authOptions.Value.TokenUrl}') {{ req.url = '/{OAuthProxyUrl}'; }} return req; }}");
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
responded Responded with solution or request for more info
Projects
None yet
Development

No branches or pull requests