.. _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". Set the Scopes to ``openid``, ``profile``, ``email``, ``urn:globus:auth:scope:transfer.api.globus.org:all``. 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==0.11.1 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 defintiion 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"]))