Device Authorization Grant (Flow) support for requests-oauthlib
Project description
Device Authorization Grant Session (DAGS) for Requests-OAuthlib
"Good dags. D'ya like dags?" - Mickey O'Neil, Snatch (2000)
Provides OAuth2 Sessions that support Device Authorization Grant flows as defined in RFC 8628.
The DeviceAuthorizationGrantSession class extends requests_oauthlib.OAuth2Session and leverages oauthlib.oauth2.rfc8628.clients.DeviceClient to initiate a Device Authorization Grant flow.
Calling the session's authorize() method will prompt the user to authorize the device and block the thread until a response is received from the authorizing server, or until the server-issued device code expires.
Right now, the library prints a message to stdout with the authorization URL. Future enhancements may include automatic browser launch, and perhaps additional options that don't require blocking the thread.
NOTE: This project is not affiliated with Requests-OAuthlib or any of its dependent libraries.
Install
pip install requests-oauthlib-dags
Examples
Easy Flow
This example initiates the Device Authorization Grant flow, outputs the authorization URL and user code with which the user needs to respond to stdout, and then blocks the thread until either the Authorization Server reports that the authorization has been approved by the user by providing an access token, the provided device code expires, or the Authorization Server otherwise reports that the grant has been denied.
from requests_oauthlib_dags import DeviceAuthorizationGrantSession
client_id = "your_client_id"
device_endpoint = "https://your_auth_server_device_grant_endpoint"
token_endpoint = "https://your_auth_server_token_endpoint"
session = DeviceAuthorizationGrantSession(client_id)
token = session.start_flow(
device_auth_url=device_authorization_endpoint,
token_url=token_endpoint,
)
# Prompt with auth url is output to `stdout`; execution resumes when the auth server accepts the user device grant.
print(token)
# Attempt to access a protected resource:
response = session.get("https://somesite.com/secure/resource")
Automatically Open Browser to Authorization URL
To have the flow process also attempt to open a browser tab to the Authorization URL, simply add the try_open_browser parameter:
token = session.start_flow(
device_auth_url=device_authorization_endpoint,
token_url=token_endpoint,
try_open_browser=True,
)
Manual Flow
This example provides a bit more control over the grant flow; it is essentially what start_flow does but you can interject any additional logic between the steps if needed:
from requests_oauthlib_dags import DeviceAuthorizationGrantSession
client_id = "your_client_id"
device_endpoint = "https://your_auth_server_device_grant_endpoint"
token_endpoint = "https://your_auth_server_token_endpoint"
session = DeviceAuthorizationGrantSession(client_id)
# Get the url and user code needed to authorize the device
authorization_url, user_code = session.authorization_url(device_endpoint)
# Prompt the user / attempt to open a browser window
session.authorize(
authorization_url=authorization_url,
user_code=user_code,
try_open_browser=True, # Omit or set to False to skip
)
# Block the thread until the authorization succeeds or fails
token = session.wait_for_authorization(token_endpoint)
Alternatives to Blocking with wait_for_authorization
If you want to implement your own logic to periodically check the Authorization Server, use the check_authorization method.
from requests_oauthlib_dags import DeviceAuthorizationGrantSession, exception
client_id = "your_client_id"
device_endpoint = "https://your_auth_server_device_grant_endpoint"
token_endpoint = "https://your_auth_server_token_endpoint"
session = DeviceAuthorizationGrantSession(client_id)
# Get the url and user code needed to authorize the device
authorization_url, user_code = session.authorization_url(device_endpoint)
# Prompt the user / attempt to open a browser window
session.authorize(
authorization_url=authorization_url,
user_code=user_code,
try_open_browser=True, # Omit or set to False to skip
)
# Wrap the following in your custom loop:
try:
authorized = session.check_authorization(token_endpoint)
except exception.SlowDownError:
# When this occurs, you should slow down your polling by at minimum 5 seconds,
# or apply an exponential backoff;
# See: https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
...
if authorized:
print(session.token)
break
Automatic Token Refresh
To avoid requiring clients to re-authorize whenever the access token expires, the DeviceAuthorizationGrantSession supports refreshing tokens via the same mechanism as OAuth2Session; see Refreshing tokens for more details.
The following example uses keyring to securely store the offline refresh token and use it to obtain a current access token:
import keyring
from requests_oauthlib_dags import DeviceAuthorizationGrantSession
client_id = "your_client_id"
device_endpoint = "https://your_auth_server_device_grant_endpoint"
token_endpoint = "https://your_auth_server_token_endpoint"
keyring_service = "my_service"
keyring_username = "my_username"
def update_token(token):
keyring.set_password(keyring_service, keyring_username, token["refresh_token"])
session = DeviceAuthorizationGrantSession(
client_id,
auto_refresh_url=token_endpoint, # Enable auto refresh
token_updater=update_token,
scope=["offline_access"], # See: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
)
refresh_token = keyring.get_password(keyring_service, keyring_username) # Try to get a stored refresh token
if refresh_token is not None:
# Use the refresh token
token = session.refresh_token(token_url=token_endpoint, refresh_token=refresh_token)
else:
# No existing refresh token, start the grant flow
token = session.authorize(
device_auth_url=device_endpoint,
token_url=token_endpoint,
)
update_token(token)
# Prompt with auth url is output to `stdout`; execution resumes when the auth server accepts the user device grant.
print(token)
# Attempt to access a protected resource:
response = session.get("https://somesite.com/secure/resource")
Default Headers
You can also configure the Session to always provide a set of default headers that will be provided with all requests:
session = DeviceAuthorizationGrantSession(
client_id,
headers={"Content-Type": "application/json"},
)
Contributing
This package utilizes Poetry for dependency management and pre-commit for ensuring code formatting is automatically done and code style checks are performed.
git clone https://github.com/Daveography/requests-oauthlib-dags.git requests-oauthlib-dags
cd requests-oauthlib-dags
pip install poetry
poetry install
poetry run pre-commit install
poetry run pre-commit autoupdate
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file requests_oauthlib_dags-0.1.0.tar.gz.
File metadata
- Download URL: requests_oauthlib_dags-0.1.0.tar.gz
- Upload date:
- Size: 8.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d34f808b8ef23ea63e832897f593f0ee65f71124216a5a940a37dc5a972d0b6
|
|
| MD5 |
73e227959404dc52e4c0830172fdc8a2
|
|
| BLAKE2b-256 |
bb55fade5e7d35b6e11e7ad9f70b9d29b6a87ab3cc7b0df95dc229a90a7e3718
|
Provenance
The following attestation bundles were made for requests_oauthlib_dags-0.1.0.tar.gz:
Publisher:
publish.yaml on Daveography/requests-oauthlib-dags
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
requests_oauthlib_dags-0.1.0.tar.gz -
Subject digest:
6d34f808b8ef23ea63e832897f593f0ee65f71124216a5a940a37dc5a972d0b6 - Sigstore transparency entry: 231676243
- Sigstore integration time:
-
Permalink:
Daveography/requests-oauthlib-dags@f7a4dd3e222387620c3d226c8dc24e337de46518 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Daveography
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@f7a4dd3e222387620c3d226c8dc24e337de46518 -
Trigger Event:
release
-
Statement type:
File details
Details for the file requests_oauthlib_dags-0.1.0-py3-none-any.whl.
File metadata
- Download URL: requests_oauthlib_dags-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12309cb3212a1aad73638c754126da99d4ca94b1b45197b2da24d9ae912b7e56
|
|
| MD5 |
18bd91aca68abfeb17d611136be0d14e
|
|
| BLAKE2b-256 |
e4bdfbdd4be58656f34f61e7b141643bf9c5ca95da96e987729b1dbec2ba90cb
|
Provenance
The following attestation bundles were made for requests_oauthlib_dags-0.1.0-py3-none-any.whl:
Publisher:
publish.yaml on Daveography/requests-oauthlib-dags
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
requests_oauthlib_dags-0.1.0-py3-none-any.whl -
Subject digest:
12309cb3212a1aad73638c754126da99d4ca94b1b45197b2da24d9ae912b7e56 - Sigstore transparency entry: 231676271
- Sigstore integration time:
-
Permalink:
Daveography/requests-oauthlib-dags@f7a4dd3e222387620c3d226c8dc24e337de46518 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Daveography
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@f7a4dd3e222387620c3d226c8dc24e337de46518 -
Trigger Event:
release
-
Statement type: