Skip to content

Commit 2451a78

Browse files
[SDK-3746] Add support for MFA APIs (#505)
* start of mfa support * add more MFA endpoint support * comment cleanup Co-authored-by: Jim Anderson <jimranderson@gmail.com>
1 parent d4e0120 commit 2451a78

11 files changed

Lines changed: 715 additions & 14 deletions

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

Lines changed: 247 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package com.auth0.client.auth;
22

33
import com.auth0.client.mgmt.ManagementAPI;
4-
import com.auth0.json.auth.PasswordlessEmailResponse;
5-
import com.auth0.json.auth.PasswordlessSmsResponse;
6-
import com.auth0.json.auth.UserInfo;
4+
import com.auth0.json.auth.*;
75
import com.auth0.net.*;
86
import com.auth0.net.client.Auth0HttpClient;
97
import com.auth0.net.client.DefaultHttpClient;
@@ -14,6 +12,8 @@
1412
import okhttp3.OkHttpClient;
1513
import org.jetbrains.annotations.TestOnly;
1614

15+
import java.util.Collections;
16+
import java.util.List;
1717
import java.util.Objects;
1818

1919
/**
@@ -896,6 +896,250 @@ public TokenRequest exchangeMfaOtp(String mfaToken, char[] otp) {
896896
return request;
897897
}
898898

899+
/**
900+
* Creates a request to exchange the mfa token and an out-of-band (OOB) challenge (either Push notification, SMS, or Voice).
901+
* Confidential clients (Regular Web Apps) <strong>must</strong> have a client secret configured on this {@code AuthAPI} instance.
902+
* <pre>
903+
* {@code
904+
* try {
905+
* TokenHolder result = authAPI.exchangeMfaOob("the-mfa-token", new char[]{'a','n','o','t','p'}, new char[]{'b','i','n','d','c','o','d','e'})
906+
* .execute()
907+
* .getBody();
908+
* } catch (Auth0Exception e) {
909+
* //Something happened
910+
* }
911+
* }
912+
* </pre>
913+
*
914+
* @param mfaToken the mfa_token received from the mfa_required error that occurred during login. Must not be null.
915+
* @param oobCode the OOB Code provided by the user. Must not be null.
916+
* @param bindingCode A code used to bind the side channel (used to deliver the challenge) with the main channel you are using to authenticate. This is usually an OTP-like code delivered as part of the challenge message. May be null.
917+
*
918+
* @return a Request to configure and execute.
919+
*
920+
* @see <a href="https://auth0.com/docs/api/authentication#verify-with-out-of-band-oob-">Verify with Out-of-band (OOB) API documentation</a>
921+
*/
922+
public TokenRequest exchangeMfaOob(String mfaToken, char[] oobCode, char[] bindingCode) {
923+
Asserts.assertNotNull(mfaToken, "mfa token");
924+
Asserts.assertNotNull(oobCode, "OOB code");
925+
926+
TokenRequest request = new TokenRequest(client, getTokenUrl());
927+
request.addParameter(KEY_CLIENT_ID, clientId);
928+
request.addParameter(KEY_GRANT_TYPE, "http://auth0.com/oauth/grant-type/mfa-oob");
929+
request.addParameter(KEY_MFA_TOKEN, mfaToken);
930+
request.addParameter("oob_code", oobCode);
931+
932+
if (Objects.nonNull(bindingCode) && bindingCode.length > 0) {
933+
request.addParameter("binding_code", bindingCode);
934+
}
935+
936+
addSecret(request, false);
937+
return request;
938+
}
939+
940+
/**
941+
* Creates a request to exchange the mfa token using a recovery code.
942+
* Confidential clients (Regular Web Apps) <strong>must</strong> have a client secret configured on this {@code AuthAPI} instance.
943+
* <pre>
944+
* {@code
945+
* try {
946+
* TokenHolder result = authAPI.exchangeMfaRecoveryCode("the-mfa-token", new char[]{'c','o','d','e'})
947+
* .execute()
948+
* .getBody();
949+
* } catch (Auth0Exception e) {
950+
* //Something happened
951+
* }
952+
* }
953+
* </pre>
954+
*
955+
* @param mfaToken the mfa_token received from the mfa_required error that occurred during login. Must not be null.
956+
* @param recoveryCode the recovery code provided by the user. Must not be null.
957+
* @return a Request to configure and execute.
958+
*
959+
* @see <a href="https://auth0.com/docs/api/authentication#verify-with-recovery-code">Verify with a recovery code API documentation</a>
960+
*/
961+
public TokenRequest exchangeMfaRecoveryCode(String mfaToken, char[] recoveryCode) {
962+
Asserts.assertNotNull(mfaToken, "mfa token");
963+
Asserts.assertNotNull(recoveryCode, "recovery code");
964+
965+
TokenRequest request = new TokenRequest(client, getTokenUrl());
966+
request.addParameter(KEY_CLIENT_ID, clientId);
967+
request.addParameter(KEY_GRANT_TYPE, "http://auth0.com/oauth/grant-type/mfa-recovery-code");
968+
request.addParameter(KEY_MFA_TOKEN, mfaToken);
969+
request.addParameter("recovery_code", recoveryCode);
970+
971+
addSecret(request, false);
972+
return request;
973+
}
974+
975+
/**
976+
* Request a challenge for multi-factor authentication (MFA) based on the challenge types supported by the application and user.
977+
* Confidential clients (Regular Web Apps) <strong>must</strong> have a client secret configured on this {@code AuthAPI} instance.
978+
* <pre>
979+
* {@code
980+
* try {
981+
* MfaChallengeResponse result = authAPI.mfaChallengeRequest("the-mfa-token", "otp", "authenticator-id")
982+
* .execute()
983+
* .getBody();
984+
* } catch (Auth0Exception e) {
985+
* //Something happened
986+
* }
987+
* }
988+
* </pre>
989+
*
990+
* @param mfaToken The token received from mfa_required error. Must not be null.
991+
* @param challengeType A whitespace-separated list of the challenges types accepted by your application.
992+
* @param authenticatorId The ID of the authenticator to challenge.
993+
* @return a Request to execute.
994+
* @see <a href="https://auth0.com/docs/api/authentication#challenge-request">Challenge Request API documentation</a>
995+
*/
996+
public Request<MfaChallengeResponse> mfaChallengeRequest(String mfaToken, String challengeType, String authenticatorId) {
997+
Asserts.assertNotNull(mfaToken, "mfa token");
998+
999+
String url = baseUrl
1000+
.newBuilder()
1001+
.addPathSegment("mfa")
1002+
.addPathSegment("challenge")
1003+
.build()
1004+
.toString();
1005+
1006+
BaseRequest<MfaChallengeResponse> request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference<MfaChallengeResponse>() {
1007+
});
1008+
1009+
request.addParameter(KEY_MFA_TOKEN, mfaToken);
1010+
request.addParameter(KEY_CLIENT_ID, clientId);
1011+
addSecret(request, false);
1012+
if (Objects.nonNull(challengeType)) {
1013+
request.addParameter("challenge_type", challengeType);
1014+
}
1015+
if (Objects.nonNull(authenticatorId)) {
1016+
request.addParameter("authenticator_id", authenticatorId);
1017+
}
1018+
return request;
1019+
}
1020+
1021+
/**
1022+
* Associates or adds a new OTP authenticator for multi-factor authentication (MFA).
1023+
* Confidential clients (Regular Web Apps) <strong>must</strong> have a client secret configured on this {@code AuthAPI} instance.
1024+
* <pre>
1025+
* {@code
1026+
* try {
1027+
* CreatedOTPResponse result = authAPI.addOTPAuthenticator("the-mfa-token")
1028+
* .execute()
1029+
* .getBody();
1030+
* } catch (Auth0Exception e) {
1031+
* //Something happened
1032+
* }
1033+
* }
1034+
* </pre>
1035+
*
1036+
* @param mfaToken The token received from mfa_required error. Must not be null.
1037+
* @return a Request to execute.
1038+
* @see <a href="https://auth0.com/docs/api/authentication#add-an-authenticator">Add an Authenticator API documentation</a>
1039+
*/
1040+
public Request<CreatedOtpResponse> addOtpAuthenticator(String mfaToken) {
1041+
Asserts.assertNotNull(mfaToken, "mfa token");
1042+
1043+
String url = baseUrl
1044+
.newBuilder()
1045+
.addPathSegment("mfa")
1046+
.addPathSegment("associate")
1047+
.build()
1048+
.toString();
1049+
1050+
BaseRequest<CreatedOtpResponse> request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference<CreatedOtpResponse>() {
1051+
});
1052+
1053+
request.addParameter("authenticator_types", Collections.singletonList("otp"));
1054+
request.addParameter(KEY_CLIENT_ID, clientId);
1055+
addSecret(request, false);
1056+
request.addHeader("Authorization", "Bearer " + mfaToken);
1057+
return request;
1058+
}
1059+
1060+
/**
1061+
* Associates or adds a new OOB authenticator for multi-factor authentication (MFA).
1062+
* Confidential clients (Regular Web Apps) <strong>must</strong> have a client secret configured on this {@code AuthAPI} instance.
1063+
* <pre>
1064+
* {@code
1065+
* try {
1066+
* CreatedOobResponse result = authAPI.addOobAuthenticator("the-mfa-token", Collections.singletonList("sms"), "phone-number")
1067+
* .execute()
1068+
* .getBody();
1069+
* } catch (Auth0Exception e) {
1070+
* //Something happened
1071+
* }
1072+
* }
1073+
* </pre>
1074+
*
1075+
* @param mfaToken The token received from mfa_required error. Must not be null.
1076+
* @param oobChannels The type of OOB channels supported by the client. Must not be null.
1077+
* @param phoneNumber The phone number for "sms" or "voice" channels. May be null if not using "sms" or "voice".
1078+
* @return a Request to execute.
1079+
* @see <a href="https://auth0.com/docs/api/authentication#add-an-authenticator">Add an Authenticator API documentation</a>
1080+
*/
1081+
public Request<CreatedOobResponse> addOobAuthenticator(String mfaToken, List<String> oobChannels, String phoneNumber) {
1082+
Asserts.assertNotNull(mfaToken, "mfa token");
1083+
Asserts.assertNotNull(oobChannels, "OOB channels");
1084+
1085+
String url = baseUrl
1086+
.newBuilder()
1087+
.addPathSegment("mfa")
1088+
.addPathSegment("associate")
1089+
.build()
1090+
.toString();
1091+
1092+
BaseRequest<CreatedOobResponse> request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference<CreatedOobResponse>() {
1093+
});
1094+
1095+
request.addParameter("authenticator_types", Collections.singletonList("oob"));
1096+
request.addParameter("oob_channels", oobChannels);
1097+
request.addParameter(KEY_CLIENT_ID, clientId);
1098+
if (phoneNumber != null) {
1099+
request.addParameter("phone_number", phoneNumber);
1100+
}
1101+
addSecret(request, false);
1102+
request.addHeader("Authorization", "Bearer " + mfaToken);
1103+
return request;
1104+
}
1105+
1106+
1107+
/**
1108+
* Returns a list of authenticators associated with your application.
1109+
* <pre>
1110+
* {@code
1111+
* try {
1112+
* List<MfaAuthenticator> result = authAPI.listAuthenticators("token")
1113+
* .execute()
1114+
* .getBody();
1115+
* } catch (Auth0Exception e) {
1116+
* //Something happened
1117+
* }
1118+
* }
1119+
* </pre>
1120+
*
1121+
* @param accessToken The Access Token obtained during login. The token must possess a scope of {@code read:authenticators}
1122+
* and an audience of {@code https://YOUR_DOMAIN/mfa/}
1123+
* @return a Request to execute.
1124+
* @see <a href="https://auth0.com/docs/api/authentication#list-authenticators">List authenticators API documentation</a>
1125+
*/
1126+
public Request<List<MfaAuthenticator>> listAuthenticators(String accessToken) {
1127+
Asserts.assertNotNull(accessToken, "access token");
1128+
1129+
String url = baseUrl
1130+
.newBuilder()
1131+
.addPathSegment("mfa")
1132+
.addPathSegment("authenticators")
1133+
.build()
1134+
.toString();
1135+
1136+
BaseRequest<List<MfaAuthenticator>> request = new BaseRequest<>(client, null, url, HttpMethod.GET, new TypeReference<List<MfaAuthenticator>>() {
1137+
});
1138+
1139+
request.addHeader("Authorization", "Bearer " + accessToken);
1140+
return request;
1141+
}
1142+
8991143
private TokenRequest exchangeCode(String code, String redirectUri, boolean secretRequired) {
9001144
Asserts.assertNotNull(code, "code");
9011145
Asserts.assertNotNull(redirectUri, "redirect uri");
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.auth0.json.auth;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
import java.util.List;
8+
9+
@JsonIgnoreProperties(ignoreUnknown = true)
10+
@JsonInclude(JsonInclude.Include.NON_NULL)
11+
public class CreatedOobResponse {
12+
13+
@JsonProperty("oob_code")
14+
private String oobCode;
15+
16+
@JsonProperty("barcode_uri")
17+
private String barcodeUri;
18+
19+
@JsonProperty("authenticator_type")
20+
private String authenticatorType;
21+
22+
@JsonProperty("oob_channel")
23+
private String oobChannel;
24+
25+
@JsonProperty("recovery_codes")
26+
private List<String> recoveryCodes;
27+
28+
public String getOobCode() {
29+
return oobCode;
30+
}
31+
32+
public String getBarcodeUri() {
33+
return barcodeUri;
34+
}
35+
36+
public String getAuthenticatorType() {
37+
return authenticatorType;
38+
}
39+
40+
public List<String> getRecoveryCodes() {
41+
return recoveryCodes;
42+
}
43+
44+
public String getOobChannel() {
45+
return oobChannel;
46+
}
47+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.auth0.json.auth;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
import java.util.List;
8+
9+
@JsonIgnoreProperties(ignoreUnknown = true)
10+
@JsonInclude(JsonInclude.Include.NON_NULL)
11+
public class CreatedOtpResponse {
12+
13+
@JsonProperty("secret")
14+
private String secret;
15+
16+
@JsonProperty("barcode_uri")
17+
private String barcodeUri;
18+
19+
@JsonProperty("authenticator_type")
20+
private String authenticatorType;
21+
22+
@JsonProperty("recovery_codes")
23+
private List<String> recoveryCodes;
24+
25+
public String getSecret() {
26+
return secret;
27+
}
28+
29+
public String getBarcodeUri() {
30+
return barcodeUri;
31+
}
32+
33+
public String getAuthenticatorType() {
34+
return authenticatorType;
35+
}
36+
37+
public List<String> getRecoveryCodes() {
38+
return recoveryCodes;
39+
}
40+
41+
}

0 commit comments

Comments
 (0)