Skip to content

Commit 93d3100

Browse files
authored
feat: Add Token Vault support to the Authentication API (#894)
1 parent 779d500 commit 93d3100

2 files changed

Lines changed: 126 additions & 0 deletions

File tree

src/main/java/com/auth0/client/auth/AuthAPI.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public class AuthAPI {
5959
private static final String KEY_CLIENT_ASSERTION_TYPE = "client_assertion_type";
6060
private static final String KEY_SUBJECT_TOKEN = "subject_token";
6161
private static final String KEY_SUBJECT_TOKEN_TYPE = "subject_token_type";
62+
private static final String KEY_REQUESTED_TOKEN_TYPE = "requested_token_type";
63+
private static final String KEY_LOGIN_HINT = "login_hint";
6264
private static final String PATH_OAUTH = "oauth";
6365
private static final String PATH_TOKEN = "token";
6466
private static final String PATH_DBCONNECTIONS = "dbconnections";
@@ -840,6 +842,54 @@ public TokenRequest exchangeToken(String subjectToken, String subjectTokenType)
840842
return request;
841843
}
842844

845+
/**
846+
* Creates a request to exchange an Auth0 refresh token for a federated identity provider's access token
847+
* using the Token Vault grant
848+
* {@code urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token}.
849+
* The connection must have the token vault enabled, and client authentication
850+
* (client secret or client assertion) is required as this must be a private client.
851+
* <pre>
852+
* {@code
853+
* try {
854+
* TokenHolder result = authAPI.getTokenForConnection("google-oauth2", refreshToken, "google-user-id")
855+
* .execute()
856+
* .getBody();
857+
* String federatedAccessToken = result.getAccessToken();
858+
* } catch (Auth0Exception e) {
859+
* //Something happened
860+
* }
861+
* }
862+
* </pre>
863+
*
864+
* @see <a href="https://auth0.com/docs/secure/tokens/token-vault">Token Vault documentation</a>
865+
* @param connection the name of the federated connection to obtain an access token for
866+
* (for example {@code google-oauth2}). Must not be null.
867+
* @param refreshToken a valid Auth0 refresh token to exchange. Must not be null.
868+
* @param loginHint the user's ID within the identity provider specified by the connection
869+
* (for example, the Google user ID when the connection is {@code google-oauth2}).
870+
* May be null, in which case no {@code login_hint} is sent.
871+
* @return a Request to configure and execute.
872+
*/
873+
public TokenRequest getTokenForConnection(String connection, String refreshToken, String loginHint) {
874+
Asserts.assertNotNull(connection, "connection");
875+
Asserts.assertNotNull(refreshToken, "refresh token");
876+
877+
TokenRequest request = new TokenRequest(client, getTokenUrl());
878+
request.addParameter(KEY_CLIENT_ID, clientId);
879+
request.addParameter(
880+
KEY_GRANT_TYPE, "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token");
881+
request.addParameter(KEY_SUBJECT_TOKEN, refreshToken);
882+
request.addParameter(KEY_SUBJECT_TOKEN_TYPE, "urn:ietf:params:oauth:token-type:refresh_token");
883+
request.addParameter(
884+
KEY_REQUESTED_TOKEN_TYPE, "http://auth0.com/oauth/token-type/federated-connection-access-token");
885+
request.addParameter(KEY_CONNECTION, connection);
886+
if (loginHint != null) {
887+
request.addParameter(KEY_LOGIN_HINT, loginHint);
888+
}
889+
addClientAuthentication(request, true);
890+
return request;
891+
}
892+
843893
/**
844894
* Creates a request to revoke an existing Refresh Token.
845895
* Confidential clients (Regular Web Apps) <strong>must</strong> have a client secret configured on this {@code AuthAPI} instance.

src/test/java/com/auth0/client/auth/AuthAPITest.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,82 @@ public void shouldCreateTokenExchangeRequestWithClientAssertion() throws Excepti
10351035
assertThat(response.getAccessToken(), not(emptyOrNullString()));
10361036
}
10371037

1038+
// Token Vault - Federated Connection Access Token
1039+
1040+
@Test
1041+
public void shouldThrowOnGetTokenForConnectionWithNullConnection() {
1042+
verifyThrows(
1043+
IllegalArgumentException.class,
1044+
() -> api.getTokenForConnection(null, "test-refresh-token", null),
1045+
"'connection' cannot be null!");
1046+
}
1047+
1048+
@Test
1049+
public void shouldThrowOnGetTokenForConnectionWithNullRefreshToken() {
1050+
verifyThrows(
1051+
IllegalArgumentException.class,
1052+
() -> api.getTokenForConnection("google-oauth2", null, null),
1053+
"'refresh token' cannot be null!");
1054+
}
1055+
1056+
@Test
1057+
public void shouldCreateGetTokenForConnectionRequest() throws Exception {
1058+
TokenRequest request = api.getTokenForConnection("google-oauth2", "test-refresh-token", null);
1059+
assertThat(request, is(notNullValue()));
1060+
1061+
server.jsonResponse(AUTH_TOKENS, 200);
1062+
TokenHolder response = request.execute().getBody();
1063+
RecordedRequest recordedRequest = server.takeRequest();
1064+
1065+
assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token"));
1066+
assertThat(recordedRequest, hasHeader("Content-Type", "application/json"));
1067+
1068+
Map<String, Object> body = bodyFromRequest(recordedRequest);
1069+
assertThat(
1070+
body,
1071+
hasEntry(
1072+
"grant_type",
1073+
"urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token"));
1074+
assertThat(body, hasEntry("client_id", CLIENT_ID));
1075+
assertThat(body, hasEntry("client_secret", CLIENT_SECRET));
1076+
assertThat(body, hasEntry("subject_token", "test-refresh-token"));
1077+
assertThat(body, hasEntry("subject_token_type", "urn:ietf:params:oauth:token-type:refresh_token"));
1078+
assertThat(
1079+
body,
1080+
hasEntry(
1081+
"requested_token_type", "http://auth0.com/oauth/token-type/federated-connection-access-token"));
1082+
assertThat(body, hasEntry("connection", "google-oauth2"));
1083+
assertThat(body, not(hasKey("login_hint")));
1084+
1085+
assertThat(response, is(notNullValue()));
1086+
assertThat(response.getAccessToken(), not(emptyOrNullString()));
1087+
}
1088+
1089+
@Test
1090+
public void shouldCreateGetTokenForConnectionRequestWithLoginHint() throws Exception {
1091+
TokenRequest request = api.getTokenForConnection("google-oauth2", "test-refresh-token", "google-user-id");
1092+
assertThat(request, is(notNullValue()));
1093+
1094+
server.jsonResponse(AUTH_TOKENS, 200);
1095+
TokenHolder response = request.execute().getBody();
1096+
RecordedRequest recordedRequest = server.takeRequest();
1097+
1098+
Map<String, Object> body = bodyFromRequest(recordedRequest);
1099+
assertThat(body, hasEntry("connection", "google-oauth2"));
1100+
assertThat(body, hasEntry("login_hint", "google-user-id"));
1101+
1102+
assertThat(response, is(notNullValue()));
1103+
assertThat(response.getAccessToken(), not(emptyOrNullString()));
1104+
}
1105+
1106+
@Test
1107+
public void getTokenForConnectionRequiresClientAuthentication() {
1108+
verifyThrows(
1109+
IllegalStateException.class,
1110+
() -> apiNoClientAuthentication.getTokenForConnection("google-oauth2", "test-refresh-token", null),
1111+
"A client secret or client assertion signing key is required for this operation");
1112+
}
1113+
10381114
// Login with Passwordless
10391115

10401116
@Test

0 commit comments

Comments
 (0)