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
{{ message }}
Repository navigation
feat(auth): refresh grant + device-flow parity for public clients — retire campus-cli's last raw token calls #87
Follow-up from the 2026-10-04 consumer-workaround retirement sweep (campus-profile#31, campus-classroom#33, campus-cli#29 retired the connections/broker/revoke workarounds from #71–#73). What cannot be retired in consumers yet:
Refresh-token grant for public clients. campus-cli's refresh_access_token (campus_cli/auth/common.py:190-249 @ 7908a3e) still raw-POSTs {auth_url}/oauth/token with grant_type=refresh_token. The grant exists server-side and the auth root's token() accepts it (server-mode _get_token_from_session uses it), but a public client that keeps its own credential store has no library helper of the form "take this stored token, return the refreshed pair".
Device-flow parity. campus-cli's device flow (campus_cli/auth/login.py:41-182) is still raw requests with bespoke handling the library must match before the CLI can delete it: Campus's structured error envelope {"error": {code, message, details.oauth_error}} with flat-OAuth fallback; RFC 8628 §3.5 slow_down (+5s persisted interval); authorization_pending/expired_token/access_denied mapping; network retry on non-final poll attempts. request_device_code()/poll_for_token() exist since fix: bug sweep — timetable envelopes, session cleanup, oauth device flow #70 — migration should start with a parity check of poll_for_token against that list.
(Smaller) JsonClient-level auto-refresh on 401 would let user-session apps stop depending on proactive is_expired() checks (campus-classroom now leans on with_user_session(refresh_if_expired=True); a mid-request 401 after the expiry skew still surfaces to the app).
Suggested shape
auth.refresh(stored: OAuthToken) -> OAuthToken (or a pluggable token-store hook on Campus(mode="device")) for (1).
A documented parity table (error mapping + slow_down semantics) for (2), then retire the CLI's login.py copies.
Consumer context: campus-cli#29 kept revoke_token best-effort-degradable to False; the same pattern will apply to the refresh/device migrations.
Follow-up from the 2026-10-04 consumer-workaround retirement sweep (campus-profile#31, campus-classroom#33, campus-cli#29 retired the connections/broker/revoke workarounds from #71–#73). What cannot be retired in consumers yet:
Refresh-token grant for public clients. campus-cli's
refresh_access_token(campus_cli/auth/common.py:190-249@ 7908a3e) still raw-POSTs{auth_url}/oauth/tokenwithgrant_type=refresh_token. The grant exists server-side and the auth root'stoken()accepts it (server-mode_get_token_from_sessionuses it), but a public client that keeps its own credential store has no library helper of the form "take this stored token, return the refreshed pair".Device-flow parity. campus-cli's device flow (
campus_cli/auth/login.py:41-182) is still rawrequestswith bespoke handling the library must match before the CLI can delete it: Campus's structured error envelope{"error": {code, message, details.oauth_error}}with flat-OAuth fallback; RFC 8628 §3.5slow_down(+5s persisted interval);authorization_pending/expired_token/access_deniedmapping; network retry on non-final poll attempts.request_device_code()/poll_for_token()exist since fix: bug sweep — timetable envelopes, session cleanup, oauth device flow #70 — migration should start with a parity check ofpoll_for_tokenagainst that list.(Smaller) JsonClient-level auto-refresh on 401 would let user-session apps stop depending on proactive
is_expired()checks (campus-classroom now leans onwith_user_session(refresh_if_expired=True); a mid-request 401 after the expiry skew still surfaces to the app).Suggested shape
auth.refresh(stored: OAuthToken) -> OAuthToken(or a pluggable token-store hook onCampus(mode="device")) for (1).login.pycopies.Consumer context: campus-cli#29 kept
revoke_tokenbest-effort-degradable toFalse; the same pattern will apply to the refresh/device migrations.