diff --git a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.java b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.java index a79b57524..024ee3c83 100755 --- a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.java +++ b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.java @@ -78,6 +78,9 @@ public class AuthenticationAPIClient { private static final String PASSWORD_KEY = "password"; private static final String EMAIL_KEY = "email"; private static final String PHONE_NUMBER_KEY = "phone_number"; + private static final String OAUTH_CODE_KEY = "code"; + private static final String REDIRECT_URI_KEY = "redirect_uri"; + private static final String TOKEN_KEY = "token"; private static final String DELEGATION_PATH = "delegation"; private static final String ACCESS_TOKEN_PATH = "access_token"; private static final String SIGN_UP_PATH = "signup"; @@ -90,8 +93,7 @@ public class AuthenticationAPIClient { private static final String RESOURCE_OWNER_PATH = "ro"; private static final String TOKEN_INFO_PATH = "tokeninfo"; private static final String USER_INFO_PATH = "userinfo"; - private static final String OAUTH_CODE_KEY = "code"; - private static final String REDIRECT_URI_KEY = "redirect_uri"; + private static final String REVOKE_PATH = "revoke"; private static final String HEADER_AUTHORIZATION = "Authorization"; private final Auth0 auth0; @@ -617,6 +619,43 @@ public DatabaseConnectionRequest resetPassword(@N return new DatabaseConnectionRequest<>(request); } + /** + * Request the revoke of a given refresh_token. Once revoked, the refresh_token cannot be used to obtain new tokens. + * The client must be of type 'Native' or have the 'Token Endpoint Authentication Method' set to 'none' for this endpoint to work. + * + * Example usage: + *
+     * {@code
+     * client.revokeToken("{refresh_token}")
+     *      .start(new BaseCallback() {
+     *          {@literal}Override
+     *          public void onSuccess(Void payload) {}
+     *
+     *          {@literal}Override
+     *          public void onFailure(AuthenticationException error) {}
+     *      });
+     * }
+     * 
+ * + * @param refreshToken the token to revoke + * @return a request to start + */ + @SuppressWarnings("WeakerAccess") + public Request revokeToken(@NonNull String refreshToken) { + final Map parameters = ParameterBuilder.newBuilder() + .setClientId(getClientId()) + .set(TOKEN_KEY, refreshToken) + .asDictionary(); + + HttpUrl url = HttpUrl.parse(auth0.getDomainUrl()).newBuilder() + .addPathSegment(OAUTH_PATH) + .addPathSegment(REVOKE_PATH) + .build(); + + return factory.POST(url, client, gson, authErrorBuilder) + .addParameters(parameters); + } + /** * Requests new Credentials using a valid Refresh Token. The received token will have the same audience and scope as first requested. How the new Credentials are requested depends on the {@link Auth0#isOIDCConformant()} flag. * - If the instance is OIDC Conformant the endpoint will be /oauth/token with 'refresh_token' grant, and the response will include an id_token and an access_token if 'openid' scope was requested when the refresh_token was obtained. diff --git a/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.java b/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.java index 421c6c64d..8b08bc5f0 100755 --- a/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.java +++ b/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.java @@ -1573,6 +1573,46 @@ public void shouldFetchProfileAfterLoginRequest() throws Exception { assertThat(callback, hasPayloadOfType(Authentication.class)); } + + @Test + public void shouldRevokeToken() throws Exception { + Auth0 auth0 = new Auth0(CLIENT_ID, mockAPI.getDomain(), mockAPI.getDomain()); + AuthenticationAPIClient client = new AuthenticationAPIClient(auth0); + + mockAPI.willReturnSuccessfulEmptyBody(); + final MockAuthenticationCallback callback = new MockAuthenticationCallback<>(); + client.revokeToken("refreshToken") + .start(callback); + + final RecordedRequest request = mockAPI.takeRequest(); + assertThat(request.getHeader("Accept-Language"), is(getDefaultLocale())); + assertThat(request.getPath(), equalTo("/oauth/revoke")); + + Map body = bodyFromRequest(request); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("token", "refreshToken")); + + assertThat(callback, hasNoError()); + } + + @Test + public void shouldRevokeTokenSync() throws Exception { + Auth0 auth0 = new Auth0(CLIENT_ID, mockAPI.getDomain(), mockAPI.getDomain()); + AuthenticationAPIClient client = new AuthenticationAPIClient(auth0); + + mockAPI.willReturnSuccessfulEmptyBody(); + client.revokeToken("refreshToken") + .execute(); + + final RecordedRequest request = mockAPI.takeRequest(); + assertThat(request.getHeader("Accept-Language"), is(getDefaultLocale())); + assertThat(request.getPath(), equalTo("/oauth/revoke")); + + Map body = bodyFromRequest(request); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("token", "refreshToken")); + } + @Test public void shouldRenewAuthWithOAuthTokenIfOIDCConformant() throws Exception { Auth0 auth0 = new Auth0(CLIENT_ID, mockAPI.getDomain(), mockAPI.getDomain()); diff --git a/auth0/src/test/java/com/auth0/android/util/AuthenticationAPI.java b/auth0/src/test/java/com/auth0/android/util/AuthenticationAPI.java index 26a9e14ff..c98139356 100755 --- a/auth0/src/test/java/com/auth0/android/util/AuthenticationAPI.java +++ b/auth0/src/test/java/com/auth0/android/util/AuthenticationAPI.java @@ -106,6 +106,11 @@ public AuthenticationAPI willReturnSuccessfulSignUp() { return this; } + public AuthenticationAPI willReturnSuccessfulEmptyBody() { + server.enqueue(responseEmpty(200)); + return this; + } + public AuthenticationAPI willReturnSuccessfulLogin() { String json = "{\n" + " \"refresh_token\": \"" + REFRESH_TOKEN + "\",\n" + @@ -175,7 +180,12 @@ public AuthenticationAPI willReturnApplicationResponseWithBody(String body, int return this; } - private MockResponse responseWithPlainText(String statusMessage, int statusCode){ + private MockResponse responseEmpty(int statusCode) { + return new MockResponse() + .setResponseCode(statusCode); + } + + private MockResponse responseWithPlainText(String statusMessage, int statusCode) { return new MockResponse() .setResponseCode(statusCode) .addHeader("Content-Type", "text/plain")