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:
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 = "<CLIENT_ID>"
APP_CLIENT_SECRET = "<CLIENT_SECRET>"
Login¶
Let’s add login functionality to the end of example_app.py
, along with a
basic index page:
@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:
@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:
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"]))