Identity for Quart

Prerequisite

Create a hello world web project in Quart. Here we assume the project’s main file is named app.py.

Configuration

  1. Install dependency by pip install identity[quart]

  2. Create an instance of the identity.quart.Auth object, and assign it to a global variable inside your app.py:

    import os
    
    from quart import Quart
    from identity.quart import Auth
    
    app = Quart(__name__)
    
    auth = Auth(
        app,
    
        # Instruction for these settings is available in this project's README file.
        # https://github.com/rayluo/identity?tab=readme-ov-file#scenarios-supported
        os.getenv('CLIENT_ID'),
        client_credential=os.getenv('CLIENT_SECRET'),
        redirect_uri=
            # Recommended to register and use a redirect_uri.
            # It looks like http://localhost:5000/redirect for local development,
            # or https://your_website.com/redirect for your production.
            # If absent, Identity library will fall back to a Device Code mode.
            os.getenv('REDIRECT_URI'),
    
        ...,  # See below on how to feed in the authority url parameter
        )
    

    Tip

    We recommend storing settings in environment variables. The snippet above read data from environment variables.

    Initializing Auth object differently based on Identity Provider type

    Its authority URL looks like

    Initialize Auth() object like this

    Microsoft Entra ID

    https://login.microsoftonline.com/tenant

    Auth(…, authority=url, …)

    Microsoft Entra External ID

    https://contoso.ciamlogin.com/contoso.onmicrosoft.com

    Microsoft Entra External ID with Custom Domain

    https://contoso.com/tenant

    Auth(…, oidc_authority=url, …)

    Azure AD B2C

    N/A

    Auth(…, b2c_tenant_name=”contoso”, b2c_signup_signin_user_flow=”susi”)

  3. 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]
    
  4. 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

  1. In your web project’s app.py, decorate some views with the identity.quart.Auth.login_required() decorator. It will automatically trigger sign-in.

    @app.route("/")
    @auth.login_required
    async def index(*, context):
        user = context['user']
        ...
    
  2. In your web project’s any template that you see fit, add this URL to present the logout link:

    <a href="{{ url_for('identity.logout') }}">Logout</a>
    

Web app that logs in users and calls a web API on their behalf

  1. Decorate your token-consuming views using the same 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

class identity.quart.Auth(app: Quart | None, *args, post_logout_view: callable | None = None, **kwargs)

A long-live identity auth helper for a Quart web project.

__init__(app: Quart | None, *args, post_logout_view: callable | None = None, **kwargs)

Create an identity helper for a web application.

Parameters:
  • app (Quart) –

    It can be a Quart app instance, or None.

    1. If your app object is globally available, you may pass it in here. Usage:

      # In your app.py
      app = Quart(__name__)
      auth = Auth(app, authority=..., client_id=..., ...)
      

    2. But if you are using Application Factory pattern, your app is not available globally, so you need to pass None here, and call Auth.init_app() later, inside or after your app factory function. Usage:

    # In your auth.py
    auth = Auth(app=None, authority=..., client_id=..., ...)
    
    # In your other blueprints or modules
    from auth import auth
    
    bp = Blueprint("my_blueprint", __name__)
    
    @bp.route("/")
    @auth.login_required
    async def my_view(*, context):
        ...
    
    # In your app.py
    from auth import auth
    import my_blueprint
    def build_app():
        app = Quart(__name__)
        auth.init_app(app)
        app.register_blueprint(my_blueprint.bp)
        return app
    
    app = build_app()
    

  • post_logout_view (callable) – Optional. If not provided, the user will be redirected to the root URL of the app. If provided, it shall be the view (which is a function) that will be redirected to, after the user has logged out.

It also passes extra parameters to identity.pallet.PalletAuth.

get_edit_profile_url()

A helper to get the URL for Microsoft Entra B2C’s edit profile page.

You can pass this URL to your template and render it there.

init_app(app)

Initialize the auth helper with your app instance.

login_required(function=None, /, *, scopes: List[str] | None = None)

A decorator that ensures the user to be logged in, and optinoally also have consented to a list of scopes.

A user not meeting the requirement(s) will be brought to the login page. For already logged-in user, the view will be called with a keyword argument named “context” which is a dict containing the user object.

Usage:

@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
    )
Parameters:

scopes (list[str]) –

A list of scopes that your app will need to use. When present, the context will also contain an “access_token”, “token_type”, and likely “expires_in” and “refresh_token”.

Usage:

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