Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to loading a Swagger spec (.json/.yaml) that is protected by Basic auth. UI version 3.0 #2793

Closed
domaalex opened this issue Mar 27, 2017 · 34 comments

Comments

@domaalex
Copy link

http://stackoverflow.com/questions/42995594/how-to-use-basic-auth-in-swagger-ui-v-3-0

@domaalex domaalex changed the title Add possibility to loading a Swagger spec (.json/.yaml) that is protected by Basic auth Add possibility to loading a Swagger spec (.json/.yaml) that is protected by Basic auth. UI version 3.0 Mar 27, 2017
@webron
Copy link
Contributor

webron commented Mar 27, 2017

@ponelat did we end up exposing a method for doing that?

@ponelat
Copy link
Member

ponelat commented Mar 28, 2017

Nope... ( I mean its possible, but ugly ).
I suggest the following ( ping @fehguy @shockey )

  • We expose a swaggerProxy option, that will act as requestInterceptor / responseInterceptor pipeline for decorating requests for swagger specs.

  • We expose a simple interface for adding any auths ( ie: finish off the auth feature )

    • Swagger.ApiKeyAuthorization, Swagger.PasswordAuthorization, Swagger.HeaderAuthorization, Swagger.OAuthTokenAuthorization

Here are some ideas...

Swagger({ 
  url: '...',
  swaggerAuthorizations: [ // Note, an array of Auths only applied to fetching swagger specs
    Swagger.ApiKeyAuthorization('myQuery', 'foo', 'query'),
    Swagger.QueryAuthorization('myQuery', 'bar'), // Better than ApiKeyAuthorization?
    Swagger.PasswordAuthorization('fooUser', 'fooPaswsword')
  ]
}).then(...)
Swagger({
  url: '...',
  swaggerProxy(req)  {
     req.headers.Authorization = 'bar'
    // or
    this.passwordAuth(req, 'fooUser', 'fooPassword') // Add a basic auth
    return this.http(req)  // Continue the request
    // or
   return // undefined, we'd infer that we should continued the request as above. In case the user forgets to
  }
}).then(...)

My favourite, ( ie: both of the above )...

Swagger({ 
  url: '...',
  swaggerProxies: [ // Note, an array of functions, applied to fetching swagger specs
    Swagger.PasswordAuthorization('fooUser', 'fooPaswsword'), // decorate request with this auth
    ...,
    (req) => { // We can write our own decorators...
       if(this.matchUrl(req.url, 'example.com') {   // Might be a useful helper
         req.headers['X-ThisIsASpecialRequest'] = true // Whatever
         return Swagger.HeaderAuthorization('FooHeader', 'bar')(req) // we can also use the helpers.
       }
    }
  ],
  tryItOutProxies: [ ... ] // Same for try-it-out-requests?
}).then(...)

@webron
Copy link
Contributor

webron commented Mar 29, 2017

@ponelat I think @buunguyen had some thoughts on that. I guess the main question is, why change the behavior from 2.X?

@ponelat
Copy link
Member

ponelat commented Mar 29, 2017

The key difference is that we were applying auth to both requests in the try-it-out and fetching the swagger specs ( from what I recall ). I think they should be separate.

Bonus:

Something not mentioned here but important, is having a literal representation of auths... so that they can be including the config file ( function aren't easily added to the config file, if even possible ).

@webron
Copy link
Contributor

webron commented Mar 29, 2017

Right, but that I still fail to see why we need to break interface. We can possibly add, but the first step would be to support the original behavior.

@ponelat
Copy link
Member

ponelat commented Mar 29, 2017

That's fine too.
This is about the next steps. The future of Auth for all mankind.

@dinvlad
Copy link

dinvlad commented Mar 31, 2017

Sorry for a tangential question, but can we also have support for Authorization token scheme for fetching the API? I know it's been addressed in the past per #1503, but I don't see if it's still supported in the new version. Per your comments from above it would seem it's not, is that right?

@webron
Copy link
Contributor

webron commented Mar 31, 2017

@dinvlad - that's the goal, yes.

@jahlborn
Copy link

jahlborn commented Apr 21, 2017

@webron Any indications on the priority of this issue? we are very interested on embedding swagger-ui into our project in the near term, but this is a blocker for us as all our swagger specs are protected by authentication.

@webron
Copy link
Contributor

webron commented Apr 21, 2017

The P2 is the priority. Not top at the moment. If it's urgent to you, please consider submitting a PR.

@martinpagesaal
Copy link

martinpagesaal commented May 31, 2017

I'm having kind of the same issue.
#3157

I also had this issue with the api endpoint when executing them.
In this case I made a simple workaround with api_key

/**
* @swagger
* securityDefinitions:
*   APIKeyHeader:
*     type: apiKey
*     in: header
*     name: Authorization
*/

And when asked for the key, I copy the Bearer key generated outside swagger.

Is there a way to workaround with something like this for the swagger.json getting?

@martinpagesaal
Copy link

martinpagesaal commented Jun 1, 2017

I made the most horrible workaround but it worked for me and thats good enough for me now.

Idea:

Override System Fetch function to add extra header when getting swagger.json
So I add an input that will call a function that gets System fetch from UI and overrides it with some extra code from me, this will add the Authorization code on the header, get swagger file and return everything to normal.

Code:

on index.html right after

<script>
  var orgFetch;
  window.setExtraHeader = function(bearerCode) {
    var system = window.ui.getSystem();
    if(!system) return;
    if(!orgFetch) {
      console.log('Getting original fetch function')
      orgFetch = system.fn.fetch;
    }

    system.fn.fetch = function(obj) {
      if(!obj) return;
      if(!obj.headers) {
        obj.headers = {};
      }
      obj.headers['Authorization'] = bearerCode
      console.log('Fetch Object:', obj);
      return orgFetch(obj)
        .then(function(fetchRes) {
          if(fetchRes && fetchRes.ok) {
            setTimeout(function() {
              document.getElementById('bearer-code-input').remove();
              system.fn.fetch = orgFetch;
            }, 100)
          }
          return fetchRes;
        })
    }
    system.specActions.download()
  }
</script>
<div>
  <input id="bearer-code-input" type="text" placeholder="Copy / Paste your Authorization code here!" onchange="window.setExtraHeader(this.value)" style="height: 2em;width: 100%;font-size: 1em;"/>
</div>

@madhushib
Copy link

Hi @martinpagesaal,

I am also trying to adapt your workaround, but it fails to reload the UI. Can you please tell me the specific location where you included the above code?
Is it after this script or somewhere else?

<script> window.onload = function() { // Build a system const ui = SwaggerUIBundle({ url: "http://localhost:8040/serverpages/lists/intent2.xqy?item=http%3A%2F%2Fstudent.unsw.edu.au%2FUNSW%2FTV%2FModel%23Pet_Find_by_Tag&format=swaggerJson&&", dom_id: '#swagger-ui', presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], plugins: [ SwaggerUIBundle.plugins.DownloadUrl ], layout: "StandaloneLayout" }) window.ui = ui } </script>

@martinpagesaal
Copy link

martinpagesaal commented Jun 8, 2017

Hi @madhushib

This is my full index.html

<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>

<body>
<script>
  var orgFetch;
  window.setExtraHeader = function(bearerCode) {
    var system = window.ui.getSystem();
    if(!system) return;
    if(!orgFetch) {
      console.log('Getting original fetch function')
      orgFetch = system.fn.fetch;
    }

    system.fn.fetch = function(obj) {
      if(!obj) return;
      if(!obj.headers) {
        obj.headers = {};
      }
      obj.headers['Authorization'] = bearerCode
      console.log('Fetch Object:', obj);
      return orgFetch(obj)
        .then(function(fetchRes) {
          if(fetchRes && fetchRes.ok) {
            setTimeout(function() {
              document.getElementById('bearer-code-input').remove();
              system.fn.fetch = orgFetch;
            }, 100)
          }
          return fetchRes;
        })
    }
    system.specActions.download()
  }
</script>
<div>
  <input id="bearer-code-input" type="text" placeholder="1. Goto Admin. 2. Query Admin user and get Code. 3. Paste it here and Enter 4. Explore" onchange="window.setExtraHeader(this.value)" style="height: 2em;width: 100%;font-size: 1em;"/>
</div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
  <defs>
    <symbol viewBox="0 0 20 20" id="unlocked">
          <path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
    </symbol>

    <symbol viewBox="0 0 20 20" id="locked">
      <path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
    </symbol>

    <symbol viewBox="0 0 20 20" id="close">
      <path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
    </symbol>

    <symbol viewBox="0 0 20 20" id="large-arrow">
      <path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
    </symbol>

    <symbol viewBox="0 0 20 20" id="large-arrow-down">
      <path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
    </symbol>


    <symbol viewBox="0 0 24 24" id="jump-to">
      <path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
    </symbol>

    <symbol viewBox="0 0 24 24" id="expand">
      <path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
    </symbol>

  </defs>
</svg>

<div id="swagger-ui"></div>

<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {

  // Build a system
  const ui = SwaggerUIBundle({
    url: "/swagger.json",
    dom_id: '#swagger-ui',
    presets: [
      SwaggerUIBundle.presets.apis,
      SwaggerUIStandalonePreset
    ],
    plugins: [
      SwaggerUIBundle.plugins.DownloadUrl
    ],
    layout: "StandaloneLayout"
  })

  window.ui = ui
}
</script>
</body>

</html>

and this is part of my app.js

var swaggerDefinition = {
    info: {
      title: 'Node Swagger API',
      version: '1.0.0',
      description: 'Demonstrating how to describe a RESTful API with Swagger',
    },
    host: appConfig.swagger.host,
    basePath: '',
  };

  // options for the swagger docs
  var options = {
    // import swaggerDefinitions
    swaggerDefinition: swaggerDefinition,
    // path to the API docs
    apis: ['./model/*.js', './conf/routes.js'],
  };

  // initialize swagger-jsdoc
  var swaggerSpec = swaggerJSDoc(options);

  app.get('/swagger.json', jwt(), function(req, res) {
    res.setHeader('Content-Type', 'application/json');
    res.send(swaggerSpec);
  });
  app.get('/api-docs/index.html', function(req, res) {
    res.setHeader('Content-Type', 'text/html');
    res.sendFile(path.join(__dirname, './api-docs', 'index.html'));
  });

  app.get(express.static(path.join(__dirname, 'public')));

I hope this helps you!

@freeunion1possible
Copy link

@martinpagesaal, i had been try your solution, it works well.
@madhushib , there have another solution for this issue. According the sample for jersey(https://github.com/swagger-api/swagger-samples/tree/master/java/java-jersey-jaxrs/src/main/java/io/swagger/sample), just need 2 steps.

  1. init the basic auth
    Swagger swagger = new Swagger();
    swagger.securityDefinition("Authorization", new BasicAuthDefinition());
    new SwaggerContextService().updateSwagger(swagger);
  2. add annoation for the rest service
    @ApiOperation(value = "", authorizations = @authorization(value = "Authorization"))

@TheCodeDestroyer
Copy link

TheCodeDestroyer commented Aug 21, 2017

Nope... ( I mean its possible, but ugly ).

@ponelat Can we maybe see the ugly code until this issue is resolved?
So far the only solution I can think of is XMLHttpRequest interceptor...

@ponelat
Copy link
Member

ponelat commented Aug 23, 2017

@TheCodeDestroyer
We do have requestInterceptors, they are for all requests ( try-it-out, $ref downloading, initial swagger downloading, etc ). And one could add a custom check for the url there ( if url == your swagger spec, add auth ).

An alternative is to change the download action, if its only the swagger spec you're concerned with.
Both can be done, and will try to get an example soon as I get a free moment.

@danilobuerger
Copy link

Setting configs.preFetch works for me:

const ui = SwaggerUIBundle({
  configs: {
    preFetch: function(req) {
      // Modify req, set auth
      return req
    }
  }
})

@scottohara
Copy link
Contributor

@danilobuerger Nice find, but judging by this comment (assuming I'm looking at the correct implementation) it sounds like configs.preFetch is being deprecated?

// Wrap a http function ( there are otherways to do this, conisder this deprecated )
export function makeHttp(httpFn, preFetch, postFetch) {
  postFetch = postFetch || (a => a)
  preFetch = preFetch || (a => a)

@danilobuerger
Copy link

Yeah, I read that too. My hope is that this story will be done before its removed.

@shockey
Copy link
Contributor

shockey commented Oct 2, 2017

I don't encourage the long-term use of a deprecated interface... but FWIW, we won't remove preFetch and postFetch before swagger-client@4.

@CarstenRilke
Copy link

@webron is there a rough time schedule for implementation?

We need access to protected API spec because our definitions are security-critical and thus not public.
Context: We would like to use openAPI for Confluence, but the definitions should be modular in a protected repostory.

@shockey
Copy link
Contributor

shockey commented Oct 9, 2017

@CarstenRilke, no one is working on this at the moment, as our resources are limited and our main focus is OAS3 support right now.

If you have the time, a pull request that fixes this would be appreciated. If you need this ASAP, please note that folks have mentioned workarounds to this issue upthread!

@webron
Copy link
Contributor

webron commented Oct 9, 2017

@CarstenRilke - also, it's not that it's not doable with the current code - make sure you read the thread fully.

@shockey
Copy link
Contributor

shockey commented Oct 10, 2017

It was pointed out to us that our request interceptors were not intercepting spec fetch. This has been fixed, and will be available this Friday when we release Swagger-UI again.

Official solution

Given user myUser and password myPassword, here's an example, using httpbin as an example of a page protected by Basic auth:

SwaggerUI({
  url: "https://httpbin.org/basic-auth/myUser/myPassword",
  requestInterceptor: (req) => {
    if(req.loadSpec) {
      var hash = btoa("myUser" + ":" + "myPassword") // base64
      req.headers.Authorization = "Basic " + hash
    }
    return req
  }
})

Basically, you define a requestInterceptor that attaches the basic auth information for you. Modify
myUser and myPassword to suit your needs.

If you have questions about this solution, please open a new ticket.

Thanks everyone!

@shockey shockey closed this as completed Oct 10, 2017
@scottohara
Copy link
Contributor

Thanks @shockey .

Note that setting a requestInterceptor will also affect Try-it-out requests, so for anyone who only wants the basic auth header applied to the fetch of the spec URL (after the next release on Friday with the fix), you could do this:

// The spec URL
const url = "https://www.example.com/authorised-users-only/spec.json";

SwaggerUI({
  url,  // spec url
  requestInterceptor: (req) => {

    // Only set Authorization header if the request matches the spec URL
    if (req.url === url) {
      req.headers.Authorization = "Basic " + btoa("myUser" + ":" + "myPassword");
    }

    return req;
  }
})

@shockey
Copy link
Contributor

shockey commented Oct 10, 2017

Thanks for pointing that out, @scottohara! You're correct, I overlooked that in my original example.

There's an internal flag that's set on the request, loadSpec. I've updated my example above to use it, and I'm going to add some tests to ensure that flag stays in the DownloadUrl plugin.

@webron
Copy link
Contributor

webron commented Aug 23, 2018

@brianupskill as the ticket is closed, the request is pretty much lost (hard to track). Please file a new ticket.

@HaleyWang
Copy link

HaleyWang commented Mar 13, 2019

ui.getConfigs().requestInterceptor = (req) => { req.headers.hahaha= "123"; return req; }
It worked for me

@shockey
Copy link
Contributor

shockey commented Mar 13, 2019

@HaleyWang please don't change things on the object you get back from ui.getConfigs(), it's not an official interface and is likely to break in the future 😄

@tmkasun
Copy link
Contributor

tmkasun commented May 30, 2019

I'm too doing the same things as @HaleyWang , Because I want to change the access token value dynamically.

@shockey , How can I change the Authorization header dynamically after initializing the swagger UI ?
I tried the ui.preauthorizeApiKey("MyJWTAuth", "Bearer <your token value here>") as per this comment #2915 (comment) , But it didn't work.

I'm using Swagger/OpenAPI 2.0 , and swagger-ui version 3.22.2.

@dinvlad
Copy link

dinvlad commented May 30, 2019

@tmkasum and everyone else: I use code like

const requestInterceptor = async req => {
  req.headers['Authorization'] = 'Bearer ' + await getIdToken();
  return req;
};

SwaggerUI({
  url,
  dom_id: '#swagger-ui',
  requestInterceptor,
  showMutatedRequest: true,
});

@sacwe
Copy link

sacwe commented Jan 27, 2020

In swagger-ui 3.2x, to manually set Authorization header based on values entered in the authorize popup for basic authentication, we can use below code.

const ui = SwaggerUIBundle({
        dom_id: '#swagger-ui',
        requestInterceptor: (req) => {
        if (!req.loadSpec) {
            var authorized = this.ui.authSelectors.authorized();
            //'Basic_Authentication' is security scheme key for basic authentication in the OpenApi file
            var basicAuth = getEntry(authorized, 'Basic_Authentication');
            if (basicAuth) {
                var basicAuthValue = getEntry(basicAuth, 'value');
                if (basicAuthValue) {
                    var username = getEntry(basicAuthValue, 'username');
                    var password = getEntry(basicAuthValue, 'password');
                    if (username && password) {
                        req.headers.Authorization = "Basic " + btoa(username + ":" + password); 
                    }
                }
            }
        }
        return req;
    }

//traverse through the object structure of swagger-ui authorized object to get value for an entryName
    function getEntry(complexObj, entryName) {
        if (complexObj && complexObj._root && complexObj._root.entries) {
            var objEntries = complexObj._root.entries;
            for (var t = 0; t < objEntries.length; t++) {
                var entryArray = objEntries[t];
                if (entryArray.length > 1) {
                    var name = entryArray[0];
                    if (name === entryName) {
                        return entryArray[1];
                    }
                }
            }
        }

        return null;
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

17 participants