Swashbuckle.AspNetCore
Swashbuckle.AspNetCore copied to clipboard
ClientCredentials Flow Doesn't Send client_id and client_secret as Form Data Fields and Throw Auth ErrorTypeError: Failed to fetch
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.
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?
+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.
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.
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,
@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? 😜
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.
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 theclient_secret
is missing from the request to thetoken
endpoint.
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
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
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. -------------------------------------------------------------
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.
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?
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; }
Any update on this issue?
This page speaks of a property useBasicAuthenticationWithAccessCodeGrant is this something that is affecting this?
Have you guys tested if this works in DotSwashbuckle? But it sounds like swagger UI issue tho...
@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.
Pasting my swagger.json into editor.swagger.io shows the same behaviour:
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: https://github.com/swagger-api/swagger-ui/issues/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.
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:
- Install the AspNetCore.Proxy NuGet package
- ConfigureServices:
...
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; }}");
});
This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.
This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.
Please, don't close this issue without a solution in your UI.
I think it should be closed, this isn't a Swashbuckle issue.
Please, don't close this issue without a solution in your UI.
Yeah its third party UI
I've another workaround for this issue (not need to use UseRequestInterceptor):
:one: AddSecurityDefinition
Register a different relative Url for the SwaggerUI to get the token:
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
TokenUrl = new Uri("oauth2/token", UriKind.Relative),
Scopes = new Dictionary<string, string>
{
{ $"api://{configuration.GetRequiredSection("AzureAd")["ClientId"]}/.default", ".default"}
}
}
}
});
:two: Use AspNetCore.Proxy
Use the AspNetCore.Proxy to proxy the oauth2/token
url to the login microsoft url. And make sure to remove the Origin
and Referer
headers, else you get:
Auth ErrorError: response status is 400, error: invalid_request, description: AADSTS9002326: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type. Request origin: 'https://localhost:5001'. Trace ID: 706d4105-0835-4a8a-9376-696190d97100 Correlation ID: 0ff4adbb-8004-4de9-93b7-37c87468281c Timestamp: 2024-06-25 11:08:19Z
if (app.Environment.IsDevelopment())
{
app.UseProxies(proxies =>
{
var tenantId = app.Configuration.GetRequiredSection("AzureAd")["TenantId"];
proxies.Map("swagger/oauth2/token", proxy => proxy.UseHttp($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", optionsBuilder => optionsBuilder
.WithBeforeSend((_, httpRequestMessage) =>
{
httpRequestMessage.Headers.Remove("Origin");
httpRequestMessage.Headers.Remove("Referer");
return Task.CompletedTask;
})
.WithShouldAddForwardedHeaders(false)
));
});
}
3️⃣ UseSwaggerUI
Configure UseSwaggerUI to use the Id and secret (only when in development mode)
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.OAuthClientId(app.Configuration.GetRequiredSection("SwaggerUI")["ClientId"]);
options.OAuthClientSecret(app.Configuration.GetRequiredSection("SwaggerUI")["ClientSecret"]);
options.DisplayRequestDuration();
options.OAuthAppName(typeof(Program).Assembly.GetName(false).Name);
});
}
:gear: appsettings
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "***",
"ClientId": "***"
},
"SwaggerUI": {
"ClientId": "***",
"ClientSecret": "***"
}
}
💻 Demo
Clicking the "Authorize 🔒" button in Swagger UI shows:
And clicking the "Authorize" button successfully gets a valid token which can be used:
I've another workaround for this issue (not need to use UseRequestInterceptor):
It's cool you've got it working, but whether this is useful or not depends on what you're using Swagger for. If you use it as a test harness for testing your API, then this workaround is acceptable.
If however you're using Swagger as living documentation of your API, then this workaround is misleading. Consumers of your API will think that they should call oauth2/token to get a token, instead of https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token.