6

I'm trying to implement swagger into my Asp.Net Web API, and i'm running into a problem.

I'm using the password resource owner flow, and i'm having to add a work around in order to do this, which is covered in the following stack overflow question :-

Swagger/Swashbuckle: OAuth2 with Resource Owner Password Credentials Grant

I've got everything working, the Bearer token is added via javascript to the request header in the current browser window, but the api calls to the controller methods requiring authorization are still return "401 - Authorization Failed".

Here is the JavaScript that gets the bearer token and adds the header :-

 $('#input_apiKey').change(function () {
    var key = $('#input_apiKey')[0].value;
    var credentials = key.split(':'); //username:password expected
    $.ajax({
        url: "http://localhost:42291/token",
        type: "post",
        contenttype: 'x-www-form-urlencoded',
        data: "grant_type=password&username=" + credentials[0] + "&password=" + credentials[1],
        success: function (response) {
            var bearerToken = 'Bearer ' + response.access_token;

            window.swaggerUi.api.clientAuthorizations.add('Authorization', new window.SwaggerClient.ApiKeyAuthorization('Authorization', bearerToken, 'header'));
            window.swaggerUi.api.clientAuthorizations.remove('api_key');

            alert("Login Succesfull!");
        },
        error: function (xhr, ajaxoptions, thrownerror) {
            alert("Login failed!");
        }
    });
}); 

The Curl in the response in Swagger is :-

curl -X GET --header "Accept: application/json" --header "Authorization: Bearer NqlSG-WyTx2zkYE8xFklGyZWlQDZdsCKZBHruEXvX47N7PAzw4-jZ4eH5D0yFzQTXj13RwKFFt1rUZt2fzWj1vR5UR87wdlKC3YvsTojYV4-3DsWwY7qYRfiKPuM0j09c3X5lnrtlBVJ1rBRUH0TLjfw_yGxgoLBwOJl9xyC1YWNoPOe2nzL4lMOHodAnMem0IBMJmUo3Rt575tnWAbBsQXWhlImDIxCZXvkZdJtlXfIfBSUdY9gfRWL0ZjKbf7m2-yLzH0gpMAMuKaADmJlIudJc0d4SP1Nn2Kh2HuVH8CX4QgZuu4egl9N6rY2smorP2vBSC4_dC4CpmYYzOTu2wUnUhHDY2Q6NWl377ijDKwZLcW9jtD-2tBiEGmFuRV0mVGnh0zc4w9Ao9jPCdtrbSyGitgloBW-UG2bfyao3eE" "http://localhost:42291/api/v1/claims"

I cant see anything wrong with this at all.

I've then used Postman to call the exact same URL call, using the same access token that was generated in the javascript call...

Guess what... it works fine.

EDIT

I've tried removing the authorization attribute from the controller, so that i can check the request as it hits the controller method.

looking in the request headers, the Authorization property is null.

Not sure why this is. the CURL suggests its been placed into the request.

EDIT 2

Ive included my Security Definitions:-

"securityDefinitions": {
        "oauth2": {
            "type": "oauth2",
            "description": "OAuth2 Password Grant",
            "flow": "password",
            "tokenUrl": "http://localhost:42291/token",
            "scopes": {}
        }
    }

EDIT 3 The cURL displayed in the Swagger UI for this api call, when exposed through cURL directly at the command line works without issue.

Now I'm completely confused.

Community
  • 1
  • 1
Derek
  • 8,300
  • 12
  • 56
  • 88
  • 1
    Please use Fiddler (http://www.telerik.com/fiddler) to check the difference between the two calls (swagger and postman). Also for a test I would change add('Authorization'... to add('someUniqueKey'... – Dunken Jan 27 '16 at 07:19
  • Do you really need `securityDefinitions`? I didn't... though I need an older version. – Dunken Jan 27 '16 at 10:12
  • I think this authorization denial is failing earlier in the pipeline, it seems im not the only person to have this issue. I think it could be something to do with the pre-flight OPTIONS request being hijacked by IIS. Man this sucks! – Derek Jan 27 '16 at 10:16
  • With Chrome Developer tools (Ctrl-Shift-I) you could debug swagger in the client... You could then check if you 1. get a token and 2. whether the token gets applied to your requests... – Dunken Jan 27 '16 at 10:20
  • Ive added more information, its getting pretty interesting now, if youhave time to read it all! – Derek Jan 27 '16 at 13:11
  • 1
    Could be a CORS issue. Make sure your API supports CORS. – Dunken Jan 28 '16 at 08:01
  • I have it enabled, but I'll do some testing to see if its working as it should be. Thanks! – Derek Jan 28 '16 at 08:50
  • can you check https://stackoverflow.com/questions/38746372/swagger-oauth-got-it-so-now-what ? – Gobliins Aug 04 '16 at 06:28

2 Answers2

8

I've managed to correct the problem. It was a simple type mismatch that has caused me days of grief.

In the onComplete.JS, i needed to create a key that matches the key presented in the swagger specification.

If you examine my code snippets above you will see that i created a key and called it "Authorization". But that does not match the named security definition "oauth2".

The working code :-

$('#input_apiKey').change(function () {
    var key = $('#input_apiKey')[0].value;
    var credentials = key.split(':'); 
    $.ajax({
        url: "http://localhost:42291/token",
        type: "post",
        contenttype: 'x-www-form-urlencoded',
        data: "grant_type=password&username=" + credentials[0] + "&password=" + credentials[1],
        success: function (response) {

            var bearerToken = "Bearer " + response.access_token;

            window.swaggerUi.api.clientAuthorizations.remove('api_key');

            var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization("Authorization", bearerToken, "header");

            window.swaggerUi.api.clientAuthorizations.add('oauth2', apiKeyAuth);

            alert("Login Succesfull!");

        },
        error: function (xhr, ajaxoptions, thrownerror) {
            alert("Login failed!");
        }
    });
});

Just to explain this a bit further, you need to create an implementation of IOperationFilter so that swagger can determine which methods of the api require Authorizaion. When you have configured this correctly, you should see a security definition against each api call in the swagger specification :-

enter image description here

My implementation of IOperationFilter :-

public class AssignOAuth2SecurityRequirements : IOperationFilter
    {
        /// <summary>
        /// Apply Security Measures.
        /// </summary>
        /// <param name="operation"></param>
        /// <param name="schemaRegistry"></param>
        /// <param name="apiDescription"></param>
        /// <exception cref="NotImplementedException"></exception>
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
        {
            // Determine if the operation has the Authorize attribute
            var authorizeAttributes = apiDescription.ActionDescriptor.GetCustomAttributes<AuthorizeAttribute>();

            if (!authorizeAttributes.Any())
                return;

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

            // Add the appropriate security definition to the operation
            var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
            {
                { "oauth2", Enumerable.Empty<string>() }
            };

            operation.security.Add(oAuthRequirements);
        }
    }
Derek
  • 8,300
  • 12
  • 56
  • 88
  • I had a different issue however, this pointed me in the right direction: "oauth2", Enumerable.Empty(); You need to have a different name for each grant type and they must have an entry in the implementation of IOperationFilter – twoleggedhorse May 01 '17 at 14:54
  • @Derek - I tried following your instructions but was unable to resolve my problem, can you please look into my question https://stackoverflow.com/questions/47976035/swagger-ui-oauth-password-flow-retrieve-and-add-token-to-authorized-requests – Jitender Sharma Dec 27 '17 at 06:18
3

The authorization mechanism expects that each operation has a security assigned to it. If not present, the header won't be sent. Please share your spec file if you think it's properly assigned

fehguy
  • 6,724
  • 25
  • 23
  • Hey, I've added my security definitions to the Question, can you see any problems with it? fehguy – Derek Jan 27 '16 at 08:59
  • Ive checked my swagger json, the auth2 tag is attached to those methods that expect authorization, so that all seems correct. When i look at the request in Fiddler, there is no auth header. – Derek Jan 27 '16 at 09:26