Skip to content

Commit a78c3b2

Browse files
authored
[SDK-3858] Support JWT Client Authentication (#507)
* [SDK-3858] Support JWT Client Authentication * rename builder method
1 parent 3be1684 commit a78c3b2

8 files changed

Lines changed: 514 additions & 67 deletions

File tree

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

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public class AuthAPI {
5454
private static final String KEY_OTP = "otp";
5555
private static final String KEY_REALM = "realm";
5656
private static final String KEY_MFA_TOKEN = "mfa_token";
57-
57+
private static final String KEY_CLIENT_ASSERTION = "client_assertion";
58+
private static final String KEY_CLIENT_ASSERTION_TYPE = "client_assertion_type";
5859
private static final String PATH_OAUTH = "oauth";
5960
private static final String PATH_TOKEN = "token";
6061
private static final String PATH_DBCONNECTIONS = "dbconnections";
@@ -65,6 +66,7 @@ public class AuthAPI {
6566
private final Auth0HttpClient client;
6667
private final String clientId;
6768
private final String clientSecret;
69+
private final ClientAssertionSigner clientAssertionSigner;
6870
private final HttpUrl baseUrl;
6971

7072
/**
@@ -83,7 +85,7 @@ public class AuthAPI {
8385
@Deprecated
8486
@SuppressWarnings("deprecation")
8587
public AuthAPI(String domain, String clientId, String clientSecret, com.auth0.client.HttpOptions options) {
86-
this(domain, clientId, clientSecret, buildNetworkingClient(options));
88+
this(domain, clientId, clientSecret, null, buildNetworkingClient(options));
8789
}
8890

8991
/**
@@ -110,7 +112,20 @@ public AuthAPI(String domain, String clientId, String clientSecret) {
110112
* @return a Builder for further configuration.
111113
*/
112114
public static AuthAPI.Builder newBuilder(String domain, String clientId, String clientSecret) {
113-
return new AuthAPI.Builder(domain, clientId, clientSecret);
115+
return new AuthAPI.Builder(domain, clientId).withClientSecret(clientSecret);
116+
}
117+
118+
/**
119+
* Initialize a new {@link Builder} to configure and create an instance. Use this to construct an instance
120+
* with a client assertion signer used in place of a client secret when calling token APIs.
121+
*
122+
* @param domain the tenant's domain. Must be a non-null valid HTTPS URL.
123+
* @param clientId the application's client ID.
124+
* @param clientAssertionSigner the {@code ClientAssertionSigner} used to create the signed client assertion.
125+
* @return a Builder for further configuration.
126+
*/
127+
public static AuthAPI.Builder newBuilder(String domain, String clientId, ClientAssertionSigner clientAssertionSigner) {
128+
return new AuthAPI.Builder(domain, clientId).withClientAssertionSigner(clientAssertionSigner);
114129
}
115130

116131
/**
@@ -124,7 +139,7 @@ public static AuthAPI.Builder newBuilder(String domain, String clientId) {
124139
return new AuthAPI.Builder(domain, clientId);
125140
}
126141

127-
private AuthAPI(String domain, String clientId, String clientSecret, Auth0HttpClient httpClient) {
142+
private AuthAPI(String domain, String clientId, String clientSecret, ClientAssertionSigner clientAssertionSigner, Auth0HttpClient httpClient) {
128143
Asserts.assertNotNull(domain, "domain");
129144
Asserts.assertNotNull(clientId, "client id");
130145
Asserts.assertNotNull(httpClient, "Http client");
@@ -135,9 +150,11 @@ private AuthAPI(String domain, String clientId, String clientSecret, Auth0HttpCl
135150
}
136151
this.clientId = clientId;
137152
this.clientSecret = clientSecret;
153+
this.clientAssertionSigner = clientAssertionSigner;
138154
this.client = httpClient;
139-
140155
}
156+
157+
141158
/**
142159
* Given a set of options, it creates a new instance of the {@link OkHttpClient}
143160
* configuring them according to their availability.
@@ -487,7 +504,7 @@ public TokenRequest login(String emailOrUsername, char[] password) {
487504
request.addParameter(KEY_GRANT_TYPE, "password");
488505
request.addParameter(KEY_USERNAME, emailOrUsername);
489506
request.addParameter(KEY_PASSWORD, password);
490-
addSecret(request, true);
507+
addClientAuthentication(request, true);
491508
return request;
492509
}
493510

@@ -555,7 +572,7 @@ public TokenRequest login(String emailOrUsername, char[] password, String realm)
555572
request.addParameter(KEY_USERNAME, emailOrUsername);
556573
request.addParameter(KEY_PASSWORD, password);
557574
request.addParameter(KEY_REALM, realm);
558-
addSecret(request, true);
575+
addClientAuthentication(request, true);
559576
return request;
560577
}
561578

@@ -597,7 +614,7 @@ public TokenRequest exchangePasswordlessOtp(String emailOrPhone, String realm, c
597614
request.addParameter(KEY_USERNAME, emailOrPhone);
598615
request.addParameter(KEY_REALM, realm);
599616
request.addParameter(KEY_OTP, otp);
600-
addSecret(request, false);
617+
addClientAuthentication(request, false);
601618
return request;
602619
}
603620

@@ -629,7 +646,7 @@ public TokenRequest requestToken(String audience) {
629646
request.addParameter(KEY_CLIENT_ID, clientId);
630647
request.addParameter(KEY_GRANT_TYPE, "client_credentials");
631648
request.addParameter(KEY_AUDIENCE, audience);
632-
addSecret(request, true);
649+
addClientAuthentication(request, true);
633650
return request;
634651
}
635652

@@ -663,7 +680,7 @@ public Request<Void> revokeToken(String refreshToken) {
663680
VoidRequest request = new VoidRequest(client, null, url, HttpMethod.POST);
664681
request.addParameter(KEY_CLIENT_ID, clientId);
665682
request.addParameter(KEY_TOKEN, refreshToken);
666-
addSecret(request, false);
683+
addClientAuthentication(request, false);
667684
return request;
668685
}
669686

@@ -696,7 +713,7 @@ public TokenRequest renewAuth(String refreshToken) {
696713
request.addParameter(KEY_CLIENT_ID, clientId);
697714
request.addParameter(KEY_GRANT_TYPE, "refresh_token");
698715
request.addParameter(KEY_REFRESH_TOKEN, refreshToken);
699-
addSecret(request, false);
716+
addClientAuthentication(request, false);
700717
return request;
701718
}
702719

@@ -816,7 +833,7 @@ public BaseRequest<PasswordlessEmailResponse> startPasswordlessEmailFlow(String
816833
request.addParameter(KEY_CONNECTION, "email");
817834
request.addParameter(KEY_EMAIL, email);
818835
request.addParameter("send", type.getType());
819-
addSecret(request, false);
836+
addClientAuthentication(request, false);
820837
return request;
821838
}
822839

@@ -857,7 +874,7 @@ public BaseRequest<PasswordlessSmsResponse> startPasswordlessSmsFlow(String phon
857874
request.addParameter(KEY_CLIENT_ID, clientId);
858875
request.addParameter(KEY_CONNECTION, "sms");
859876
request.addParameter("phone_number", phoneNumber);
860-
addSecret(request, false);
877+
addClientAuthentication(request, false);
861878
return request;
862879
}
863880

@@ -892,7 +909,7 @@ public TokenRequest exchangeMfaOtp(String mfaToken, char[] otp) {
892909
request.addParameter(KEY_GRANT_TYPE, "http://auth0.com/oauth/grant-type/mfa-otp");
893910
request.addParameter(KEY_MFA_TOKEN, mfaToken);
894911
request.addParameter(KEY_OTP, otp);
895-
addSecret(request, false);
912+
addClientAuthentication(request, false);
896913
return request;
897914
}
898915

@@ -933,7 +950,7 @@ public TokenRequest exchangeMfaOob(String mfaToken, char[] oobCode, char[] bindi
933950
request.addParameter("binding_code", bindingCode);
934951
}
935952

936-
addSecret(request, false);
953+
addClientAuthentication(request, false);
937954
return request;
938955
}
939956

@@ -968,7 +985,7 @@ public TokenRequest exchangeMfaRecoveryCode(String mfaToken, char[] recoveryCode
968985
request.addParameter(KEY_MFA_TOKEN, mfaToken);
969986
request.addParameter("recovery_code", recoveryCode);
970987

971-
addSecret(request, false);
988+
addClientAuthentication(request, false);
972989
return request;
973990
}
974991

@@ -1008,7 +1025,7 @@ public Request<MfaChallengeResponse> mfaChallengeRequest(String mfaToken, String
10081025

10091026
request.addParameter(KEY_MFA_TOKEN, mfaToken);
10101027
request.addParameter(KEY_CLIENT_ID, clientId);
1011-
addSecret(request, false);
1028+
addClientAuthentication(request, false);
10121029
if (Objects.nonNull(challengeType)) {
10131030
request.addParameter("challenge_type", challengeType);
10141031
}
@@ -1052,7 +1069,7 @@ public Request<CreatedOtpResponse> addOtpAuthenticator(String mfaToken) {
10521069

10531070
request.addParameter("authenticator_types", Collections.singletonList("otp"));
10541071
request.addParameter(KEY_CLIENT_ID, clientId);
1055-
addSecret(request, false);
1072+
addClientAuthentication(request, false);
10561073
request.addHeader("Authorization", "Bearer " + mfaToken);
10571074
return request;
10581075
}
@@ -1098,7 +1115,7 @@ public Request<CreatedOobResponse> addOobAuthenticator(String mfaToken, List<Str
10981115
if (phoneNumber != null) {
10991116
request.addParameter("phone_number", phoneNumber);
11001117
}
1101-
addSecret(request, false);
1118+
addClientAuthentication(request, false);
11021119
request.addHeader("Authorization", "Bearer " + mfaToken);
11031120
return request;
11041121
}
@@ -1149,7 +1166,7 @@ private TokenRequest exchangeCode(String code, String redirectUri, boolean secre
11491166
request.addParameter(KEY_GRANT_TYPE, "authorization_code");
11501167
request.addParameter("code", code);
11511168
request.addParameter("redirect_uri", redirectUri);
1152-
addSecret(request, secretRequired);
1169+
addClientAuthentication(request, secretRequired);
11531170
return request;
11541171
}
11551172

@@ -1162,40 +1179,54 @@ private String getTokenUrl() {
11621179
.toString();
11631180
}
11641181

1165-
private void addSecret(BaseRequest<?> request, boolean required) {
1166-
if (required && Objects.isNull(this.clientSecret)) {
1167-
throw new IllegalStateException("A client secret is required for this operation");
1182+
private void addClientAuthentication(BaseRequest<?> request, boolean required) {
1183+
if (required && (this.clientSecret == null && this.clientAssertionSigner == null)) {
1184+
throw new IllegalStateException("A client secret or client assertion signing key is required for this operation");
11681185
}
1169-
if (Objects.nonNull(this.clientSecret)) {
1170-
request.addParameter(KEY_CLIENT_SECRET, this.clientSecret);
1186+
1187+
if (Objects.nonNull(this.clientAssertionSigner)) {
1188+
request.addParameter(KEY_CLIENT_ASSERTION, this.clientAssertionSigner.createSignedClientAssertion(clientId, baseUrl.toString(), clientId));
1189+
request.addParameter(KEY_CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
1190+
} else if (Objects.nonNull(this.clientSecret)) {
1191+
request.addParameter(KEY_CLIENT_SECRET, clientSecret);
11711192
}
11721193
}
11731194

1195+
11741196
/**
11751197
* Builder for {@link AuthAPI} API client instances.
11761198
*/
11771199
public static class Builder {
11781200
private final String domain;
11791201
private final String clientId;
1180-
private final String clientSecret;
1202+
private String clientSecret;
1203+
private ClientAssertionSigner clientAssertionSigner;
11811204
private Auth0HttpClient httpClient;
11821205

1183-
/**
1184-
* Create a new Builder
1185-
* @param domain the domain of the tenant.
1186-
* @param clientId the client ID of the Auth0 application.
1187-
* @param clientSecret the client secret of the Auth0 application.
1188-
*/
1189-
public Builder(String domain, String clientId, String clientSecret) {
1206+
public Builder(String domain, String clientId) {
11901207
this.domain = domain;
11911208
this.clientId = clientId;
1209+
this.clientSecret = null;
1210+
}
1211+
1212+
/**
1213+
* Configure the client with a client secret.
1214+
* @param clientSecret the client secret of your application.
1215+
* @return the builder instance.
1216+
*/
1217+
public Builder withClientSecret(String clientSecret) {
11921218
this.clientSecret = clientSecret;
1219+
return this;
11931220
}
11941221

1195-
public Builder(String domain, String clientId) {
1196-
this.domain = domain;
1197-
this.clientId = clientId;
1198-
this.clientSecret = null;
1222+
/**
1223+
* Configure the client with a client assertion signer.
1224+
* @param clientAssertionSigner the client assertion signer to create the signed client assertion.
1225+
* @return the builder instance.
1226+
*/
1227+
public Builder withClientAssertionSigner(ClientAssertionSigner clientAssertionSigner) {
1228+
this.clientAssertionSigner = clientAssertionSigner;
1229+
return this;
11991230
}
12001231

12011232
/**
@@ -1214,7 +1245,7 @@ public Builder withHttpClient(Auth0HttpClient httpClient) {
12141245
* @return the configured {@code AuthAPI} instance.
12151246
*/
12161247
public AuthAPI build() {
1217-
return new AuthAPI(domain, clientId, clientSecret,
1248+
return new AuthAPI(domain, clientId, clientSecret, clientAssertionSigner,
12181249
Objects.nonNull(httpClient) ? httpClient : DefaultHttpClient.newBuilder().build());
12191250
}
12201251
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.auth0.client.auth;
2+
3+
/**
4+
* Responsible for creating a signed client assertion used to authenticate to the Authentication API
5+
*
6+
* @see <a href="https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OpenID Connect Core Specification</a>
7+
*/
8+
public interface ClientAssertionSigner {
9+
10+
/**
11+
* Creates a signed JWT representing a client assertion used to authenticate to the Authentication API.
12+
*
13+
* @param issuer the Issuer. This MUST contain the client_id of the OAuth Client.
14+
* @param audience the audience that identifies the Authorization Server as an intended audience.
15+
* @param subject the Subject. This MUST contain the client_id of the OAuth Client.
16+
17+
* @return a signed JWT representing the client assertion.
18+
*/
19+
String createSignedClientAssertion(String issuer, String audience, String subject);
20+
}

0 commit comments

Comments
 (0)