Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enable Oauth2 client credentials flow in Swashbuckle

Im using IdentityServer3 to secure a Web API with the client credentials grant. For documentation Im using Swashbuckle but can't figure out how to enable Oauth2 in the SwaggerConfig for the client credentials (application) flow. Any help would be appreciated!

like image 201
mstrand Avatar asked Nov 17 '15 09:11

mstrand


People also ask

What is OAuth2 client credentials flow?

The OAuth 2.0 client credentials grant flow permits a web service (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling another web service.

How do I use client ID and client secret in swagger?

In the grant_type field, enter client_credentials. In the client_id field, enter/paste the client ID value generated with your API credentials. In the client_secret field, enter/paste the client secret value generated with your API credentials. At the bottom of the POST operation panel, click the Try it out!

How do I add OAuth2 to swagger?

Describing OAuth 2.0 Using OpenAPI. To describe an API protected using OAuth 2.0, first, add a security scheme with type: oauth2 to the global components/securitySchemes section. Then add the security key to apply security globally or to individual operations: # Step 1 - define the security scheme.


2 Answers

I was able to get this working. Most of the answer can be found here.

There were a few parts I had to change to get the client_credential grant to work. The first part is in the EnableSwagger and EnableSwaggerUi calls:

config.EnableSwagger(c => 
  {
    c.SingleApiVersion("v1", "sample api");
    c.OAuth2("oauth2")
     .Description("client credentials grant flow")
     .Flow("application")
     .Scopes(scopes => scopes.Add("sampleapi", "try out the sample api"))
     .TokenUrl("http://authuri/token");
    c.OperationFilter<AssignOAuth2SecurityRequirements>();
  }).EnableSwaggerUi(c =>
  {
    c.EnableOAuth2Support("sampleapi", "samplerealm", "Swagger UI");
  });

The important change here is .Flow("application") I also used the .TokenUrl call instead of .AuthorizationUrl This is just dependent on your particular authorization scheme is set up.

I also used a slightly different AssignOAuth2SecurityRequirements class

public class AssignOAuth2SecurityRequirements : IOperationFilter
{
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
      var authorized = apiDescription.ActionDescriptor.GetCustomAttributes<AuthorizeAttribute>();
      if (!authorized.Any()) return;

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

      var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
      {
          {"oauth2", Enumerable.Empty<string>()}
      };

      operation.security.Add(oAuthRequirements);
    }
}

This should be sufficient to get the authentication switch to show. The other problem for me was that the default authentication dialog is set up so a user just has to select a scope and then click authorize. In my case this didn't work due to the way I have authentication set up. I had to re-write the dialog in the swagger-oauth.js script and inject it into the SwaggerUI.

like image 135
Bill Clyde Avatar answered Sep 20 '22 12:09

Bill Clyde


I had a bit more trouble getting this all working, but after a lot of perseverance I found a solution that works without having to inject any JavaScript into the SwaggerUI. NOTE: Part of my difficulties might have been due to using IdentityServer3, which is a great product, just didn't know about a configuration issue.

Most of my changes are similar to bills answer above, but my Operation Filter is different. In my controller all the methods have an Authorize tag with no Roles like so:

[Authorize]
// Not this
[Authorize(Roles = "Read")] // This doesn't work for me.

With no Roles defined on the Authorize tag the OperationFilter looks like this:

    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        // Correspond each "Authorize" role to an oauth2 scope, since I don't have any "Roles" defined, this didn't work
        // and is in most of the Apply methods I found online.  If you are like me and your [Authorize] tag doesn't contain
        // any roles this will not work.
        //var scopes = apiDescription.ActionDescriptor.GetFilterPipeline()
        //    .Select(filterInfo => filterInfo.Instance)
        //    .OfType<AuthorizeAttribute>()
        //    .SelectMany(attr => attr.Roles.Split(','))
        //    .Distinct();

        var scopes = new List<string>() { "Read" }; // For me I just had one scope that is added to all all my methods, you might have to be more selective on how scopes are added.

        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);
        }
    }

The SwaggerConfig looks like this:

public static void Register()
{
    var thisAssembly = typeof(SwaggerConfig).Assembly;
    GlobalConfiguration.Configuration
        .EnableSwagger(c =>
        {
           c.SingleApiVersion("v1", "waPortal");
           c.OAuth2("oauth2")
                .Description("OAuth2 Client Credentials Grant Flow")
                .Flow("application")
                .TokenUrl("http://security.RogueOne.com/core/connect/token")
                .Scopes(scopes =>
                {
                    scopes.Add("Read", "Read access to protected resources");
                });
            c.IncludeXmlComments(GetXmlCommentsPath());
            c.UseFullTypeNameInSchemaIds();
            c.DescribeAllEnumsAsStrings();
            c.OperationFilter<AssignOAuth2SecurityRequirements>();
        })
        .EnableSwaggerUi(c =>
        {
            c.EnableOAuth2Support(
                clientId: "swaggerUI",
                clientSecret: "BigSecretWooH00",
                realm: "swagger-realm",
                appName: "Swagger UI"
            );
        });
}

The last part was the hardest to figure out, which I finally did with the help of the Chrome Developer tools that showed a little red X on the network tag showing the following error message:

XMLHttpRequest cannot load http://security.RogueOne.com/core/connect/token. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:62561' is therefore not allowed access.

I described this error here Swagger UI not parsing reponse which was due to IdentityServer3 correctly not adding a response header of "Access-Control-Allow-Origin:http://localhost:62561" You can force IdentityServer3 to send that header by updating you client creation to be the following:

new Client
{
    ClientName = "SwaggerUI",
    Enabled = true,
    ClientId = "swaggerUI",
    ClientSecrets = new List<Secret>
    {
        new Secret("PasswordGoesHere".Sha256())
    },
    Flow = Flows.ClientCredentials,
    AllowClientCredentialsOnly = true,
    AllowedScopes = new List<string>
    {
        "Read"
    },

    Claims = new List<Claim>
    {
        new Claim("client_type", "headless"),
        new Claim("client_owner", "Portal"),
        new Claim("app_detail", "allow")
    },
    PrefixClientClaims = false
    // Add the AllowedCorOrigins to get the Access-Control-Allow-Origin header to be inserted for the following domains
    ,AllowedCorsOrigins = new List<string>
    {
        "http://localhost:62561/"
        ,"http://portaldev.RogueOne.com"
        ,"https://portaldev.RogueOne.com"
    }
}    

The AllowedCorsOrigins was the last piece of my puzzle. Hopefully this helps someone else who is facing the same issue

like image 27
user2197446 Avatar answered Sep 21 '22 12:09

user2197446