Skip to content

allow for swagger oauth redirect #89

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/openapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,37 @@ Documentation components can be passed by accessing the internal apispec
{'description': 'Item ID', 'format': 'int32', 'required': True}
)

Use Swagger OAuth2 Authentication
---------------------------------

Swagger can automatically redirect to OAuth2 URLs and retrieve a token for use as an Authentication Header. See the `Swagger OAuth2 Docs`_ for more information. Your redirect to supply to your OAuth provider will be ``{HOST}://{OPENAPI_URL_PREFIX}/{OPENAPI_SWAGGER_UI_PATH}/oauth2-redirect``. The code below is an example of what should be supplied as config to app in order to activate this feature.

.. code-block:: python

app.config['API_SPEC_OPTIONS'] = {'components':
{
'securitySchemes': {
},
'OAuth2': {
'type': 'oauth2',
'flows': {
'implicit': {
'authorizationUrl': self.AUTHORIZATION_URL,
'scopes': {
'openid': 'openid token'
}
}
}
}
}
},
'security': [{
'OAuth2': []
}]
}
app.config['OPENAPI_SWAGGER_UI_ENABLE_OAUTH'] = True


Register Custom Fields
----------------------

Expand Down Expand Up @@ -276,3 +307,4 @@ number.

.. _ReDoc: https://github.com/Rebilly/ReDoc
.. _Swagger UI: https://swagger.io/tools/swagger-ui/
.. _Swagger OAuth2 Docs: https://swagger.io/docs/specification/authentication/oauth2/
18 changes: 18 additions & 0 deletions flask_rest_api/spec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ def _register_swagger_ui_rule(self, blueprint):
_add_leading_slash(swagger_ui_path),
endpoint='openapi_swagger_ui',
view_func=self._openapi_swagger_ui)
if (self._app.config.get(
'OPENAPI_SWAGGER_UI_ENABLE_OAUTH', False)):
blueprint.add_url_rule(
_add_leading_slash(
'{}/oauth2-redirect'.format(
swagger_ui_path
)
),
endpoint='openapi_swagger_ui_redirect',
view_func=self._openapi_swagger_ui_redirect)

def _openapi_json(self):
"""Serve JSON spec file"""
Expand All @@ -142,10 +152,18 @@ def _openapi_swagger_ui(self):
return flask.render_template(
'swagger_ui.html', title=self._app.name,
swagger_ui_url=self._swagger_ui_url,
swagger_ui_enable_oauth=self._app.config.get(
'OPENAPI_SWAGGER_UI_ENABLE_OAUTH', False),
swagger_ui_supported_submit_methods=(
self._swagger_ui_supported_submit_methods)
)

def _openapi_swagger_ui_redirect(self):
"""Expose OpenAPI redirect url with Swagger UI"""
return flask.render_template(
'swagger_ui_redirect.html'
)


class APISpecMixin(DocBlueprintMixin):
"""Add APISpec related features to Api class"""
Expand Down
3 changes: 3 additions & 0 deletions flask_rest_api/spec/templates/swagger_ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<script>
window.onload = function() {
const ui = SwaggerUIBundle({
{% if swagger_ui_enable_oauth == True %}
oauth2RedirectUrl: "{{ url_for('api-docs.openapi_swagger_ui_redirect',_external=True) }}",
{% endif %}
url: "{{ url_for('api-docs.openapi_json') }}",
dom_id: '#swagger-ui-container',
deepLinking: true,
Expand Down
67 changes: 67 additions & 0 deletions flask_rest_api/spec/templates/swagger_ui_redirect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;

if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}

arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}

isValid = qp.state === sentState

if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}

if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}

oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>