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 securityDefinitions to openapi/swagger spec #1082

Closed
omani opened this issue Mar 8, 2018 · 21 comments · Fixed by #2363
Closed

Add securityDefinitions to openapi/swagger spec #1082

omani opened this issue Mar 8, 2018 · 21 comments · Fixed by #2363
Assignees
Labels

Comments

@omani
Copy link

omani commented Mar 8, 2018

Hi,

it would be nice if we could include a securityDefinitions field to the swagger.json. By doing this we can have a custom header field in swagger-ui where you can paste your jwt.

right now I can't set an Authorization Bearer token with each request. but swagger-ui would show a button if some securityDefinitions is included in the openapi/swagger json spec I get from the root path.

or is there already any other way to do this?

@omani
Copy link
Author

omani commented Mar 9, 2018

I've figured out this is enough in the swagger.json to have an "Authorize" button:

"securityDefinitions": {
		"JWT": {
			"type": "apiKey",
			"in": "query",
			"name": "access_token"
		}
	},
	"security": [
		{
			"JWT": []
		}
	],
	"responses": {
		"UnauthorizedError": {
			"description": "Access token is missing or invalid"
		}
	},

notice that name is a name I've chosen. in this case swagger does append a query parameter ?access_token=... to the request everytime I make a request.

@rvernica
Copy link

rvernica commented Apr 2, 2018

Could you say more about what you did? Where is the swagger.json file located? Do you get an Authorization Bearer token from Swagger UI now?

@omani
Copy link
Author

omani commented Apr 2, 2018

you get the swagger.json from the root path of your backend (postgrest).

I wrote a script to fetch my swagger.json from the backend and append the above mentioned part to it.

#!/bin/bash

HOST=$1

curl -sX GET "${HOST}/?access_token=eyJhbGciOiJIUzI1NiIsInR5..." | python -mjson.tool > /tmp/pretty_swagger.json

sed -i '$ d' /tmp/pretty_swagger.json
sed -i '$ d' /tmp/pretty_swagger.json

cat >> /tmp/pretty_swagger.json <<INSERT
	"swagger": "2.0",
	"securityDefinitions": {
		"JWT": {
			"type": "apiKey",
			"in": "query",
			"name": "access_token"
		}
	},
	"security": [
		{
			"JWT": []
		}
	],
	"responses": {
		"UnauthorizedError": {
			"description": "Access token is missing or invalid"
		}
	}
}
INSERT

cp /tmp/pretty_swagger.json /somewhere/you/want/swagger.json

@omani
Copy link
Author

omani commented Apr 2, 2018

oh and I start a simplehttpserver with python to serve the swagger json from where I start the server (that directory). you have to enable CORS for swagger to work (fetch this file):

$ cat cors_enabled_simplehttpserver.py 
#!/usr/bin/env python2
from SimpleHTTPServer import SimpleHTTPRequestHandler
import BaseHTTPServer

class CORSRequestHandler (SimpleHTTPRequestHandler):
    def end_headers (self):
        self.send_header('Access-Control-Allow-Origin', '*')
        SimpleHTTPRequestHandler.end_headers(self)

if __name__ == '__main__':
    BaseHTTPServer.test(CORSRequestHandler, BaseHTTPServer.HTTPServer)
$ 

then you can run swagger (docker) and pass it the API_URL:

API_URL=http://localhost:8000/swagger.json sudo docker run -it -e API_URL=$API_URL -p 9000:8080 swaggerapi/swagger-ui

then hit localhost:9000 and you should see swaggerUI with an authorize button.

@rvernica
Copy link

rvernica commented Apr 2, 2018

Thanks!

It would be nice for postgrest to have a flag and include this extra JSON content in the file it produces.

@omani
Copy link
Author

omani commented Apr 2, 2018

yes either a flag or I say let's just put it in there anyway (hardcode it). it depends on the user if he wants to use authorization. the above snippet is enough to have it working. if you have endpoints which do not require an access token it will just be discarded anyway (because we are sending it as a query here).

@respondcreate
Copy link

respondcreate commented May 11, 2018

This would be an ideal addition to PostgREST so great suggestion @omani! The below def for securityDefinitions would enable in-header JWT authentication described here in the docs to be done via a connected swagger interface:

securityDefinitions: {
    "Bearer": {
       " name": "Authorization",
        "in": "header",
        "type": "apiKey"
    }
}

Can anyone with knowledge of this codebase and/or Haskell chime in on the effort involved in to getting this implemented?

@bwbroersma
Copy link

For a project I decided to 'fix/patch' some OpenAPI output with ngx_http_sub_module:
https://github.com/openstate/allmanak/blob/7270a300b855d5e7797c83f0ab352b8f665f6b4c/nginx/default.conf#L53-L56 (explicitly include the sub_filter logic only for the OpenAPI endpoint)
https://github.com/openstate/allmanak/blob/7270a300b855d5e7797c83f0ab352b8f665f6b4c/nginx/shared/v1-openapi-filter.conf (patch stuff like documentation links and remove all non-GET stuff from the API, autogenerated by a curl | jq one-liner)
https://github.com/openstate/allmanak/blob/7270a300b855d5e7797c83f0ab352b8f665f6b4c/nginx/shared/v1-generated-strip-openapi-edits.conf (ugly unreadable auto generated file)
https://github.com/openstate/allmanak/blob/7270a300b855d5e7797c83f0ab352b8f665f6b4c/nginx/shared/http.conf#L3-L5 (a nginx $-trick which is needed)

You can also add the securityDefinitions that way, see:
https://github.com/openstate/allmanak/blob/22f843b3663729dd0316fa0dc705d684375b1530/nginx/shared/openapi-rewrites.conf#L3-L4

@royrusso
Copy link

royrusso commented Oct 18, 2019

This would be really handy. Perhaps PostGRest can see that PGRST_JWT_SECRET is set and add the necessary fields to the swagger json?

@hemantham0002
Copy link

@omani Your code worked for me man!!

@wscherphof
Copy link
Contributor

wscherphof commented Nov 7, 2019

We use the swaggerapi/swagger-ui Docker image, and configure a responseInterceptor and a requestInterceptor to get the Authorization going.

Dockerfile:

FROM swaggerapi/swagger-ui

COPY conf/interceptors.js /usr/share/nginx/configurator/interceptors.js

RUN configurator=/usr/share/nginx/configurator; \
    index="${configurator}/index.js"; \
    interceptors="${configurator}/interceptors.js"; \
    search='SwaggerUIBundle({'; \
    replace="${search} $(cat ${interceptors} | tr -d '\n')"; \
    sed -i "s|${search}|${replace}|g" "${index}"

conf/interceptors.js:

responseInterceptor: response => {
 \n
 const obj = response.obj;
 if (obj \&\& obj.swagger) {
  obj.securityDefinitions = {
   AuthorizationHeader:
    { name: "Authorization"
    , in:   "header"
    , type: "apiKey"
    , description: 'Submit value "Bearer $token", then execute the Introspection request, then click Explore button (top right).'
    }
  };
  obj.security = [{ AuthorizationHeader: [] }];
  response.text = JSON.stringify(obj);
  response.data = response.text;
 }
 \n
 return response;
},
requestInterceptor: request => {
 \n
 window.AuthorizationHeader = window.AuthorizationHeader \|\| request.headers.Authorization;
 request.headers.Authorization = request.headers.Authorization \|\| window.AuthorizationHeader;
 \n
 return request;
},

Or even without the Authorize button, actually executing a login request to log in, and saving the token in localStorage:

conf/interceptors.js:

responseInterceptor: response => {\n
 if (response.url.endsWith('login') \&\& response.ok) {\n
  const obj = response.obj[0] ? response.obj[0] : response.obj;\n
  localStorage.setItem('Authorization', 'Bearer ' + obj.token);\n
  location.reload();\n
 }
 if (response.status === 401 \|\| response.url.endsWith('logout')) {\n
  localStorage.removeItem('Authorization');\n
  location.reload();\n
 }\n
 return response;\n
},\n
requestInterceptor: request => {\n
 request.headers.Authorization = localStorage.getItem('Authorization');\n
 if (request.headers.Authorization === null) {\n
  delete request.headers.Authorization;\n
 }\n
 return request;\n
},

@jshrake
Copy link

jshrake commented Jun 24, 2020

I ended up with a slightly different solution to this problem. I wrote a server (an express app written in typescript) that sits between postgrest and swagger-ui and modifies the JSON returned by postgrest directly in the request:

import cors from 'cors';
import express from 'express';
import fetch from 'node-fetch';

// CLI Usage
// API_URL=http://your_postgrest_url_and_port node ./dist/index.js

const port = process.env.PORT || 8080;
const apiUrl = process.env.API_URL!;
const authJson = {
  'securityDefinitions':
      {'jwt': {'type': 'apiKey', 'in': 'header', 'name': 'Authorization'}},
  'security': [{'jwt': []}]
};
const app = express();
app.use(cors());
app.get('/', function(req: express.Request, res: express.Response) {
  fetch(apiUrl)
      .then((link) => link.json())
      .then((swaggerJson) => res.json({...swaggerJson, ...authJson}));
});
app.listen(port, function() {
  console.log(`http://localhost:${port} exposing API at ${apiUrl}`);
});

swagger-ui should now render a green "Authorize" button on the upper right part the page. When you specify the jwt value, make sure to explicitly prepend the string literal "Bearer " to the token (see OAI/OpenAPI-Specification#583).

Screen Shot 2020-06-23 at 8 04 58 PM

@bwbroersma
Copy link

BTW since nobody mentioned since version 6.0 there is:

#1317, Allow override of OpenAPI spec through root-spec config option

How to use:

A while ago I've noticed we need to refactor the codebase and replace some parts in Haskell with SQL so we can offer a true mirror of our schema cache in the "base spec". This will require some work but it's doable.

For now, for anyone that would like to help us constructing the OpenAPI function, the root-spec config is already available(added in #1317) and you can use the base spec, later we can update it with up-to-date schema cache queries.

Also you can use https://github.com/gavinwahl/postgres-json-schema for testing.

Originally posted by @steve-chavez in #790 (comment)

It's not yet in the documentation because:

I think I'll not include the root-spec(#1317) config in the docs yet since we don't have a default base spec to offer. Once we have it I'll include it.

Originally posted by @steve-chavez in PostgREST/postgrest-docs#239 (comment)

@johnnylambada
Copy link

@bwbroersma Can you elaborate on your comment? I'm not sure how that helps inject the required securityDefinitions and security strings into the JSON as described by previous posters. Thanks!

@johnnylambada
Copy link

johnnylambada commented Nov 26, 2020

Standing on the shoulders of the great advise in this thread, I made an nginx proxy for the swagger json endpoint that inserts the code necessary for JWT to work in swagger. Check out the nginx config file. You can also pull the entire repository and run it on your own in docker.

@c-p-b
Copy link

c-p-b commented Apr 7, 2022

For my use-case, I happened to be running postgrest on supabase in kubernetes and was using nginx-ingress controller already to route into my swagger and postgrest internally. Since my ingress was already nginx running as a proxy pass, we only have to modify our existing ingress controller to insert the securityDefinitions. Here is what my ingress config looks like after making the changes @johnnylambada linked:

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: supabase-ingress-rest
  namespace: postgres # note! must be located in the same namespace as where service resides
  annotations: 
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    # enable supplying jwt supabase api key to postgrest
    nginx.ingress.kubernetes.io/configuration-snippet: |
      sub_filter '"externalDocs"' '"securityDefinitions":{"JWT":{"type":"apiKey","in":"header","name":"Authorization"}, "supabaseApiKey": {"type":"apiKey","in":"header","name":"apiKey"}},"security":[{"JWT":[], "supabaseApiKey":[]}],"responses":{"UnauthorizedError":{"description":"Access token is missing or invalid"}},"externalDocs"';
      sub_filter_types application/openapi+json;
      sub_filter_once off;
spec:
  rules:
  - host: supabase.internalurl # the domain you want associated
    http:
      paths:
      - path: /rest/v1(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: supabase-supabase-rest # existing service
            port:
              number: 3000  # existing service port
  ingressClassName: nginx
---

I am not sure how you would do it if you were using some other non-nginx based ingress, but you would instead probably use whatever equivalent does the same thing as these sub_filter configurations.

Note one minor annoyance. For the JWT token with this setup you have to prepend Bearer in the field e.g. paste "Bearer my-token-here" with a space in between 'Bearer' and your token in the authorize field. I am not sure if there is a way to have that done for you already. I believe OpenAPI 3 fixes this issue or maybe there is a way to do it in OpenAPI 2

@tvogt
Copy link

tvogt commented Jul 8, 2022

Still puzzled that there seems no easy way to just turn this on with a config option in docker compose or something. I'm using the docker images for postgrest and swagger-ui and don't see why I would set up yet another server to intercept or proxy something. I just want to turn on the authenticate button, which should be a simple config option or environment variable.

@laurenceisla
Copy link
Member

laurenceisla commented Jul 14, 2022

@tvogt The "Authorize" button in Swagger should be available now after the latest update. To activate it, you'll need to add openapi-security-active=true to the configuration file. Here's the related pre-release: https://github.com/PostgREST/postgrest/releases/tag/v9.0.1.20220714

@tvogt
Copy link

tvogt commented Jul 17, 2022

@laurenceisla fantastic! What's the parameter for the docker image? Or is that built by other people?

@laurenceisla
Copy link
Member

@laurenceisla fantastic! What's the parameter for the docker image? Or is that built by other people?

@tvogt Use the env variable PGRST_OPENAPI_SECURITY_ACTIVE="true".

@yevon
Copy link
Contributor

yevon commented Aug 8, 2022

@laurenceisla fantastic! What's the parameter for the docker image? Or is that built by other people?

@tvogt Use the env variable PGRST_OPENAPI_SECURITY_ACTIVE="true".

I was trying to add this version to try the new swagger authorization button, but I'm getting errors with any version superior to the stable 9.0.1:

image

Any ideas what could it be? If I return to 9.0.1 docker image all works fine again.

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

Successfully merging a pull request may close this issue.