You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Thank you @Colin-b and all contributors for this awesome package!
Just wanted to share some thoughts on possibility of async OAuth2 flow. I don't know all the corner cases this package handles, so please let me know whenever I write something naive.
OAuth2 flows use a global cache with a (threading) lock. It's a problem for async code (Support for async httpx clients #48 (comment)), but also doesn't seem necessary. The token and lock could be simply instance variables. The only problem that that I can think of, is when user creates multiple instances with the same auth server and key. Is there a valid case for such usage?
Actually the cache holds two locks, one for accessing cache, the other for refreshing the lock. Again this seem unnecessary:
when token is valid, the first concurrent call can acquire lock, check expiry and release it, while others wait for a short time.
when token is expired, the first concurrent call can acquire the lock, check expiry and refresh the token, while others have to wait until it finishes
The OAuth2 flows use locks within .auth_flow() method, against the Auth documentation.
If the authentication scheme does I/O such as disk access or network calls, or uses
synchronization primitives such as locks, you should override .sync_auth_flow()
and/or .async_auth_flow() instead of .auth_flow() to provide specialized
implementations that will be used by Client and AsyncClient respectively.
This is addressed in Support for async httpx clients #48 patch.
Having two locks makes sense when storage and refresh were split. In this case acquiring the refresh lock could be pulled to .a/sync_auth_flow().
.auth_flow(), via .request_new_token() uses own instance of httpx.Client. Again, it doesn't seem necessary. Httpx already provides a/sync-portable protocol for making HTTP requests from Auth instances: response = yield request.
If the auth server needs different transport options than the target server, mounts can be used. And if authentication is needed by the authentication server (meta-auth, client_auth param), the yielded requests need to be processed by meta-auth first.
Since all OAuth2 implementations in this package follow the pattern:
try getting cached token
if expired, fallback to self.request_new_grant()
set header
steps 1+2 run with a lock, and 3 is very lightweight, the entire flow can run with a single lock.
When the above are addressed, supporting both sync and async should be as simple as implementing this class:
classLockingRefreshingAuth(Auth):
def__init__(self):
self._sync_lock=threading.Lock()
self._async_lock=anyio.Lock()
defsync_auth_flow(self, request: Request) ->Generator[Request, Response, None]:
ifself.requires_request_body:
request.read()
withself._sync_lock:
# apply the loop over `self.auth_flow()`flow=self.auth_flow(request)
...
asyncdefasync_auth_flow(self, request: Request) ->AsyncGenerator[Request, Response]:
# like above, but async
Implementations would still yield their auth requests instead of directly using Client, and yield the user request before returning.
I've forked the repo to work on a POC. If it makes sense, and it's welcome, I'll make a PR. Meanwhile, comments are more than welcome.
edit 1: I've just noticed that auth token refresh requests also need to have authentication headers, and I guess that's why a client is used. But I still think it's unnecessary, and simply another Auth instance can be used
edit 2: It makes sense to have two locks if one lock protects a single Auth instance and the other the global cache.
Thank you @Colin-b and all contributors for this awesome package!
Just wanted to share some thoughts on possibility of async OAuth2 flow. I don't know all the corner cases this package handles, so please let me know whenever I write something naive.
OAuth2 flows use a global cache with a (threading) lock. It's a problem for async code (Support for async httpx clients #48 (comment)), but also doesn't seem necessary. The token and lock could be simply instance variables. The only problem that that I can think of, is when user creates multiple instances with the same auth server and key. Is there a valid case for such usage?
Actually the cache holds two locks, one for accessing cache, the other for refreshing the lock. Again this seem unnecessary:
The OAuth2 flows use locks within
.auth_flow()method, against the Auth documentation.This is addressed in Support for async httpx clients #48 patch.
Having two locks makes sense when storage and refresh were split. In this case acquiring the refresh lock could be pulled to
.a/sync_auth_flow()..auth_flow(), via.request_new_token()uses own instance ofhttpx.Client. Again, it doesn't seem necessary. Httpx already provides a/sync-portable protocol for making HTTP requests from Auth instances:response = yield request.If the auth server needs different transport options than the target server, mounts can be used. And if authentication is needed by the authentication server (meta-auth,
client_authparam), the yielded requests need to be processed by meta-auth first.Since all OAuth2 implementations in this package follow the pattern:
self.request_new_grant()steps 1+2 run with a lock, and 3 is very lightweight, the entire flow can run with a single lock.
When the above are addressed, supporting both sync and async should be as simple as implementing this class:
Implementations would still yield their auth requests instead of directly using Client, and yield the user request before returning.
I've forked the repo to work on a POC. If it makes sense, and it's welcome, I'll make a PR. Meanwhile, comments are more than welcome.
edit 1: I've just noticed that auth token refresh requests also need to have authentication headers, and I guess that's why a client is used. But I still think it's unnecessary, and simply another Auth instance can be used
edit 2: It makes sense to have two locks if one lock protects a single Auth instance and the other the global cache.