swagger-js icon indicating copy to clipboard operation
swagger-js copied to clipboard

Cookie parameters and authorization

Open shockey opened this issue 7 years ago • 16 comments

Currently, cookie parameters and authorizations fail to be applied in the browser, though it succeeds in Node. This is due to the fact that browsers bar applications from setting or mutating the Cookie request header arbitrarily(citation needed), while Node doesn't particularly care what you do with the header.

https://github.com/swagger-api/swagger-js/blob/a864bebb3b41ea8e64e707626377bf5db081901a/src/execute/oas3/parameter-builders.js#L118

Here's some solutions that I came up with for Client/UI/Editor.

Possible solutions

  • Use document.cookie to set the page's cookie content, send those cookies to another origin with fetch({ withCredentials: 'include' }), then put the original cookies back.

This approach would work, but it's quite hacky, and could cause problems for complex applications that use our library. It would not work in IE or Safari, since they don't support withCredentials, which is bad.

  • Add an optional request proxy server option, and provide a server implementation that forges cookies for Swagger-Client.

This would only be needed when a user wants to use cookie parameters, but would require the user to maintain a server instance in order for their requests to work. (Or we maintain one.)

  • Provide Swagger-UI and Swagger-Editor variants packaged within Electron instead of the browser, which bypass web security restrictions and allow arbitrary cookie values.

This is how Postman works.

  • Provide Swagger request helper browser extensions that are capable of bypassing security policies.

This could be relatively straightforward: expose a Swagger-Client interface through an extension, and then call that interface instead of the Swagger-Client that comes with distributions of Swagger-UI/Swagger-Editor.

  • Label as wontfix for browsers.

shockey avatar Oct 21 '17 00:10 shockey

Any update here?

vanta avatar Jun 28 '18 17:06 vanta

If the API doc and the API endpoint are on the same origin, setting cookies with document.cookie should work. Is it good to support sending cookies in the case that the doc and the endpoint are on the same origin?

IvanaGyro avatar Apr 09 '20 18:04 IvanaGyro

@shockey, is there any progress on this issue? This is a very valuable use case for our application - we're exposing Swagger API documentation on a developer portal, and would like to allow the users to test out the APIs. This requires authorization and has been rendered useless by the issue you've described... :(

denis-kosovich avatar Apr 08 '21 06:04 denis-kosovich

Same here. I had to add the "header auth" to get the "local login" done 🥲, even though it's not a very good practice.

haixiangyan avatar Jul 31 '21 01:07 haixiangyan

amazing how this issue is still alive, my swagger doesnt send header Cookie to backend

darklight147 avatar Oct 04 '21 15:10 darklight147

I faced the same problem. I need to pass header Cookie to backed ...

develop-dvs avatar Oct 21 '21 12:10 develop-dvs

As an alternative solution setting withCredentials: true worked perfectly for my use case.

https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/#withCredentials

withCredentials -> Boolean=false If set to true, enables passing credentials, as defined in the Fetch standard, in CORS requests that are sent by the browser. Note that Swagger UI cannot currently set cookies cross-domain (see this issue) - as a result, you will have to rely on browser-supplied cookies (which this setting enables sending) that Swagger UI cannot control.

So each time I make a request to /app/auth/login or /app/auth/register a http-only same-site cookie is stored by the browser and then it will be appended to each following request (so this approach avoids setting the authorization Swagger UI field)

alereca avatar Feb 23 '22 23:02 alereca

Will anything be done to fix this?

KarolBorkowski avatar Nov 02 '22 10:11 KarolBorkowski

Hi everybody,

IMHO this is not something that is fixable. From security reasons it is just not technically possible to set the Cookie header. But if you face this issue, swagger-js and SwaggerUI are able to work around it to certain level.

swagger-js

I'll start with swagger-js examples.

Basic example using low-level swagger-js http client

Given that <request-url> requires auth-cookie cookie to authenticate, we can use credentials to tell the http client (fetch under the hood), to read the cookies from the browser. The browser cookie must be in following form:

auth-cookie=<token>; SameSite=None; Secure

SameSite and Secure flags must be send so that following request will relay the cookie during the request.

const response = await SwaggerClient.http({
    url: <request-url>,
    credentials: 'include',
});
Basic example of swagger-js HTTP client for OAS operations

Given that https://httpbin.org/get requires auth-cookie cookie to authenticate, we can use credentials to tell the OAS HTTP client (fetch under the hood), to read the cookies from the browser. The browser cookie must be in following form:

auth-cookie=<token>; SameSite=None; Secure

SameSite and Secure flags must be send so that following request will relay the cookie during the request.

const pojoDefinition = {
  "openapi": "3.0.0",
  "info": {
    "title": "Testing API",
    "version": "1.0.0"
  },
  "components": {
    "schemas": {
      "user": {
        "properties": {
          "id": {
            "type": "integer"
          }
        }
      }
    },
    "securitySchemes": {
      "BasicAuth": {
        "type": "http",
        "scheme": "basic"
      },
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-KEY"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer"
      },
      "oAuth2": {
        "type": "oauth2",
        "flows": {
          "implicit": {
            "authorizationUrl": "https://api.example.com/oauth2/authorize",
            "scopes": {
              "read": "authorize to read"
            }
          }
        }
      }
    }
  },
  "servers": [
    {
      "url": "https://httpbin.org"
    }
  ],
  "paths": {
    "/get": {
      "get": {
        "operationId": "getUserList",
        "description": "Get list of users",
        "security": [
          {
            "BasicAuth": [],
            "BearerAuth": [],
            "ApiKey": [],
            "oAuth2": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "search query parameter",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "style": "pipeDelimited",
            "explode": false
          }
        ],
        "responses": {
          "200": {
            "description": "List of users",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/user"
                }
              }
            }
          }
        }
      }
    }
  }
};

SwaggerClient.http.withCredentials = true;
const response = await SwaggerClient.execute({
  spec: pojoDefinition,
  operationId: 'getUserList',
  parameters: { q: 'search string' },
});
Basic example of SwaggerUI + withCredentials configuration option

Given that every Try it out url requires auth-cookie cookie to authenticate, we can use credentials to tell the OAS HTTP client (fetch under the hood), to read the cookies from the browser. The browser cookie must be in following form:

auth-cookie=<token>; SameSite=None; Secure

SameSite and Secure flags must be send so that following request will relay the cookie during the request.

  window.ui = SwaggerUIBundle({
    url: "https://petstore.swagger.io/v2/swagger.json",
    dom_id: '#swagger-ui',
    deepLinking: true,
    presets: [
      SwaggerUIBundle.presets.apis,
      SwaggerUIStandalonePreset
    ],
    plugins: [
      SwaggerUIBundle.plugins.DownloadUrl
    ],
    layout: "StandaloneLayout",
    withCredentials: true,
  });
Basic example of SwaggerUI + requestInterceptor configuration option

Given that every Try it out url requires auth-cookie cookie to authenticate, we can use credentials to tell the OAS HTTP client (fetch under the hood), to read the cookies from the browser. The browser cookie must be in following form:

auth-cookie=<token>; SameSite=None; Secure

SameSite and Secure flags must be send so that following request will relay the cookie during the request.

  window.ui = SwaggerUIBundle({
    url: "https://petstore.swagger.io/v2/swagger.json",
    dom_id: '#swagger-ui',
    deepLinking: true,
    presets: [
      SwaggerUIBundle.presets.apis,
      SwaggerUIStandalonePreset
    ],
    plugins: [
      SwaggerUIBundle.plugins.DownloadUrl
    ],
    layout: "StandaloneLayout",
    requestInterceptor: (request) => {
      if (request.url matches pattern) {
        request.credentials = 'include';
      } else {
        request.credentials = 'omit';
      } 

      return request;
    }
  });

Using requestInterceptor allows full control over how requests are made.


Now to the most important question - how do I actually create the auth-cookie=<token>; SameSite=None; Secure cookie?

There are two options - we can use - both require server to set the cookie.

  1. Using SwaggerUI authorization mechanism

When SwaggerUI authorization is performed make your authorization server send the Cookie response header which will set the auth-cookie (with SameSite=None + Secure) flags for the SwaggerUI.

  1. Make a HTTP request to you API before SwaggerUI initialization
  // this will provide server send cookie that SwaggerUI will authomatically
  // relay to subsequent `Try it out` calls.
  // Of course this cookie will be some kind of a generic cookie
  // that will not be specific the the user.
  await fetch('https://my-api/provide/cookie, { credentials: 'include' });

  window.ui = SwaggerUIBundle({
    url: "https://petstore.swagger.io/v2/swagger.json",
    dom_id: '#swagger-ui',
    deepLinking: true,
    presets: [
      SwaggerUIBundle.presets.apis,
      SwaggerUIStandalonePreset
    ],
    plugins: [
      SwaggerUIBundle.plugins.DownloadUrl
    ],
    layout: "StandaloneLayout",
    withCredentials: true,
  });

NOTE1: If you have setup where you use SwaggerUI Authorization mechanism and this authorization returns token that you need to send back to subsequent requests as cookie, there's not much you can do in browser. If the server requires cookie authorization it should also properly send response cookie header. Unfortunately that is not always the case.

NOTE2: as already mentioned document.cookie can be used when SwaggerUI and API lives on the same domain: document.cookie="auth-cookie=<token>; SameSite=None; Secure". Note that this will override all cookies on the domain - you'd better use https://github.com/js-cookie/js-cookie to manipulate a single cookie.

NOTE3: as already mentioned, if SwaggerUI and API don't live on the same domain, the best option (given that extension doesn't exist) would be to setup a HTTP proxy server that will relay HTTP requests to actual API and for example translate the X-HTTP-COOKIE header to a real Cookie header. SwaggerUI requestInterceptor can be then used to intercept requests and rewrite the request.url to a proxy server URL.

Hope this helps a bit with the issue.

char0n avatar Nov 07 '22 16:11 char0n

Do you happen to have any updates regarding this issue? Cookie couldn't be sent from Swagger UI to the server

ibrahim-ajarmeh avatar Feb 28 '23 12:02 ibrahim-ajarmeh

Any progress ? 🧐

plefebvre91 avatar May 10 '23 13:05 plefebvre91

Why cant document.cookie be used by swagger?

dreamfalcon avatar May 18 '23 08:05 dreamfalcon

Hi @ibrahim-ajarmeh, @plefebvre91

Did you manage to look at https://github.com/swagger-api/swagger-js/issues/1163#issuecomment-1305901395?

char0n avatar May 18 '23 09:05 char0n

Why cant document.cookie be used by swagger?

@dreamfalcon can you elaborate in detail how that would work? I've already described how to use document.cookie and custom Cookie header for the same origin requests:

NOTE2: as already mentioned document.cookie can be used when SwaggerUI and API lives on the same domain: document.cookie="auth-cookie=; SameSite=None; Secure"

  window.ui = SwaggerUIBundle({
    url: "https://petstore.swagger.io/v2/swagger.json",
    dom_id: '#swagger-ui',
    deepLinking: true,
    presets: [
      SwaggerUIBundle.presets.apis,
      SwaggerUIStandalonePreset
    ],
    plugins: [
      SwaggerUIBundle.plugins.DownloadUrl
    ],
    layout: "StandaloneLayout",
    withCredentials: true,
  });

This allows to create cookie from JavaScript using document.cookie API and use withCredentials: true configuration option to send that cookie with sub-subsequent requests that swagger-client will make.


Now using the above mentioned solution doesn't work for different origin requests. As mentioned multiple times before, because of browser security limitations you cannot create a cookie using document.cookie API for different domain.

MDN: https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#write_a_new_cookie

image

Cookie must be created by the server in the browser in order for SwaggerUI to resend the cookie when withCredentials: true is set. This is already described in https://github.com/swagger-api/swagger-js/issues/1163#issuecomment-1305901395

image

char0n avatar May 18 '23 09:05 char0n

Why cant document.cookie be used by swagger?

@dreamfalcon can you elaborate in detail how that would work? I've already described how to use document.cookie and custom Cookie header for the same origin requests:

NOTE2: as already mentioned document.cookie can be used when SwaggerUI and API lives on the same domain: document.cookie="auth-cookie=; SameSite=None; Secure"

  window.ui = SwaggerUIBundle({
    url: "https://petstore.swagger.io/v2/swagger.json",
    dom_id: '#swagger-ui',
    deepLinking: true,
    presets: [
      SwaggerUIBundle.presets.apis,
      SwaggerUIStandalonePreset
    ],
    plugins: [
      SwaggerUIBundle.plugins.DownloadUrl
    ],
    layout: "StandaloneLayout",
    withCredentials: true,
  });

This allows to create cookie from JavaScript using document.cookie API and use withCredentials: true configuration option to send that cookie with sub-subsequent requests that swagger-client will make.

Now using the above mentioned solution doesn't work for different origin requests. As mentioned multiple times before, because of browser security limitations you cannot create a cookie using document.cookie API for different domain.

MDN: https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#write_a_new_cookie

image

Cookie must be created by the server in the browser in order for SwaggerUI to resend the cookie when withCredentials: true is set. This is already described in #1163 (comment)

image

If I manually set document.cookie it works. But why dosent swagger does this when clicking on the Authorize button?

image

dreamfalcon avatar May 18 '23 14:05 dreamfalcon

If I manually set document.cookie it works. But why dosent swagger does this when clicking on the Authorize button?

@dreamfalcon all right, so we're talking about SwaggerUI specifically here, not swagger-client (this repo). Yes, in that case we can do what you suggest. We could modify document.cookies when authorizing via SwaggerUI. That would make the situation for cookie authorization on same origin better. Would you mind creating an issue on SwaggerUI repo? You can also issue PR against SwaggerUI adding the functionality and ping me personally for a quick review.

char0n avatar May 19 '23 08:05 char0n