Using a GlobusApp

Programmatic communication with Globus services relies on the authorization of requests. Management and resolution of this authorization can become an arduous task, especially when a script needs to interact with different services each carrying an individual set of complex authentication and authorization requirements. To assist with this task, this library provides a utility construct called a GlobusApp.

A GlobusApp is a distinct object which will manage Globus Auth requirements (i.e., scopes) identified by their associated service (i.e., resource server). In addition to storing them, a GlobusApp provides a mechanism to resolve unmet requirements through browser- and API-based authorization flows, supplying the resulting tokens to bound clients as requested.

There are two flavors of GlobusApp:

  • UserApp, a GlobusApp for interactions between an end user and Globus services. Operations are performed as a user identity.

  • ClientApp, a GlobusApp for interactions between a client (i.e. service account) and Globus services. Operations are performed as a client identity.

Setup

A GlobusApp is a heavily configurable object. For common scripting usage however, instantiation only requires two parameters:

  1. App Name - A human readable name to identify your app in HTTP requests and token caching (e.g., “My Cool Weathervane”).

  2. Client Info - either a Native Client’s ID or a Confidential Client’s ID and secret pair.

    • There are important distinctions to consider when choosing your client type; see Developing an Application Using Globus Auth.

      A simplified heuristic to help choose the client type however is:

      • Use a Confidential Client when your client needs to own cloud resources itself and will be used in a trusted environment where you can securely hold a secret.

      • Use a Native Client when your client will be facilitating interactions between a user and a service, particularly if it is bundled within a script or cli tool to be distributed to end-users’ machines.

    Note

    Both UserApps and ClientApps require a client.

    In a UserApp, the client sends requests representing a user identity; whereas in a ClientApp, the requests represent the client identity itself.

Once instantiated, a GlobusApp can be passed to any service client using the init app keyword argument (e.g. TransferClient(app=my_app)). Doing this will bind the app to the client, registering a default set of scopes requirements for the service client’s resource server and configuring the app as the service client’s auth provider.

Construct a UserApp then bind it to a Transfer client and a Flows client.

Note

UserApp.__init__(...) currently only supports Native clients. Confidential client support is forthcoming.

import globus_sdk
from globus_sdk.experimental.globus_app import UserApp

CLIENT_ID = "..."
my_app = UserApp("my-user-app", client_id=CLIENT_ID)

transfer_client = globus_sdk.TransferClient(app=my_app)
flows_client = globus_sdk.FlowsClient(app=my_app)

Construct a ClientApp, then bind it to a Transfer client and a Flows client.

Note

ClientApp.__init__(...) requires the client_secret keyword argument. Native clients, which lack secrets, are not allowed.

import globus_sdk
from globus_sdk.experimental.globus_app import ClientApp

CLIENT_ID = "..."
CLIENT_SECRET = "..."
my_app = ClientApp("my-client-app", client_id=CLIENT_ID, client_secret=CLIENT_SECRET)

transfer_client = globus_sdk.TransferClient(app=my_app)
flows_client = globus_sdk.FlowsClient(app=my_app)

Usage

From this point, the app manages scope validation, token caching and routing for any bound clients.

In the above example, listing a client’s or user’s flows becomes as simple as:

flows = flows_client.list_flows()["flows"]

If cached tokens are missing, expired, or otherwise insufficient (e.g., the first time you run the script), the app will automatically initiate an auth flow to acquire new tokens. With a UserApp, the app will print a URL to the terminal with a prompt instructing a the user to follow the link and enter the code they’re given back into the terminal. With a ClientApp, the app will retrieve tokens programmatically through a Globus Auth API.

Once this auth flow has finished, the app will cache tokens for future use and invocation of your requested method will proceed as expected.

Manually Running Login Flows

While your app will automatically initiate and oversee login flows when needed, sometimes an author may want to explicitly control when an authorization occurs. To manually trigger a login flow, call GlobusApp.login(...). The app will evaluate the current scope requirements against available tokens, initiating a login flow if it determines that any requirements across any resource servers are unmet. Resulting tokens will be cached for future use.

This method accepts two optional keyword args:

  • auth_params, a collection of additional auth parameters to customize the login. This allows for specifications such as requiring that a user be logged in with an MFA token or rendering the authorization webpage with a specific message.

  • force, a boolean flag instructing the app to perform a login flow regardless of whether it is required.

from globus_sdk.experimental.auth_requirements_error import GlobusAuthorizationParameters

...

my_app.login(
    auth_params=GlobusAuthorizationParameters(
        session_message="Please authenticate with MFA",
        session_required_mfa=True,
    )
)

Manually Defining Scope Requirements

Globus service client classes all maintain an internal list of default scope requirements to be attached to any bound app. These scopes represent an approximation of a “standard set” for each service. This list however is not sufficient for all use cases.

For example, the FlowsClient defines its default scopes as flows:view_flows and flows:run_status (read-only access). These scopes will not be sufficient for a script which needs to create new flows or modify existing ones. For that script, the author must manually attach the flows:manage_flows scope to the app.

This can be done in one of two ways:

  1. Through a service client initialization, using the app_scopes kwarg.

    from globus_sdk import Scope, FlowsClient
    
    FlowsClient(app=my_app, app_scopes=[Scope(FlowsClient.scopes.manage_flows)])
    

    This approach results in an app which only requires the flows:manage_flows scope. The default scopes (flows:view_flows and flows:run_status) are not registered.

  2. Through a service client’s add_app_scope method.

    from globus_sdk import FlowsClient
    
    flows_client = FlowsClient(app=my_app)
    flows_client.add_app_scope(FlowsClient.scopes.manage_flows)
    

    This approach will add the flows:manage_flows scope to the app’s existing set of scopes. Since app_scopes was omitted in the client initialization, the default scopes are registered as well.