diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java index acdb3c83..342e70ca 100644 --- a/src/main/java/com/auth0/client/auth/AuthAPI.java +++ b/src/main/java/com/auth0/client/auth/AuthAPI.java @@ -59,6 +59,8 @@ public class AuthAPI { private static final String KEY_CLIENT_ASSERTION_TYPE = "client_assertion_type"; private static final String KEY_SUBJECT_TOKEN = "subject_token"; private static final String KEY_SUBJECT_TOKEN_TYPE = "subject_token_type"; + private static final String KEY_REQUESTED_TOKEN_TYPE = "requested_token_type"; + private static final String KEY_LOGIN_HINT = "login_hint"; private static final String PATH_OAUTH = "oauth"; private static final String PATH_TOKEN = "token"; private static final String PATH_DBCONNECTIONS = "dbconnections"; @@ -840,6 +842,54 @@ public TokenRequest exchangeToken(String subjectToken, String subjectTokenType) return request; } + /** + * Creates a request to exchange an Auth0 refresh token for a federated identity provider's access token + * using the Token Vault grant + * {@code urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token}. + * The connection must have the token vault enabled, and client authentication + * (client secret or client assertion) is required as this must be a private client. + *
+     * {@code
+     * try {
+     *      TokenHolder result = authAPI.getTokenForConnection("google-oauth2", refreshToken, "google-user-id")
+     *          .execute()
+     *          .getBody();
+     *      String federatedAccessToken = result.getAccessToken();
+     * } catch (Auth0Exception e) {
+     *      //Something happened
+     * }
+     * }
+     * 
+ * + * @see Token Vault documentation + * @param connection the name of the federated connection to obtain an access token for + * (for example {@code google-oauth2}). Must not be null. + * @param refreshToken a valid Auth0 refresh token to exchange. Must not be null. + * @param loginHint the user's ID within the identity provider specified by the connection + * (for example, the Google user ID when the connection is {@code google-oauth2}). + * May be null, in which case no {@code login_hint} is sent. + * @return a Request to configure and execute. + */ + public TokenRequest getTokenForConnection(String connection, String refreshToken, String loginHint) { + Asserts.assertNotNull(connection, "connection"); + Asserts.assertNotNull(refreshToken, "refresh token"); + + TokenRequest request = new TokenRequest(client, getTokenUrl()); + request.addParameter(KEY_CLIENT_ID, clientId); + request.addParameter( + KEY_GRANT_TYPE, "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token"); + request.addParameter(KEY_SUBJECT_TOKEN, refreshToken); + request.addParameter(KEY_SUBJECT_TOKEN_TYPE, "urn:ietf:params:oauth:token-type:refresh_token"); + request.addParameter( + KEY_REQUESTED_TOKEN_TYPE, "http://auth0.com/oauth/token-type/federated-connection-access-token"); + request.addParameter(KEY_CONNECTION, connection); + if (loginHint != null) { + request.addParameter(KEY_LOGIN_HINT, loginHint); + } + addClientAuthentication(request, true); + return request; + } + /** * Creates a request to revoke an existing Refresh Token. * Confidential clients (Regular Web Apps) must have a client secret configured on this {@code AuthAPI} instance. diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index 8516480c..7278a80e 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -1035,6 +1035,82 @@ public void shouldCreateTokenExchangeRequestWithClientAssertion() throws Excepti assertThat(response.getAccessToken(), not(emptyOrNullString())); } + // Token Vault - Federated Connection Access Token + + @Test + public void shouldThrowOnGetTokenForConnectionWithNullConnection() { + verifyThrows( + IllegalArgumentException.class, + () -> api.getTokenForConnection(null, "test-refresh-token", null), + "'connection' cannot be null!"); + } + + @Test + public void shouldThrowOnGetTokenForConnectionWithNullRefreshToken() { + verifyThrows( + IllegalArgumentException.class, + () -> api.getTokenForConnection("google-oauth2", null, null), + "'refresh token' cannot be null!"); + } + + @Test + public void shouldCreateGetTokenForConnectionRequest() throws Exception { + TokenRequest request = api.getTokenForConnection("google-oauth2", "test-refresh-token", null); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat( + body, + hasEntry( + "grant_type", + "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("subject_token", "test-refresh-token")); + assertThat(body, hasEntry("subject_token_type", "urn:ietf:params:oauth:token-type:refresh_token")); + assertThat( + body, + hasEntry( + "requested_token_type", "http://auth0.com/oauth/token-type/federated-connection-access-token")); + assertThat(body, hasEntry("connection", "google-oauth2")); + assertThat(body, not(hasKey("login_hint"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + } + + @Test + public void shouldCreateGetTokenForConnectionRequestWithLoginHint() throws Exception { + TokenRequest request = api.getTokenForConnection("google-oauth2", "test-refresh-token", "google-user-id"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("connection", "google-oauth2")); + assertThat(body, hasEntry("login_hint", "google-user-id")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + } + + @Test + public void getTokenForConnectionRequiresClientAuthentication() { + verifyThrows( + IllegalStateException.class, + () -> apiNoClientAuthentication.getTokenForConnection("google-oauth2", "test-refresh-token", null), + "A client secret or client assertion signing key is required for this operation"); + } + // Login with Passwordless @Test