Closed omani closed 2 years ago
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?
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
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).
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.
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
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.
@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
@laurenceisla fantastic! What's the parameter for the docker image? Or is that built by other people?
@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"
.
@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:
Any ideas what could it be? If I return to 9.0.1 docker image all works fine again.
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?