diff --git a/README.md b/README.md
index cb3937d..dbc509a 100644
--- a/README.md
+++ b/README.md
@@ -63,9 +63,22 @@ By using this library, it will automatically renew signed-in session when the ID
* [Sample written in ![Django](https://raw.githubusercontent.com/rayluo/identity/dev/docs/django.webp)](https://github.com/Azure-Samples/ms-identity-python-webapp-django)
* [Sample written in ![Flask](https://raw.githubusercontent.com/rayluo/identity/dev/docs/flask.webp)](https://github.com/Azure-Samples/ms-identity-python-webapp)
+* [Sample written in ![Quart](https://raw.githubusercontent.com/rayluo/identity/dev/docs/quart.webp)](https://github.com/rayluo/python-webapp-quart)
* Need support for more web frameworks?
[Upvote existing feature request or create a new one](https://github.com/rayluo/identity/issues)
+
+
+
+
+ How to customize the login page |
+
+
+The default login page will typically redirect users to your Identity Provider,
+so you don't have to customize it.
+But if the default login page is shown in your browser,
+you can read its HTML source code, and find the how-to instructions there.
+
|
@@ -90,18 +103,6 @@ They are demonstrated by the same samples above.
In roadmap.
-
-
-
-
- How to customize the login page |
-
-
-The default login page will typically redirect users to your Identity Provider,
-so you don't have to customize it.
-But if the default login page is shown in your browser,
-you can read its HTML source code, and find the how-to instructions there.
-
|
@@ -134,6 +135,7 @@ Choose the package declaration that matches your web framework:
* Django: `pip install identity[django]`
* Flask: `pip install identity[flask]`
+* Quart: `pip install identity[quart]`
## Versions
diff --git a/docs/flask.rst b/docs/flask.rst
index f3b90bb..014a777 100644
--- a/docs/flask.rst
+++ b/docs/flask.rst
@@ -73,19 +73,19 @@ Web app that logs in users and calls a web API on their behalf
#. Decorate your token-consuming views using the same
:py:func:`identity.flask.Auth.login_required` decorator,
- this time with a parameter ``scopes=["your_scope_1", "your_scope_2"]``.
+ this time with a parameter ``scopes=["your_scope_1", "your_scope_2"]``.
Then, inside your view, the token will be readily available via
``context['access_token']``. For example::
@app.route("/call_api")
- @auth.login_required(scopes=os.getenv("SCOPE", "").split())
+ @auth.login_required(scopes=["your_scope_1", "your_scope_2"])
def call_api(*, context):
api_result = requests.get( # Use access token to call a web api
"https://your_api.example.com",
headers={'Authorization': 'Bearer ' + context['access_token']},
timeout=30,
- ).json() # Here we assume the response format is json
+ )
...
All of the content above are demonstrated in
diff --git a/docs/quart.rst b/docs/quart.rst
index 5234309..afc86a3 100644
--- a/docs/quart.rst
+++ b/docs/quart.rst
@@ -35,20 +35,20 @@ Configuration
#. Setup session management with the `Quart-session `_ package, which currently supports either Redis or MongoDB backing stores. To use Redis as the session store, you should first install the package with the extra dependency::
-
+
pip install quart-session[redis]
#. Then add configuration to ``app.py`` pointing to your Redis instance::
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_URI'] = 'redis://localhost:6379'
-
+
Sign In and Sign Out
----------------------------------
#. In your web project's ``app.py``, decorate some views with the
- :py:func:`identity.flask.Auth.login_required` decorator.
+ :py:func:`identity.quart.Auth.login_required` decorator.
It will automatically trigger sign-in. ::
@app.route("/")
@@ -63,6 +63,31 @@ Sign In and Sign Out
Logout
+Web app that logs in users and calls a web API on their behalf
+--------------------------------------------------------------
+
+#. Decorate your token-consuming views using the same
+ :py:func:`identity.quart.Auth.login_required` decorator,
+ this time with a parameter ``scopes=["your_scope_1", "your_scope_2"]``.
+
+ Then, inside your view, the token will be readily available via
+ ``context['access_token']``. For example::
+
+ @app.route("/call_api")
+ @auth.login_required(scopes=["your_scope_1", "your_scope_2"])
+ async def call_api(*, context):
+ async with httpx.AsyncClient() as client:
+ api_result = await client.get( # Use access token to call a web api
+ os.getenv("ENDPOINT"),
+ headers={'Authorization': 'Bearer ' + context['access_token']},
+ )
+ return await render_template('display.html', result=api_result)
+
+
+All of the content above are demonstrated in
+`this Quart web app sample `_.
+
+
API reference
--------------------------
diff --git a/docs/quart.webp b/docs/quart.webp
new file mode 100644
index 0000000..71b322f
Binary files /dev/null and b/docs/quart.webp differ
diff --git a/identity/django.py b/identity/django.py
index f37ca5d..701f961 100644
--- a/identity/django.py
+++ b/identity/django.py
@@ -154,7 +154,7 @@ def my_view2(request, *, context):
"https://example.com/endpoint",
headers={'Authorization': 'Bearer ' + context['access_token']},
timeout=30,
- ).json() # Here we assume the response format is json
+ )
...
"""
# With or without brackets. Inspired by https://stackoverflow.com/a/39335652/728675
diff --git a/identity/flask.py b/identity/flask.py
index cb6f299..cc7cf30 100644
--- a/identity/flask.py
+++ b/identity/flask.py
@@ -38,7 +38,6 @@ def __init__(self, app: Flask, *args, **kwargs):
# Manually register the routes, since we cannot use @app or @bp on methods
if self._redirect_uri:
redirect_path = urlparse(self._redirect_uri).path
- #bp.route(redirect_path or "/auth_response")(self.auth_response)
bp.route(redirect_path)(self.auth_response)
bp.route(
f"{os.path.dirname(redirect_path)}/logout" # Use it in template by url_for("identity.logout")
@@ -107,12 +106,14 @@ def login_required( # Named after Django's login_required
Usage::
- @settings.AUTH.login_required
- def my_view(request, *, context):
- return render(request, 'index.html', dict(
+ @app.route("/")
+ @auth.login_required
+ def index(*, context):
+ return render_template(
+ 'index.html',
user=context["user"], # User is guaranteed to be present
# because we decorated this view with @login_required
- ))
+ )
:param list[str] scopes:
A list of scopes that your app will need to use.
@@ -121,13 +122,14 @@ def my_view(request, *, context):
Usage::
- @settings.AUTH.login_required(scopes=["scope1", "scope2"])
- def my_view2(request, *, context):
+ @app.route("/call_api")
+ @auth.login_required(scopes=["scope1", "scope2"])
+ def call_an_api(*, context):
api_result = requests.get( # Use access token to call an api
"https://example.com/endpoint",
headers={'Authorization': 'Bearer ' + context['access_token']},
timeout=30,
- ).json() # Here we assume the response format is json
+ )
...
"""
# With or without brackets. Inspired by https://stackoverflow.com/a/39335652/728675
diff --git a/identity/quart.py b/identity/quart.py
index c0870f8..28d98b3 100644
--- a/identity/quart.py
+++ b/identity/quart.py
@@ -38,7 +38,6 @@ def __init__(self, app: Quart, *args, **kwargs):
# Manually register the routes, since we cannot use @app or @bp on methods
if self._redirect_uri:
redirect_path = urlparse(self._redirect_uri).path
- #bp.route(redirect_path or "/auth_response")(self.auth_response)
bp.route(redirect_path)(self.auth_response)
bp.route(
f"{os.path.dirname(redirect_path)}/logout" # Use it in template by url_for("identity.logout")
@@ -107,12 +106,14 @@ def login_required( # Named after Django's login_required
Usage::
- @settings.AUTH.login_required
- def my_view(request, *, context):
- return render(request, 'index.html', dict(
+ @app.route("/")
+ @auth.login_required
+ async def index(*, context):
+ return await render_template(
+ 'index.html',
user=context["user"], # User is guaranteed to be present
# because we decorated this view with @login_required
- ))
+ )
:param list[str] scopes:
A list of scopes that your app will need to use.
@@ -121,14 +122,16 @@ def my_view(request, *, context):
Usage::
- @settings.AUTH.login_required(scopes=["scope1", "scope2"])
- def my_view2(request, *, context):
- api_result = requests.get( # Use access token to call an api
- "https://example.com/endpoint",
- headers={'Authorization': 'Bearer ' + context['access_token']},
- timeout=30,
- ).json() # Here we assume the response format is json
- ...
+ @app.route("/call_api")
+ @auth.login_required(scopes=["scope1", "scope2"])
+ async def call_api(*, context):
+ async with httpx.AsyncClient() as client:
+ api_result = await client.get( # Use access token to call a web api
+ os.getenv("ENDPOINT"),
+ headers={'Authorization': 'Bearer ' + context['access_token']},
+ )
+ return await render_template('display.html', result=api_result)
+
"""
# With or without brackets. Inspired by https://stackoverflow.com/a/39335652/728675