.. _examples_three_legged_oauth_login: Three Legged OAuth with Flask ----------------------------- This type of authorization is used for web login with a server-side application. For example, a Django app or other application server handles requests. This example uses Flask, but should be easily portable to other application frameworks. Components ~~~~~~~~~~ There are two components to this application: login and logout. Login sends a user to Globus Auth to get credentials, and then may act on the user's behalf. Logout invalidates server-side credentials, so that the application may no longer take actions for the user, and the client-side session, allowing for a fresh login if desired. Register an App ~~~~~~~~~~~~~~~ In order to complete an OAuth2 flow to get tokens, you must have a client definition registered with Globus Auth. To do so, follow the relevant documentation for the `Globus Auth Service `_ or go directly to `developers.globus.org `_ to do the registration. Make sure that the "Native App" checkbox is unchecked, and list ``http://localhost:5000/login`` in the "Redirect URIs". On the projects page, expand the client description and click "Generate Secret". Save the resulting secret a file named ``example_app.conf``, along with the client ID: .. code-block:: python SERVER_NAME = "localhost:5000" # this is the session secret, used to protect the Flask session. You should # use a longer secret string known only to your application # details are beyond the scope of this example SECRET_KEY = "abc123!" APP_CLIENT_ID = "" APP_CLIENT_SECRET = "" Shared Utilities ~~~~~~~~~~~~~~~~ Some pieces that are of use for both parts of this flow. First, you'll need to install ``Flask`` and the ``globus-sdk``. Assuming you want to do so into a fresh virtualenv: .. code-block:: bash $ virtualenv example-venv ... $ source example-venv/bin/activate $ pip install flask globus-sdk ... You'll also want a shared function for loading the SDK ``AuthClient`` which represents your application, as you'll need it in a couple of places. Create it, along with the definition for your Flask app, in ``example_app.py``: .. code-block:: python from flask import Flask, url_for, session, redirect, request import globus_sdk app = Flask(__name__) app.config.from_pyfile("example_app.conf") # actually run the app if this is called as a script if __name__ == "__main__": app.run() def load_app_client(): return globus_sdk.ConfidentialAppAuthClient( app.config["APP_CLIENT_ID"], app.config["APP_CLIENT_SECRET"] ) Login ~~~~~ Let's add login functionality to the end of ``example_app.py``, along with a basic index page: .. code-block:: python @app.route("/") def index(): """ This could be any page you like, rendered by Flask. For this simple example, it will either redirect you to login, or print a simple message. """ if not session.get("is_authenticated"): return redirect(url_for("login")) return "You are successfully logged in!" @app.route("/login") def login(): """ Login via Globus Auth. May be invoked in one of two scenarios: 1. Login is starting, no state in Globus Auth yet 2. Returning to application during login, already have short-lived code from Globus Auth to exchange for tokens, encoded in a query param """ # the redirect URI, as a complete URI (not relative path) redirect_uri = url_for("login", _external=True) client = load_app_client() client.oauth2_start_flow(redirect_uri) # If there's no "code" query string parameter, we're in this route # starting a Globus Auth login flow. # Redirect out to Globus Auth if "code" not in request.args: auth_uri = client.oauth2_get_authorize_url() return redirect(auth_uri) # If we do have a "code" param, we're coming back from Globus Auth # and can start the process of exchanging an auth code for a token. else: code = request.args.get("code") tokens = client.oauth2_exchange_code_for_tokens(code) # store the resulting tokens in the session session.update(tokens=tokens.by_resource_server, is_authenticated=True) return redirect(url_for("index")) Logout ~~~~~~ Logout is very simple -- it's just a matter of cleaning up the session. It does the added work of cleaning up any tokens you fetched by invalidating them in Globus Auth beforehand: .. code-block:: python @app.route("/logout") def logout(): """ - Revoke the tokens with Globus Auth. - Destroy the session state. - Redirect the user to the Globus Auth logout page. """ client = load_app_client() # Revoke the tokens with Globus Auth for token in ( token_info["access_token"] for token_info in session["tokens"].values() ): client.oauth2_revoke_token(token) # Destroy the session state session.clear() # the return redirection location to give to Globus AUth redirect_uri = url_for("index", _external=True) # build the logout URI with query params # there is no tool to help build this (yet!) globus_logout_url = ( "https://auth.globus.org/v2/web/logout" + "?client={}".format(app.config["PORTAL_CLIENT_ID"]) + "&redirect_uri={}".format(redirect_uri) + "&redirect_name=Globus Example App" ) # Redirect the user to the Globus Auth logout page return redirect(globus_logout_url) Using the Tokens ~~~~~~~~~~~~~~~~ Using the tokens thus acquired is a simple matter of pulling them out of the session and putting one into an ``AccessTokenAuthorizer``. For example, one might do the following: .. code-block:: python authorizer = globus_sdk.AccessTokenAuthorizer( session["tokens"]["transfer.api.globus.org"]["access_token"] ) transfer_client = globus_sdk.TransferClient(authorizer=authorizer) print("Endpoints belonging to the current logged-in user:") for ep in transfer_client.endpoint_search(filter_scope="my-endpoints"): print("[{}] {}".format(ep["id"], ep["display_name"]))