feat: add Device Authorization Flow to the Authentication API - #794
feat: add Device Authorization Flow to the Authentication API#794gagalago wants to merge 3 commits into
Conversation
56cf804 to
39cfd27
Compare
39cfd27 to
dc54547
Compare
Document that device flow runs as a public client and deliberately sends no client_secret or client assertion, and pin that in both tests so a later change to the auth behaviour cannot pass unnoticed. Back off by 5 seconds on slow_down in the polling example, per RFC 8628 section 3.5, and include verification_uri_complete in the stubbed device code response so it matches what Auth0 actually returns.
The two device tests only covered a client configured with a secret. Mirror the _with_client_secret / _with_client_assertion pair the sibling token methods use, so a configured signing key is proven not to be sent on either device endpoint.
|
All four fair Auth0 refuses device flow if you send credentials: /oauth/device/code returns 200 with just client_id/audience/scope, and 403 unauthorized_client the moment a client_secret is added. So calling the helper here would break any client that has a secret configured. Both methods now say that, and both tests assert no client_secret or client_assertion reaches the body — including on a signing-key client, mirroring the _with_client_secret / _with_client_assertion pairing you pointed at. And verification_uri_complete is in the stub and asserted now — real responses do include it. |
Changes
Implements the Device Authorization Flow in
Auth0::Api::AuthenticationEndpoints. This is the proposal from #792, opened as a pull request per the general contributing guidelines ("PRs to our libraries are always welcome"); happy to close it if you would rather settle the design on the issue first.Two new methods, additive only — no existing method, endpoint or signature changes:
start_device_flow(scope:, audience:, client_id:)→POST /oauth/device/code, returning the parsed body (device_code,user_code,verification_uri,verification_uri_complete,expires_in,interval).exchange_device_code_for_tokens(device_code, client_id:)→POST /oauth/tokenwithgrant_type=urn:ietf:params:oauth:grant-type:device_code, returning anAuth0::AccessToken.They are named after the existing
start_passwordless_sms_flow/exchange_sms_otp_for_tokenspair so they read like their neighbours, and they follow the conventions already in the file: anAuth0::InvalidParameterguard on the required positional argument, arequest_paramshash,request_with_retry, and::Auth0::AccessToken.from_responsefor the token response.Note this is unrelated to
Auth0::Api::V2::DeviceCredentials, which is the Management API device-credentials resource.No new exception classes. While the user completes their part, Auth0 answers with an HTTP error whose body carries an
errorofauthorization_pendingorslow_down.Mixins::HTTPProxy#requestalready raisesAuth0::HTTPErrorsubclasses with the raw response body as the message —AccessDeniedfor 403 andBadRequestfor 400, bothHTTPError— so callers can distinguish the polling states with what is already there. A documented accessor for thaterrorcode would be a good follow-up, but it is deliberately not in this pull request.References
For prior art inside the SDK family, Auth0.NET exposes the same two operations as
StartDeviceFlowAsync(DeviceCodeRequest)andGetTokenAsync(DeviceCodeTokenRequest)onIAuthenticationApiClient.Motivation: we run a small Rails service that acts as an OAuth proxy for our command-line client. It starts the flow, hands
user_codeandverification_urito the CLI, and polls for the token. With this in place we can delete the hand-written Faraday client that exists only because the flow is not reachable from this SDK.Testing
Three cases added to
test/unit/authentication_endpoints_test.rb, alongside the existing ones and using the same WebMock body-matching style:test_start_device_flow_requests_a_device_code— assertsclient_id,scopeandaudienceare sent to/oauth/device/code, and that the response fields come backtest_exchange_device_code_for_tokens— asserts theurn:ietf:params:oauth:grant-type:device_codegrant type anddevice_codeare sent, and that anAuth0::AccessTokenis returnedtest_exchange_device_code_for_tokens_raises_on_empty— theInvalidParameterguardI checked the tests are not vacuous: pointing
start_device_flowat a different path makes its test fail, and restoring it makes it pass again.555 runs, 4585 assertions, 0 failures, 0 errors555 runs, 0 failures, 1 error— the pre-existingCGI.parsefailure on master, unrelated to this change and fixed by #793The three new tests pass on both (
3 runs, 7 assertions, 0 failures).bundle exec rubocop --force-exclusionreports no offences on the changed files. As with the rest ofauthentication_endpoints.rb, I followed the file's single-quoted style — it is listed underAllCops.Excludein.rubocop.ymlas ported verbatim from the legacy SDK.Documentation
Added a Device Authorization Flow section to
EXAMPLES.md, showing the full loop: start the flow, display the user code, poll on the returnedinterval, and treatauthorization_pending/slow_downas continue-conditions.Checklist