Guidance on Enabling oAuth 2.0 in Generated Swagger Docs

@MSWCRB To add your own oauth authentication to Swagger UI you need to modify openapi.json which is generated by ServiceStack.Api.OpenApi plugin and available if you point to http://yoursite.com/openapi URL. You need to add securityDefinitions object to this json and describe here how your authentication provider works. Here is the blog post which explains how security definitions can be configured for json file.

When you’ll get what need to be added to json file for describing your authentication schema, you can move these changes to auto-generated json which is available at /openapi path. To do this you need to add ApiDeclarationFilter which I mentioned in previous post and add OpenApiSecuritySchema in it which is mapped 1 to 1 from json definition you’ve got on previous step.

@MSWCRB For generating OpenApi specification which supports bearer token you can use following code

Plugins.Add(new OpenApiFeature{
    ApiDeclarationFilter = api => api.SecurityDefinitions.Add("bearer", new OpenApiSecuritySchema
        {
            Type =  "apiKey",
            Name = "Authorization",
            In = "header"
        }),
    OperationFilter = (name, op) =>
    {
        if (op.Security?.Count > 0) op.Security.Add(
            new Dictionary<string, List<string>>() {{"bearer", new List<string>() }}
        );
    }
});

As @mythz mentioned above you should write BearerToken {JWT or BearerToken} in Swagger UI api_key authorization, because Swagger UI does not support bearer tokens out of the box yet.

Thanks for the responses, but I’m not sure I follow. Let me ask some questions:

  1. In the approach you describe above, before we even get to Swagger, do I need to replace the custom AuthProvider implementation with something else? if so, what?
  2. If not and the API continues to function as it does today, and all we are changing is related to Swagger, I don’t understand how to wire up to the Identity Server login page in a way that will cause the token it returns to be passed in to the API call when someone clicks “try it out.”

I think we are getting there - just need some additional guidance.

thanks!

You do not need to change your custom AuthProvider, all changes are related only to Swagger UI/OpenAPI.

For the solution I’ve described it works with token you’ve already got before you clicked “authorize” button in Swagger UI. I think you have to customize Swagger UI authorization page and include your own methods to get this token from Identity Server and insert it into api_key field when authorizing.

You can override built-in Swagger UI sources as described here and put your customized page instead of default one.

Thanks again - getting to clarity. I added the code you detailed to add the OpenApiFeature for bearer token, and switched my Plugin registration to OpenApiFeature (from SwaggerFeature) but that broke the following code:

   ApiDeclarationFilter = api =>
                {
                    api.Apis.RemoveAll(x => x.Path.Contains("/auth"));
                }

whats the OpenApi equivalent configuration?

Also, I can’t figure out where the file is that produces the auth popup page. I dont see anything resembling the /swagger-ui
/css
/images
/lib
index.html
mentioned in the docs…

In ServiceStack.Api.OpenApi you’d deal with api.Paths dictionary. Its keys contains the route path.

ApiDeclarationFilter = api =>
{
   foreach(var key in api.Paths.Key.ToArray())
   {
      if (key.Contains("/auth")
         api.Paths.Remove(key);
   }
}

I can’t tell what exactly need to change in Swagger UI to modify auth popup, because it’s a separate project which does not belong to ServiceStack, but you can get Swagger UI 2.2.10 sources here and ask Swagger UI devs where to inject the code for customizing this popup. I recommend to use v 2.2.10, because v 3+ had issues with header params.

What you need to do just copy swagger files to the directory, all of these folders (swagger-ui/css, swagger-ui/fonts etc) came from distribution files of Swagger UI 2.2.10

First off - thanks again for the response. In digging around the interwebs (partially because it altogether wasn’t clear to me how to proceed with overwriting the auth popup page in compiled Swagger code) I discovered indications that oauth2 IS supported natively in Swagger, and proceeded to add the following configuration:

  Plugins.Add(new OpenApiFeature
  {
      ApiDeclarationFilter = api => api.SecurityDefinitions.Add(
          "bearer", new OpenApiSecuritySchema { 
                Type = "oauth2", 
                AuthorizationUrl= "https://OURAUTHSERVER/connect/authorize", 
                Flow="implicit", 
                Scopes = new Dictionary<string, string>() { { "myapi", "My API" } }, 
                Description="oAuth2 Implicit Grant", 
                Name = "Authorization", 
                In = "header" }),
       
      OperationFilter = (name, op) => { 
           if (op.Security?.Count > 0) 
              op.Security.Add(new Dictionary<string, List<string>>() { { "bearer", new List<string>() } }); }
 });                 

Now, when the Authorization is clicked (the blue exclamation point button with each API) there is a popup that appears with an authorize button. When clicked, it automatically redirects to the oAuth server specified and with some fiddling (see issues below) it allows a login attempt and return back to the Swagger doc with a token that is used in the header when it attempts to call the API. I am hoping this approach can work as it is much preferred vs overwriting custom auth popup. Here are the issues that exist, I am hoping you/someone can help them to resolution such that we will have a complete guide on how to integrate ServiceStack/Swagger with oAuth2 for us and any other users.

Open Issues:

  1. The auth popup displays both oAuth2 as an option along with Basic Authentication. Basic should not be there - how do we make it go away? I assume this has to do with the code you suggested Adding to the Security collection.
  2. The scope specified in my code (myapi), is not displayed as a selectable option in the oauth2 section in the popup. Based on the Swagger documentation I have seen, it should be shown there. Because it is not shown, it is not selected and therefore the URL that gets generated and passed to the Auth server when the authenticate button is clicked is invalid as it shows scope= without any value.
  3. The URL generated also doesn’t insert the clientid field (instead it had clientid=your_client_id. This isn’t surprising because the configuration gave no place to specify the clientid. The fact that the URL is generated with the clientid field would imply there is some way to specify it…
  4. The Auth server responds with both a JWT and a reference token and the Swagger doc decided to use the reference token instead of the JWT and pass that in as the bearer token in the header. The API fails as it expects the JWT.

I believe these issues are solvable as we have Swashbuckle running on top of WebAPI in our current version. I am including below the configuration sections from that implementation in case that helps at all:

GlobalConfiguration.Configuration 
            .EnableSwagger(c =>
               c.OAuth2("oauth2")
                        .Description("OAuth2 Implicit Grant")
                        .Flow("implicit")
                        .AuthorizationUrl("https://AUTHURL/connect/authorize")
                        .Scopes(scopes =>
                        {
                            scopes.Add("myapi", "My API");
                        });
                c.OperationFilter<AssignOAuth2SecurityRequirements>();
              })
            .EnableSwaggerUi(c =>
                   c.EnableOAuth2Support(
                        clientId: "myui",
                        realm: "realm",
                        appName: "Swagger UI"
                    );
                });
                  
public class AssignOAuth2SecurityRequirements : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        var actFilters = apiDescription.ActionDescriptor.GetFilterPipeline();
        var allowsAnonymous = actFilters.Select(f => f.Instance).OfType<OverrideAuthorizationAttribute>().Any();
        if (allowsAnonymous)
            return; // must be an anonymous method

        if (operation.security == null)
            operation.security = new List<IDictionary<string, IEnumerable<string>>>();

        var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
        {
            {"oauth2", new List<string> {"myapi"}}
        };

        operation.security.Add(oAuthRequirements);
}}

Thanks in advance for whoever picks up the mantle here :slight_smile:

The issues you’re having is trying to configure the 3rd Party Swagger UI to work with your Custom IdentityServer based Auth Provider using Swagger UI’s built-in OAuth2 support. This isn’t a supported configuration and one we don’t have access to.

We’ve only enabled support for Basic Auth in Swagger UI and as I mentioned in my initial answer if you’re using any of the other built-in ServiceStack Auth Providers you can authenticate outside Swagger UI then return to Swagger UI to make authenticated requests using Cookies.

There’s 2 parts to Swagger UI, the generated OpenAPI spec which ServiceStack Automatically generates based on your Services and the Filters available on OpenApiFeature which is where you can customize the OpenAPI spec response that’s sent to Swagger UI as @xplicit has provided an example of for enabling API Key and Bearer Token Support.

The end result is consumed by Swagger UI to generate its Dynamic UI, we use the vanilla Swagger UI source-code from the https://github.com/swagger-api/swagger-ui project embedded in the ServiceStack.Api.OpenApi.dll which as linked to earlier, you can override the Swagger UI static files used by providing local copies in your project.

So you need to first find what a valid openapi.json configuration which works with Swagger UI then use the Custom Filters to apply the same modifications in the OpenApiFeature which should be straight-forward as they follow a 1:1 mapping with the Open API 2.0 Specificiation, if there are any properties missing let us know and we’ll add it.

Ideally if you supply the correct configuration it will just work as-is, otherwise you’ll likely need to make changes to Swagger UI as well. If there’s something you need us to enable to assist with generating the required OpenAPI configuration we can add it, but we can’t provide any further insight than this.

Response appreciated but not sure where to go from here. Are you saying the approach I showed above of using the built in oauth2 type wont work (in which case I need to scap it and use a different approach altogether - e.g. overwriting the popup page which still isn’t clear how to do - that link you provide doesnt explain how)? Or are you saying you don’t know how to get it to work because its beyond ServiceStack’s code base (in which case maybe someone else can help me work through the 4 issues I mentioned above)?

thanks.

For removing BasicAuth you can use ApiDeclarationFilter and SecurityDefinitions property. Just remove basic auth from SecurityDefinitions and you won’t see it in authorization page.Also you need to use OperationFilter and remove basic from Security property.

Do I correctly understand that you have swashbuckle configuration which works with this oauth server and Swagger UI? If it works can you share swashbuckle-generated swagger json? In this case we can say which filters you need to apply to get the same output for Swagger UI.

I have no idea if Swagger UI’s OAuth2 Type will work, we’ve never used it and wouldn’t work with any of our built-in OAuth/OAuth2 providers which Authenticate using OAuth Web flow and explicit AccessToken, not a bearer token that it seems to require.

I would consider taking the approach @xplicit mentions in this comment where you’d take the openapi.json generated by ServiceStack’s /openapi endpoint, customize it to a working configuration, then once you find a working configuration apply the changes using the OpenApiFeature filters, if you don’t know how to properly apply the changes send us a working openapi.json configuration and we’ll show you how to add it and add any missing properties if need be.

@mythz I wrote a custom provider (article shown above) that implements SS AuthProvider. Its working fine on the SS API when I call it from consuming applications, postman, etc. The issue here is just with Swagger. If we can get the built in oAuth2 type working that would be ideal (again, not just for me, but for many).

@xplicit here are the relevant sections from the json that swashbuckle generates:
{“swagger”:“2.0”,“info”:{“version”:“v1”,“title”:“My API Documentation”,“description”:""},“host”:“localhost:49717”,“schemes”:[“http”],“paths”:{"/mypath/{id}/attachments":{“post”:{“tags”:[“Attachment”],“operationId”:“Attachment_Post”,“consumes”:[“application/form-data”],“produces”:[“application/json”,“text/json”,“application/xml”,“text/xml”],“parameters”:[{“name”:“id”,“in”:“path”,“description”:“Guid”,“required”:true,“type”:“string”},{“name”:“file”,“in”:“formData”,“description”:“ZIP file. Files other that ‘.zip’ won’t be accepted. If archived file pathes are to long, zip file won’t be accepted.”,“required”:true,“type”:“file”}],“responses”:{“200”:{“description”:“OK”,“schema”:{“type”:“string”}}},“security”:[{“oauth2”:[“myapi”]}]}},"/loan/{id}":{“get”:{“tags”:[“Loan”],“operationId”:“Loan_Get”,“consumes”:[],“produces”:

and

},"securityDefinitions":{“oauth2”:{“type”:“oauth2”,“description”:“OAuth2 Implicit Grant”,“flow”:“implicit”,“authorizationUrl”:“https://AUTHURL/connect/authorize",“scopes”:{“myapi”:"My API”}}}}

unfortunately, neither of those show how to specify the client ID. :frowning:
I included the Swashbuckle config above that shows how it was specified in code.

all that said, with your tip @xplicit I was able to get rid of basic authentication on the popup which also somehow resolved the issue with scope not showing, as it shows now, so we killed 2 of the 4 issues! The major remaining issue is issue #3 - how to populate client_id=your-client-id which is what currently gets generated (with that dummy value) with our specific clientid…

That dummy value seems to be coming from index.html in swagger UI. In their readme, they seem to show how to configure it, but not sure how to tap into that with the OpenApi implementation. If I can’t , then I suppose we need to overwrite the index.html but I’m not sure how to do that since the Swagger implementation here is compiled code. If we can’t use the in-code configuration, how do I overwrite index.html with my own so it gets picked up by the Swagger compiler?

I suppose you already made changes for SecurityDefinitions according to swashbuckle json. If not, here is

new OpenApiFeature(ApiDeclarationFilter = api =>
{
    api.SecurityDefinitions.Add("oauth2", new OpenApiSecuritySchema
    {
        Type = "oauth2",
        Description = "OAuth2 Implicit Grant",
        Flow = "implicit",
        AuthorizationUrl = "https://AUTHURL/connect/authorize",
        Scopes = new Dictionary<string, string> { { "myapi", "My API" } }
    });
},
OperationFilter = (name, op) =>
{
    if (op.Security?.Count > 0)
    {
        op.Security.RemoveAll(d => d.ContainsKey("basic"));
        op.Security.Add(
            new Dictionary<string, List<string>>() { { "oauth2", new List<string> { "myapi" } } }
        );
    }
});

About index.html: you can create /swagger-ui folder in your web site root folder and place modified index.html there as described in ServiceStack.Api.OpenApi docs It will override default index.html which is bundled with OpenApiFeature

@xplicit thanks for the response. Are we saying there is no way to configure the clientID from code, like they show in the Readme?

specifically, they show:

ui.initOAuth({
clientId: “your-client-id”,
clientSecret: “your-client-secret-if-required”,
realm: “your-realms”,
appName: “your-app-name”,
scopeSeparator: " ",
additionalQueryStringParams: {test: “hello”}
})

You can do this, just follow instructions from previous comment:

index.html for modifcation can be downloaded from ServiceStack.Api.OpenApi sources or from Swagger UI sources

@xplicit We are talking past each other a bit - I will be more explicit (haha). There were two options I proposed in post 24 - the first, preferred option, is to configure the value of the clientID in code, the way they suggest in the readme I have linked twice and excerpted from in post 26. Please open the readme I linked and you will see exactly what I am referring to. I am asking if this is possible.

The second option, is to overwrite the index.html file, which would change the default value for clientid. This is not the intended usage by the Swagger folks - it would just be a hack-around. If the first option is not possible, then if you could provide guidance on where in my file structure, relative to the csproj file I would drop that index,.html, I would appreciate it. I can’t see any documentation on how to map the local project directory structure to the in memory structure that gets built at run-time.

thanks!

There has been multiple mentions of how to override Swagger Files (<- please read) by both myself and @xplicit, where his quoted comment is pretty straightforward, i’ll add emphasis:

Have you attempted to do this?

Every single static /swagger-ui file that’s embedded in OpenApiFeature.dll can be overridden in exactly the same way, these static files are copied directly from the distribution folder of the swagger-ui project - essentially everything in /swagger-ui is overridable.

The other option is to completely ignore the Swagger UI files in OpenApiFeature and just copy them directly from the swagger-ui GitHub project, it just needs a reference to an openapi.json url to generate is dynamic UI which you can get from the /openapi endpoint from your ServiceStack instance.

@mythz @xplicit I’m clearly having trouble communicating the first option as you guys keep talking about the second option which involves overwriting the index.html file. Can someone address what isn’t clear about the first option or at least say that it isn’t possible? Here it is again:

“You can configure OAuth2 authorization by calling initOAuth method with passed configs under the instance of SwaggerUIBundle default client_id and client_secret, realm, an application name appName, scopeSeparator, additionalQueryStringParams.

Can we do this? If you already said its not possible I apologize for asking again - I didnt see it.

@MSWCRB if you open index.html file you will see that this code is contained in index.html file. Change this file as you want and put index.html into /swagger-ui folder in the root of the web site.

Hmm. Please forget the index.html file for now. In the SwaggerUI documentation, it explains how to configure the client_id in code using the initOAuth method. Can this be done via the OpenAPI configuration? Again - this has nothing to do with index.html (I understand the default for these values are set there, but according to their docs, they should be set on the instance of SwaggerUIBundle in code).

I was asking two questions in my earlier posts and I think that was causing the confusion. I am trying only one question now…