Postgrest: Add securityDefinitions to openapi/swagger spec

Created on 8 Mar 2018  路  15Comments  路  Source: PostgREST/postgrest

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?

OpenAPI

Most helpful comment

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?

All 15 comments

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.

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?

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

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.

Thanks!

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

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).

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?

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

@omani Your code worked for me man!!

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
},

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 https://github.com/OAI/OpenAPI-Specification/issues/583).

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

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 https://github.com/PostgREST/postgrest/issues/790#issuecomment-519963068_

It's not yet in the documentation because:

I think I'll not include the root-spec(https://github.com/PostgREST/postgrest/pull/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 https://github.com/PostgREST/postgrest-docs/pull/239#issuecomment-522268972_

@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!

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ruslantalpa picture ruslantalpa  路  25Comments

opensrcery picture opensrcery  路  39Comments

posix4e picture posix4e  路  34Comments

dudleycarr picture dudleycarr  路  25Comments

ric2b picture ric2b  路  21Comments