Skip to content

Commit 0cea292

Browse files
CG-1904 improve expired session ux (#1200)
* improved user and dev experience for expired tokens.
1 parent da48f57 commit 0cea292

7 files changed

+94
-18
lines changed

examples/auth-session-management/app_managed_auth_state__app_custom_storage__oauth_user_authcode__with_browser.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,12 @@ def example_main():
9191
storage_provider=DemoStorageProvider(),
9292
)
9393

94-
# In contrast to an in-memory only application that must initialize a login every
95-
# time, an app with persistent storage can skip this when it is not needed.
96-
if not plsdk_auth.is_initialized():
97-
plsdk_auth.user_login(allow_open_browser=True)
94+
# In contrast to in-memory applications that must initialize a login every
95+
# time, an app with persistent storage can skip user prompts when they
96+
# are not needed.
97+
# This helper will prompt the user only when it is necessary.
98+
plsdk_auth.ensure_initialized(allow_open_browser=True,
99+
allow_tty_prompt=True)
98100

99101
# Create a Planet SDK object that uses the loaded auth session.
100102
sess = planet.Session(plsdk_auth)

examples/auth-session-management/app_managed_auth_state__on_disk_cli_shared__oauth_user_authcode__with_browser.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ def example_main():
1818
save_state_to_storage=True,
1919
)
2020

21-
# In contrast to an in-memory only application that must initialize a login every
22-
# time, an app with persistent storage can skip this when it is not needed.
23-
if not plsdk_auth.is_initialized():
24-
plsdk_auth.user_login(allow_open_browser=True)
21+
# In contrast to in-memory applications that must initialize a login every
22+
# time, an app with persistent storage can skip user prompts when they
23+
# are not needed.
24+
# This helper will prompt the user only when it is necessary.
25+
plsdk_auth.ensure_initialized(allow_open_browser=True,
26+
allow_tty_prompt=True)
2527

2628
# Create a Planet SDK object that uses the loaded auth session.
2729
sess = planet.Session(plsdk_auth)

examples/auth-session-management/app_managed_auth_state__on_disk_legacy__api_key.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def example_main():
77
# specified file that was created with older versions of the SDK
88
plsdk_auth = planet.Auth.from_file("legacy_api_key_file.json")
99

10-
# Explicit login is not required for API key use. The above sufficient.
10+
# Explicit login is not required for API key use. The above is sufficient.
1111
# plsdk_auth.user_login()
1212

1313
# Create a Planet SDK object that uses the loaded auth session

examples/auth-session-management/app_managed_auth_state__using_sdk_app_id.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ def example_main():
1010
# is false, the state will only be persistent in memory and the
1111
# user will need to login each time the application is run.
1212
plsdk_auth = planet.Auth.from_profile("planet-user",
13-
save_state_to_storage=False)
13+
save_state_to_storage=True)
1414

15-
if not plsdk_auth.is_initialized():
16-
plsdk_auth.user_login(allow_open_browser=True, allow_tty_prompt=True)
15+
plsdk_auth.ensure_initialized(allow_open_browser=True,
16+
allow_tty_prompt=True)
1717

1818
# Create a Planet SDK object that uses the loaded auth session.
1919
sess = planet.Session(plsdk_auth)

examples/auth-session-management/cli_managed_auth_state__explicit.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ def example_main():
1515
)
1616
sys.exit(99)
1717

18+
# Alternatively, an application can call this, which will attempt to
19+
# initialize the session if it is not already initialized.
20+
# plsdk_auth.ensure_initialized(allow_open_browser=True, allow_tty_prompt=True)
21+
1822
# Create a Planet SDK object that uses the loaded auth session.
1923
sess = planet.Session(plsdk_auth)
2024
pl = planet.Planet(sess)

planet/auth.py

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import typing
2222
import warnings
2323
import httpx
24-
from typing import List
2524

2625
from .auth_builtins import _ProductionEnv, _OIDC_AUTH_CLIENT_CONFIG__USER_SKEL, _OIDC_AUTH_CLIENT_CONFIG__M2M_SKEL
2726
import planet_auth
@@ -132,7 +131,7 @@ def from_profile(
132131
def from_oauth_user_auth_code(
133132
client_id: str,
134133
callback_url: str,
135-
requested_scopes: typing.Optional[List[str]] = None,
134+
requested_scopes: typing.Optional[typing.List[str]] = None,
136135
save_state_to_storage: bool = True,
137136
profile_name: typing.Optional[str] = None,
138137
storage_provider: typing.Optional[
@@ -193,7 +192,7 @@ def from_oauth_user_auth_code(
193192
@staticmethod
194193
def from_oauth_user_device_code(
195194
client_id: str,
196-
requested_scopes: typing.Optional[List[str]] = None,
195+
requested_scopes: typing.Optional[typing.List[str]] = None,
197196
save_state_to_storage: bool = True,
198197
profile_name: typing.Optional[str] = None,
199198
storage_provider: typing.Optional[
@@ -255,7 +254,7 @@ def from_oauth_user_device_code(
255254
def from_oauth_m2m(
256255
client_id: str,
257256
client_secret: str,
258-
requested_scopes: typing.Optional[List[str]] = None,
257+
requested_scopes: typing.Optional[typing.List[str]] = None,
259258
save_state_to_storage: bool = True,
260259
profile_name: typing.Optional[str] = None,
261260
storage_provider: typing.Optional[
@@ -456,7 +455,66 @@ def is_initialized(self) -> bool:
456455
user based sessions, this means that a login has been performed
457456
or saved login session data has been located. For M2M and API Key
458457
sessions, this should be true if keys or secrets have been
459-
properly configured.
458+
properly configured. The network will not be probed, and the user
459+
will not be prompted by this method.
460+
461+
Expired sessions or invalid credentials will not be detected.
462+
See `ensure_initialized()` for a method that will check the validity
463+
of sessions.
464+
"""
465+
466+
@abc.abstractmethod
467+
def ensure_initialized(
468+
self,
469+
allow_open_browser: typing.Optional[bool] = False,
470+
allow_tty_prompt: typing.Optional[bool] = False,
471+
) -> None:
472+
"""
473+
Do everything necessary to ensure the auth context is ready for use,
474+
while still biasing towards just-in-time operations and not making
475+
unnecessary network requests or prompts for user interaction.
476+
477+
This can be more complex than it sounds given the variations in
478+
possible session state. Clients may be initialized with active
479+
sessions, initialized with stale but still valid sessions,
480+
initialized with invalid or expired sessions, or completely
481+
uninitialized. The process taken to ensure client readiness with
482+
as little user disruption as possible is as follows:
483+
484+
1. If the client has been logged in and has a non-expired
485+
session, the client will be considered ready without prompting
486+
the user or probing the network. This will not require user
487+
interaction.
488+
2. If the client has not been logged in and is an M2M client,
489+
the client will be considered ready without prompting
490+
the user or probing the network. This will not require
491+
user interaction. Login will be delayed until it is required.
492+
3. If the client has been logged in and has an expired access token,
493+
the network will be probed to attempt a refresh of the session.
494+
This should not require user interaction. If refresh fails,
495+
the user will be prompted to perform a fresh login, requiring
496+
user interaction.
497+
4. If the client has never been logged in and is a user interactive
498+
client (verses an M2M client), a user interactive login will be
499+
initiated.
500+
501+
There still may be conditions where we believe we are
502+
ready, but requests will still ultimately fail. Saved secrets for M2M
503+
clients could be wrong, or the user could be denied by API access
504+
rules that are independent of session authentication.
505+
506+
When a user interactive login is required, the client must specify
507+
whether a local web browser may be opened and/or whether the TTY
508+
may be used to prompt the user. What is appropriate will depend
509+
on the nature of the application using the Planet SDK.
510+
511+
If the auth context cannot be made ready, an exception will be raised.
512+
513+
Parameters:
514+
allow_open_browser: specify whether login is permitted to open
515+
a local browser window.
516+
allow_tty_prompt: specify whether login is permitted to request
517+
input from the terminal.
460518
"""
461519

462520

@@ -496,5 +554,15 @@ def device_user_login_complete(self, login_initialization_info: dict):
496554
def is_initialized(self) -> bool:
497555
return self._plauth.request_authenticator_is_ready()
498556

557+
def ensure_initialized(
558+
self,
559+
allow_open_browser: typing.Optional[bool] = False,
560+
allow_tty_prompt: typing.Optional[bool] = False,
561+
) -> None:
562+
return self._plauth.ensure_request_authenticator_is_ready(
563+
allow_open_browser=allow_open_browser,
564+
allow_tty_prompt=allow_tty_prompt,
565+
)
566+
499567

500568
AuthType = Auth

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ dependencies = [
1414
"pyjwt>=2.1",
1515
"tqdm>=4.56",
1616
"typing-extensions",
17-
"planet-auth>=2.1.0",
17+
"planet-auth>=2.3.0",
1818
]
1919
readme = "README.md"
2020
requires-python = ">=3.10"

0 commit comments

Comments
 (0)