File: three_legged_oauth.rst

package info (click to toggle)
python-globus-sdk 3.54.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,032 kB
  • sloc: python: 34,226; sh: 44; makefile: 31
file content (204 lines) | stat: -rw-r--r-- 6,564 bytes parent folder | download
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"]))