AppAuth-Android icon indicating copy to clipboard operation
AppAuth-Android copied to clipboard

Device Authorization Grant support

Open solevic opened this issue 3 years ago • 8 comments

Feature Request

1. Motivation

AppAuth-Android does not currently support authentication for Android devices that either lack a browser or have limited input capabilities to fully perform the traditional authentication flow. Adding support of the extension Device Authorization Grant as described in RFC 8628 would allow such devices to obtain tokens from the authorization server with the help of a secondary device with browser and common input capabilities.

2. Description

As described in Section 1 (RFC 8628), such an implementation would require the addition of a request to perform a device authorization and a polling mechanism for the token request.

2.1 Device authorization

A request such as DeviceAuthorizationRequest could be created with the specifications in Section 3.1 (RFC 8628) and the associated response such as DeviceAuthorizationResponse with the specifications in Section 3.2 (RFC 8628).

To perform this request, an additional endpoint device_authorization_endpoint described in Section 4 (RFC 8628) would have to be added to the AuthorizationServiceDiscovery.

The request would be performed in an AsyncTask such as DeviceAuthorizationRequestTask and would expose the reponse through a callback such as DeviceAuthorizationResponseCallback.

public interface DeviceAuthorizationResponseCallback {

    void onDeviceAuthorizationRequestCompleted(@Nullable DeviceAuthorizationResponse response,
                                               @Nullable AuthorizationException ex);

}

No additional exceptions should be created for this request as the authorization server should respond in the same way as the token endpoint as described in the last paragraph of Section 3.2 (RFC 8628). The choice of an AsyncTask here would be to match the existing implementation of other requests.

To save the new authorization state, AuthState should implement an additional update method to save the last DeviceAuthorizationResponse. The last response will be used to expose parameters such as verification uri, complete verification uri and user code through get accessors as these will be required to perform the authorization request on the secondary device.

The DeviceAuthorizationReponse would also allow for the creation of a TokenRequest in a similar manner that the AuthorizationReponse already does through a member function such as:

public class DeviceAuthorizationReponse {

    public final DeviceAuthorizationRequest request;
    public final String deviceCode;

    public TokenRequest createTokenExchangeRequest() {
        if (deviceCode == null) {
            throw new IllegalStateException("deviceCode not available for exchange request");
        }

        return new TokenRequest.Builder(request.configuration, request.clientId)
            .setGrantType(GrantTypeValues.DEVICE_CODE)
            .setDeviceCode(deviceCode)
            .build();
    }

} 

An additional method should also be provided to allow for the creation of a TokenRequest with non-documented additional parameters.

The TokenRequest would implement the additional parameter deviceCode to allow operations through the extension's grant type urn:ietf:params:oauth:grant-type:device_code described in the Section 3.4 (RFC 8628).

2.2 Polling mechanism

Right after the application obtained and displayed the information necessary to perform the authorization on a secondary device, it should start polling for an access token associated to the previously obtained deviceCode in the DeviceAuthorizationReponse with the provided TokenRequest.

It would be handy if the AuthorizationService offered an API to perform the polling itself.

class AuthorizationService {

    public void performTokenPollRequest(
            @NonNull TokenRequest request,
            @Nullable Long pollingInterval,
            @NonNull Long expirationTime,
            @NonNull TokenResponseCallback callback)

}

This API would instanciate and execute an AsyncTask such as TokenRequestPollingTask that would inherit from the existing TokenRequestTask. This task would perform the polling loop in its doInBackground method while calling the super class' doInBackground to execute the provided TokenRequest. The result of this request would be parsed in the loop to interpret the errors described in Section 3.5 (RFC 8628) and decide whether to continue the polling, expand the polling interval, abort due to a critical error or due to the expiration of the device code. A set of additional AuthorizationException described in the extension should also be implemented.

The issue with this approach is the need to have access to the provided polling interval and device code expiration time that are not stored in the TokenRequest. A solution to this would be to provide a helper method in AuthState that would call the AuthorizationService polling method with the parameters provided by the last DeviceAuthorizationReponse.

public class AuthState {

    private DeviceAuthorizationResponse mLastDeviceAuthorizationResponse;

    public void performTokenPollRequest(@NonNull final AuthorizationService service,
                                        @NonNull final ClientAuthentication clientAuth,
                                        @NonNull final AuthorizationService.TokenResponseCallback callback) {
        if (mLastDeviceAuthorizationResponse == null) {
            AuthorizationException ex = AuthorizationException.fromTemplate(
                AuthorizationException.TokenRequestErrors.CLIENT_ERROR,
                new IllegalStateException("No device authorization available for token request"));
            callback.onTokenRequestCompleted(null, ex);
            return;
        }

        service.performTokenPollRequest(mLastDeviceAuthorizationResponse.createTokenExchangeRequest(),
            clientAuth,
            mLastDeviceAuthorizationResponse.tokenPollingIntervalTime,
            mLastDeviceAuthorizationResponse.codeExpirationTime,
            callback);
    }

}

Additional methods should also be provided to allow for the creation of the TokenRequest with non-documented additional parameters and to allow the polling without explicitly providing a ClientAuthentication.

A thead-safe method for canceling the polling request should also be provided.

Upon a successful TokenRequest, the app should update the AuthState with the provided TokenResponse in onTokenRequestCompleted.

3. Alternatives or Workarounds

There is currently no simple way of performing a device authorization.

I already have a draft of the implementation described above, but would love your input on my take. Let me know if you are interested in me providing a merge request.

solevic avatar Mar 18 '21 17:03 solevic

Is there any progress with device authorization? @solevic do you have some solution? Thanks.

drdla49 avatar Nov 11 '21 12:11 drdla49

Thank you @solevic! This is exactly what I am in need of.

I hope this can be merged quite quickly.

Healios avatar Nov 15 '21 12:11 Healios

Is there any news about this merge request? Do you plan to add it in the near future?

rodrigoGA avatar Mar 04 '22 15:03 rodrigoGA

Is there any news about this merge request? Do you plan to add it in the near future?

Not a maintainer, but I wouldn't expect it to get merged anytime soon. On the "surface" the request seems perfect though, only thing I found was a simple spelling error.

I forked the repository and merged the request back in november (see my fork), and it's worked really well for me. I'm using my fork via JitPack.

Healios avatar Mar 04 '22 17:03 Healios

@Healios and @solevic I thank you very much

rodrigoGA avatar Mar 04 '22 18:03 rodrigoGA

@Healios I am trying to use your library. But I can't import it. This is the jitpack bug report the master branch

I am importing it in this way implementation 'com.github.formula-micro:AppAuth-Android:master-SNAPSHOT'

rodrigoGA avatar Mar 08 '22 18:03 rodrigoGA

@rodrigoGA I'm using: implementation "com.github.formula-micro:AppAuth-Android:-SNAPSHOT"

Healios avatar Mar 08 '22 18:03 Healios

@Healios I love you

rodrigoGA avatar Mar 08 '22 19:03 rodrigoGA