1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
.. _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 <https://docs.globus.org/api/auth/>`_ or go directly to
`developers.globus.org <https://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 = "<CLIENT_ID>"
APP_CLIENT_SECRET = "<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"]))
|