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

Cookie parameters and authorization #1163

Open
shockey opened this issue Oct 21, 2017 · 16 comments
Open

Cookie parameters and authorization #1163

shockey opened this issue Oct 21, 2017 · 16 comments

Comments

@shockey
Copy link
Contributor

shockey commented Oct 21, 2017

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.

req.headers.Cookie = prefix + stylize({

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.
@vanta

This comment has been minimized.

@IvanaGyro
Copy link

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?

@denis-kosovich
Copy link

@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... :(

@haixiangyan
Copy link

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

@darklight147
Copy link

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

@develop-dvs
Copy link

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

@alereca
Copy link

alereca commented Feb 23, 2022

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)

@KarolBorkowski
Copy link

Will anything be done to fix this?

@char0n
Copy link
Member

char0n commented Nov 7, 2022

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.

@ibrahim-ajarmeh
Copy link

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

@plefebvre91
Copy link

Any progress ? 🧐

@dreamfalcon
Copy link

Why cant document.cookie be used by swagger?

@char0n
Copy link
Member

char0n commented May 18, 2023

Hi @ibrahim-ajarmeh, @plefebvre91

Did you manage to look at #1163 (comment)?

@char0n char0n self-assigned this May 18, 2023
@char0n
Copy link
Member

char0n commented May 18, 2023

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

@dreamfalcon
Copy link

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

@char0n
Copy link
Member

char0n commented May 19, 2023

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.

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