How to Create A Minimal Script without GlobusApp¶
GlobusApp
provides a number of useful abstractions in the SDK.
It handles login flows and storage of tokens, coupled with later retrieval of
those tokens for use. It can keep track of which clients have been created and
registered with an app, and therefore make intelligent decisions about how and
when to prompt users to login.
New users should read the guide for writing a minimal script before reading this doc.
GlobusApp
is built from several simpler components which can be used to
implement similar behaviors.
This doc covers how to write a simple script, but without using
GlobusApp
.
For readers who prefer to start with complete examples, jump ahead to these sections before reviewing the doc:
Cases for not Using GlobusApp¶
There are at least three main reasons to be interested in defining applications
without GlobusApp
:
You have a use case which doesn’t fit the behaviors of an app. e.g., Implementations of APIs or services.
Any legacy codebase, predating
GlobusApp
, will use the underlying constructs to implement login behaviors.Customizing and extending
GlobusApp
to suit your use case may require understanding the underlying components.
Note
Prior to the introduction of GlobusApp
, these tools were the only
way that an application could be written with the globus_sdk
. If you
are maintaining an existing application, you may need to be strategic about
when and how to upgrade such usages.
Define an Auth Client Instance¶
In order to interact with Globus Auth, you will need a client object. This will be the driver of the login flow.
import globus_sdk
# this is the tutorial client ID
# replace this string with your ID for production use
CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
# create a client for interactions with Globus Auth
auth_client = globus_sdk.NativeAppAuthClient(CLIENT_ID)
This uses a NativeAppAuthClient
, which is one of the two types of client
object supporting logins. The other is ConfidentialAppAuthClient
,
which is for clients which authenticate themselves using a stored secret.
There are differences in behavior between the two types of Auth client, but
NativeAppAuthClient
is more appropriate for scripts which you may
later redistribute.
Login and Consent for Groups Access¶
In OAuth2, login flows are always driven through a web browser. In order to connect our simple script to the browser context, we will go through a challenge-response flow. The script will print out a login URL. Upon logging in and returning to the script, you paste in a verification code, which is then exchanged for tokens.
For simplicity, we’ll print the login prompt to stdout and accept the authorization code with a prompt for input.
Additionally, we will need to specify what scopes (what actions on what services) we want access to in our script. This will drive the consent prompt in the Globus Auth web interface.
# using that client, do a login flow for Globus Groups credentials
auth_client.oauth2_start_flow(
requested_scopes=globus_sdk.GroupsClient.scopes.view_my_groups_and_memberships
)
authorize_url = auth_client.oauth2_get_authorize_url()
print(f"Please go to this URL and login:\n\n{authorize_url}\n")
auth_code = input("Please enter the code here: ").strip()
tokens = auth_client.oauth2_exchange_code_for_tokens(auth_code)
Create and Use a GroupsClient¶
To make use of the tokens procured in the previous step, you’ll need to create a client object, pass it the appropriate token data, and use it to call out to the Globus Groups API.
Credentials are passed through a generic “authorizer” interface which allows the tokens to be passed statically or as a reference to some dynamic data source.
# extract tokens from the response which match Globus Groups
groups_tokens = tokens.by_resource_server[globus_sdk.GroupsClient.resource_server]
# construct an AccessTokenAuthorizer and use it to construct the GroupsClient
groups_client = globus_sdk.GroupsClient(
authorizer=globus_sdk.AccessTokenAuthorizer(groups_tokens["access_token"])
)
# call out to the Groups service to get a listing
my_groups = groups_client.get_my_groups()
# print in CSV format
print("ID,Name,Roles")
for group in my_groups:
roles = "|".join({m["role"] for m in group["my_memberships"]})
print(",".join([group["id"], f'"{group["name"]}"', roles]))
Recap: List Groups Script¶
The previous sections can be combined into a working script.
The following example is complete. It should run without modification “as-is”.
import globus_sdk
# this is the tutorial client ID
# replace this string with your ID for production use
CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
# create a client for interactions with Globus Auth
auth_client = globus_sdk.NativeAppAuthClient(CLIENT_ID)
# using that client, do a login flow for Globus Groups credentials
auth_client.oauth2_start_flow(
requested_scopes=globus_sdk.GroupsClient.scopes.view_my_groups_and_memberships
)
authorize_url = auth_client.oauth2_get_authorize_url()
print(f"Please go to this URL and login:\n\n{authorize_url}\n")
auth_code = input("Please enter the code here: ").strip()
tokens = auth_client.oauth2_exchange_code_for_tokens(auth_code)
# extract tokens from the response which match Globus Groups
groups_tokens = tokens.by_resource_server[globus_sdk.GroupsClient.resource_server]
# construct an AccessTokenAuthorizer and use it to construct the GroupsClient
groups_client = globus_sdk.GroupsClient(
authorizer=globus_sdk.AccessTokenAuthorizer(groups_tokens["access_token"])
)
# call out to the Groups service to get a listing
my_groups = groups_client.get_my_groups()
# print in CSV format
print("ID,Name,Roles")
for group in my_groups:
roles = "|".join({m["role"] for m in group["my_memberships"]})
print(",".join([group["id"], f'"{group["name"]}"', roles]))
Adding in Refresh Tokens & Token Storage¶
To expand upon this example, it is possible to request long-lived tokens called “refresh tokens”, which are valid until they are revoked or go unused for a long period. Making use of refresh tokens is most appropriate if we also store the tokens between runs of the script, so that we can reuse the tokens.
Refresh tokens operate by getting an access token, like the example above, but allowing you to automatically replace or “refresh” that token any time it expires. We will therefore also need to elaborate our usage to handle these automatic refreshes.
Requesting Refresh Tokens¶
To request refresh tokens, simply pass refresh_tokens=True
to the
oauth2_start_flow
call:
auth_client.oauth2_start_flow(
requested_scopes=globus_sdk.GroupsClient.scopes.view_my_groups_and_memberships,
refresh_tokens=True,
)
Defining Token Storage¶
Token storage abstractions are defined in the SDK which provide the ability to read or write token data in a structured way. Defining a token storage object is simple:
import os
from globus_sdk.tokenstorage import JSONTokenStorage
token_storage = JSONTokenStorage(
os.path.expanduser("~/.list-my-globus-groups-tokens.json")
)
Linking a Login Flow to Token Storage¶
To connect the tokens from login to a token storage, use the token storage
method store_token_response()
at the end of the login flow.
And in order to make the script only prompt for login if there are no tokens,
we can ask the JSONTokenStorage.file_exists()
method whether or not there
is a file.
This rewrites our login block to be nested under a file_exists()
check:
# if there is no stored token file, we have not yet logged in
if not token_storage.file_exists():
# do a login flow, getting back a token response
auth_client.oauth2_start_flow(
requested_scopes=globus_sdk.GroupsClient.scopes.view_my_groups_and_memberships,
refresh_tokens=True,
)
authorize_url = auth_client.oauth2_get_authorize_url()
print(f"Please go to this URL and login:\n\n{authorize_url}\n")
auth_code = input("Please enter the code here: ").strip()
token_response = auth_client.oauth2_exchange_code_for_tokens(auth_code)
# now store the tokens
token_storage.store_token_response(token_response)
Construct and Use the GroupsClient¶
Now that we have a new authorizer, it is simple to construct and use a new client, just as before:
# use that authorizer to authorize the activity of the groups client
groups_client = globus_sdk.GroupsClient(authorizer=authorizer)
# call out to the Groups service to get a listing
my_groups = groups_client.get_my_groups()
# print in CSV format
print("ID,Name,Roles")
for group in my_groups:
roles = "|".join({m["role"] for m in group["my_memberships"]})
print(",".join([group["id"], f'"{group["name"]}"', roles]))
Recap: List Groups with RefreshTokens¶
As a complete example of the List Groups script with token storage and a refresh token authorizer, the above sections can be combined into the following script:
The following example is complete. It should run without modification “as-is”.
import os
import globus_sdk
from globus_sdk.tokenstorage import JSONTokenStorage
# this is the tutorial client ID
# replace this string with your ID for production use
CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
# define a client for interactions with Globus Auth
auth_client = globus_sdk.NativeAppAuthClient(CLIENT_ID)
# define token storage where tokens will be stored between runs
token_storage = JSONTokenStorage(
os.path.expanduser("~/.list-my-globus-groups-tokens.json")
)
# if there is no stored token file, we have not yet logged in
if not token_storage.file_exists():
# do a login flow, getting back a token response
auth_client.oauth2_start_flow(
requested_scopes=globus_sdk.GroupsClient.scopes.view_my_groups_and_memberships,
refresh_tokens=True,
)
authorize_url = auth_client.oauth2_get_authorize_url()
print(f"Please go to this URL and login:\n\n{authorize_url}\n")
auth_code = input("Please enter the code here: ").strip()
token_response = auth_client.oauth2_exchange_code_for_tokens(auth_code)
# now store the tokens
token_storage.store_token_response(token_response)
# load the tokens from the storage -- either freshly stored or loaded from disk
token_data = token_storage.get_token_data(globus_sdk.GroupsClient.resource_server)
# construct the RefreshTokenAuthorizer which writes back to storage on refresh
authorizer = globus_sdk.RefreshTokenAuthorizer(
token_data.refresh_token,
auth_client,
access_token=token_data.access_token,
expires_at=token_data.expires_at_seconds,
on_refresh=token_storage.store_token_response,
)
# use that authorizer to authorize the activity of the groups client
groups_client = globus_sdk.GroupsClient(authorizer=authorizer)
# call out to the Groups service to get a listing
my_groups = groups_client.get_my_groups()
# print in CSV format
print("ID,Name,Roles")
for group in my_groups:
roles = "|".join({m["role"] for m in group["my_memberships"]})
print(",".join([group["id"], f'"{group["name"]}"', roles]))