updated to the current package and the OAuth Scopes are not beeing shown in the logon UI screen.
looking for the cause and how to fix this...
was working with an other version of the package, scopes were shown and the dialog had me check them.
I'm new to Swashbuckle, and assumed I had done something wrong, but, looks like I'm not the only one struggling with this after all.
From my conversation on this Stackoverflow question I posted I think it's an issue with the swagger-ui project.. which is a standalone project that Swashbuckle somehow consumes. This post covers a slightly different issue than what you mentioned, but, I think the solution is all the same.
From @heldersepu's reply in the above stackoverlfow post:
If this is a must have functionality for your project you should get latest Swashbuckle build it and use that dll instead of getting it from nuget
this may be right, i am looking at some things, one is that the json swagger file has the scopes in it and the same json is doing the right thing with the rest of the oauth settings, so it's like one block of code is having a problem.
i looked at the source repo and i could not see any outright bug but i do not know the react framework that swagger ui uses.
as this package has the ui bundled i can not see the code directly, i am sure i can find it but....
i have also seen another project that i may try out: https://github.com/NSwag/NSwag
up to now Swashbukle has been a great help but i have to solve this and move on.... but i nood to see if nswag can handle OData 4 controllers, i need that!
another possibly option is use only Swashbuckle Core and then go to the main Swagger Ui project and see if that can be made to work , should just by an issue of setting up routing for it i think...
I've had great success using Swashbuckle with OData, there's an extra NuGet you need, but, it's been working great for my OData 4 controllers
I built out all my OData 4 pretty much with pure scaffolding.. then added these necessary NuGets:
<package id="Swashbuckle" version="5.5.3" targetFramework="net461" />
<package id="Swashbuckle.Core" version="5.5.3" targetFramework="net461" />
<package id="Swashbuckle.OData" version="3.3.0" targetFramework="net461" />
No problems whatsoever, but, now that I'm introducing access control logic / authorization.. I'm hitting these wrinkles.
sorry but my post might have been confusing....
with Swashbuckle i have Odata 4 working -- like you.
with the auth bug i was looking at options and saw another package that has swagger but not for Odata.
kind of looks like the problem starts here:
https://github.com/swagger-api/swagger-ui/blob/64dc3060b3700b12e466f8d67b7d7ec3574b015f/src/main/html/index.html
looking at that i see an OauthInit function that passes in parameters for most of the settings but not for scopes.
looks like where the problem may be coming from ??
when i get the popup dialog i can see that there is an html ul tag that has no content.
this seems to be driven by code in a js file "Swagger-oauth-js" as shown by fiddler.
that file has some code that is not getting the scopes for some reason.
here is the code i see that look like it is not working:
//TODO: only display applicable scopes (will need to pass them into handleLogin)
popup = popupDialog.find('ul.api-popup-scopes').empty();
for (i = 0; i < scopes.length; i ++) {
scope = scopes[i];
str = '<li><input type="checkbox" id="scope_' + i + '" scope="' + scope.scope + '"' +'" oauthtype="' + scope.OAuthSchemeKey +'"/>' + '<label for="scope_' + i + '">' + scope.scope ;
if (scope.description) {
if ($.map(auths, function(n, i) { return i; }).length > 1) //if we have more than one scheme, display schemes
str += '<br/><span class="api-scope-desc">' + scope.description + ' ('+ scope.OAuthSchemeKey+')' +'</span>';
else
str += '<br/><span class="api-scope-desc">' + scope.description + '</span>';
}
str += '</label></li>';
popup.append(str);
}
not sure how to fix it but there it is....
Yeah, I noticed some of this code in my Visual Studio debugger...
well.. stepping back to your big picture quesion for a moment: if the problem is in the core swagger-ui project, I don't think switching to another framework is will solve anything
I'm just wondering if the latest swagger-ui .. if I download it as part of the Swashbuckle source files and compile.. has actually addressed all of this. I'm trying to understand how the Swashbuckle DLLs that I compile directly even absorb the swagger-ui HTML/JS.. I can't see any connection (and I've posed that question in my aforementioned S.O. post)
I am also not fully clear on what they are doing.
i know that they are pulling the files from the swagger-ui and putting the content inside a dll as text they then stream back when the request comes in.
i am also wondering if there is some logic that is rejecting the scopes in the swagger ui as i see a comment about what scopes go with a flow or something like that...
starting to look like the json is not right..... checking with the swagger authors...
yes, the json is not following the spec based on what i have been told.
i am setting up a test to verify this and then we can see about a pr submission ....
currently the json is looking like this:
"security": [ { "oauth2": [ "" ] } ]
that did not create a problem in older versions of swashbuckle and the swagger ui but now it is a breaker.
what it should be looking like is:
"security": [ { "oauth2": [ "read" , "write" ] } ]
assuming that read and write are the scopes needed for that api call to authorize.
@bkwdesign
Here is a start on a fix that you can put in place right now:
parts needed and background:
i have a bit of code that checks for a method having an "Authorize" attribute, if there is one it adds the requirements to the json.
wrote the thing over a year back and it was never an issue till now.
so in the SwaggerConfig file add a line like this:
c.OperationFilter
that will call for a class to run while it builds the json.
that class should be like this:
public class AssignOAuth2SecurityRequirements : IOperationFilter {
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) {
// Determine if the operation has the Authorize attribute
var authorizeAttributes = apiDescription.ActionDescriptor.GetCustomAttributes<ATRAKAuthorizeAttribute>();
if (!authorizeAttributes.Any())
return;
// Correspond each "Authorize" role to an oauth2 scope
//var scopes =
// authorizeAttributes
// .SelectMany(attr => attr.Roles.Split(','))
// .Distinct()
// .ToList();
List<string> scopes = new List<string>() { "read", "write" };
// Initialize the operation.security property if it hasn't already been
if (operation.security == null)
operation.security = new List<IDictionary<string, IEnumerable<string>>>();
var oAuthRequirements = new Dictionary<string, IEnumerable<string>>{{ "oauth2", scopes }};
operation.security.Add(oAuthRequirements);
}
}
in my case i have a custom attribute that does not have any roles.
you should be able to modify this code to work for your project.
I had followed some docs I found elsewhere, and actually have this part in place. My generated swagger JSON looks ok.. but, the surrounding UI bits still exhibit this behavior of not showing checkboxes.
well, I have an operationfilter line that looks like this.. probably not that much difference:
c.OperationFilter<AssignOAuth2SecurityRequirements>();
ok i posted like yours but something went wrong ... yes my code is also < TYPE >
does your json have the names of the scopes ??
if you are using the standard .net Authorize attribute then it can be in the form of Authorize or Authorize( "role1","role2")
is your code checking the roles ??
in your json does the security fragment list names that match your definition at the end of the json file ?
every method that needs an auth token needs to have the scopes listed in the security section at the end of the method call , if the names do not match or if the security tag is malformed then it will not work.
when i changed my code to add the scopes it worked.
Denny - you are very kind to keep this thread updated. With your feedback I finally identified what I was missing. I do now have it working!! Thanks man!!
In my case, I did not have the right configuration in SwaggerConfig.cs , in the first section for EnableSwagger(c =>:
I initially had it set like this (don't ask me why):
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit")
.AuthorizationUrl(string.Format("https://login.microsoftonline.com/{0}/oauth2/authorize", ConfigurationManager.AppSettings["ida:Tenant"]))
.Scopes(scopes =>
{
scopes.Add("user_impersonation", "Access swagger");
});
But now it all works when I changed it to an exhaustive listing of my actual roles that I use in my controllers...
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit")
.AuthorizationUrl(string.Format("https://login.microsoftonline.com/{0}/oauth2/authorize", ConfigurationManager.AppSettings["ida:Tenant"]))
.Scopes(scopes =>
{
scopes.Add("AdminAccess", "Admin access to protected resources");
scopes.Add("FullAccess", "Full access to protected resources");
scopes.Add("UpdateAccess", "Update access to protected resources");
scopes.Add("ReadAcces", "Read access to protected resources");
});
@bkwdesign
Not a problem! glad we found the problem!
i have been doing web coding for a long time but about a year and a half ago i dived into OAuth and web api and swagger and it had been quite a trip, many times i have been stuck with stuff just like this....
by the way i have a custom attribute you might want to take a look at....
i do not put my roles in the standard Authorize attribute in my controller class.
we are developing some code to do a table based approach
so my Attribute does a check on tables to match the api method name and then checks a roles to method table to see if that method has roles , then it returns the roles then i get the users roles and check for a match.
so my controller does not have the roles , the database has them.
the default authorize attribute would mean that you have to re-build and re-deploy the controllers to change the roles for the methods .
for my setup i update the tables.
@figuerres - so.. how can I see this special authorize attribute? I've been thinking about this problem.
My Web API is only returning 401 Not Authorized responses - fwiw. I'm noticing that swagger's chatter looks a bit different that ADAL.js' chatter, when viewed via Fiddler. (I'm using Azure AD)
Are you getting valid Web API responses?
i will get the code and put it up on a github repo in the next day or two.
it's really not a huge deal of code.
for the responses might need some digging, might be best to pick that up on the site where i post the code.
in my case i am using IdentityServer 3 and some custom code.
but you might be facing some of the same issues i had when i started.
i was not seeing the authenticated IPrincipal getting the roles / claims to match the required roles to authorize the user. with your own Authorize attribute you can break into the code and check that out.
read this for part of the possible issues:
https://github.com/IdentityServer/IdentityServer3.Samples/issues/173
also see this:
https://leastprivilege.com/2016/08/21/why-does-my-authorize-attribute-not-work/
i will post a link here when my code is online to check out.
or check here https://github.com/figuerres for it to show up....
OK, @figuerres, I figured out my final issues (manifold) which were preventing me from getting everything to work.
1) Azure deployment slots: I was identifying my app, in my staging slot, using the ClientID and Audience values that were meant for the production application slot / production app registration.
2) User Assignments: I'm using Azure's role based access control (RBAC), but, I had neglected to actually assign any users to any roles.
3) Put the token in the header: Finally, I had to use this hack to ensure my swagger submitted the acquired bearer token through the header, via the 'Authorization' header value.
look at https://github.com/figuerres/AuthorizeAttribute
that is not a finished wrapped up package but has some code for you to work with.
I will add some more details but take a look , needs info on the tables i am using but not hard i think to follow and modify.
i have aspnet roles table with role name and id, i also have a webservice table that has a row for each method name in the form of ControllerName.MethodName
so i get the roles for the user in one list and the roles for the method in another list and use LINQ's Intersect() .Any() to tell me if the two lists have any shared values, got one or more you can call the method. got zero - then reject the call.
Hi @figuerres , you have tremendously help me.... thanks for the explanation now I understand what is happening...
But the link to https://github.com/figuerres/AuthorizeAttribute is wrong..., for some reason it is pointing to https://github.com/domaindrivendev/Swashbuckle/issues/url instead..
Cheers!
I'm using latest Swashbuckle.core, I have the following code
c.OAuth2("oauth2")
.Description("OAuth2 Password Grant")
.Flow("password")
.TokenUrl("https://mysts/core/connect/token")
.Scopes(scopes =>
{
scopes.Add("openid", "Required 1");
scopes.Add("email", "Required 2");
scopes.Add("all_claims", "Required 3");
});
c.OperationFilter<AssignOAuth2SecurityRequirements>();
public class AssignOAuth2SecurityRequirements : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
// Correspond each "Authorize" role to an oauth2 scope
var scopes = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<AuthorizeAttribute>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
if (scopes.Any())
{
if (operation.security == null)
operation.security = new List<IDictionary<string, IEnumerable<string>>>();
var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", scopes }
};
operation.security.Add(oAuthRequirements);
}
}
}
When I go to swagger and click on the red circle with exclamation mark, which pops up the modal "Available authorizations" where we insert the username, password, client_id and secret, but the scopes are empty.
If I turn on Fiddler and see the request done to the sts, the scopes are empty there as well.
Am I missing something or is this the actual bug that is described above in this page?
EDIT
Ok I was reviewing the code, and I noticed what I'm doing. I'm reading the attributes and getting the roles. What I actually want to do is inside AssignOAuth2SecurityRequirements, get the scopes that I've defined in the configuration (c.OAuth2...)
Most helpful comment
@bkwdesign
Here is a start on a fix that you can put in place right now:
parts needed and background:
i have a bit of code that checks for a method having an "Authorize" attribute, if there is one it adds the requirements to the json.
wrote the thing over a year back and it was never an issue till now.
so in the SwaggerConfig file add a line like this:();
c.OperationFilter
that will call for a class to run while it builds the json.
that class should be like this:
public class AssignOAuth2SecurityRequirements : IOperationFilter { public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) { // Determine if the operation has the Authorize attribute var authorizeAttributes = apiDescription.ActionDescriptor.GetCustomAttributes<ATRAKAuthorizeAttribute>(); if (!authorizeAttributes.Any()) return; // Correspond each "Authorize" role to an oauth2 scope //var scopes = // authorizeAttributes // .SelectMany(attr => attr.Roles.Split(',')) // .Distinct() // .ToList(); List<string> scopes = new List<string>() { "read", "write" }; // Initialize the operation.security property if it hasn't already been if (operation.security == null) operation.security = new List<IDictionary<string, IEnumerable<string>>>(); var oAuthRequirements = new Dictionary<string, IEnumerable<string>>{{ "oauth2", scopes }}; operation.security.Add(oAuthRequirements); } }in my case i have a custom attribute that does not have any roles.
you should be able to modify this code to work for your project.