forked from auth0/auth0-java-mvc-common
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAuthenticationController.java
More file actions
387 lines (342 loc) · 17.4 KB
/
AuthenticationController.java
File metadata and controls
387 lines (342 loc) · 17.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
package com.auth0;
import com.auth0.client.HttpOptions;
import com.auth0.client.auth.AuthAPI;
import com.auth0.jwk.JwkProvider;
import com.auth0.net.Telemetry;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* Base Auth0 Authenticator class.
* Allows to easily authenticate using the Auth0 Hosted Login Page.
*/
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "SameParameterValue"})
public class AuthenticationController {
private final RequestProcessor requestProcessor;
/**
* Called from the Builder but also from tests in order to pass the mock.
*/
@VisibleForTesting
AuthenticationController(RequestProcessor requestProcessor) {
this.requestProcessor = requestProcessor;
}
@VisibleForTesting
RequestProcessor getRequestProcessor() {
return requestProcessor;
}
/**
* Create a new {@link Builder} instance to configure the {@link AuthenticationController} response type and algorithm used on the verification.
* By default it will request response type 'code' and later perform the Code Exchange, but if the response type is changed to 'token' it will handle
* the Implicit Grant using the HS256 algorithm with the Client Secret as secret.
*
* @param domain the Auth0 domain
* @param clientId the Auth0 application's client id
* @param clientSecret the Auth0 application's client secret
* @return a new Builder instance ready to configure
*/
public static Builder newBuilder(String domain, String clientId, String clientSecret) {
return new Builder(domain, clientId, clientSecret);
}
public static class Builder {
private static final String RESPONSE_TYPE_CODE = "code";
private final String domain;
private final String clientId;
private final String clientSecret;
private String responseType;
private JwkProvider jwkProvider;
private Integer clockSkew;
private Integer authenticationMaxAge;
private boolean useLegacySameSiteCookie;
private String organization;
private String invitation;
private HttpOptions httpOptions;
private String cookiePath;
Builder(String domain, String clientId, String clientSecret) {
Validate.notNull(domain);
Validate.notNull(clientId);
Validate.notNull(clientSecret);
this.domain = domain;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.responseType = RESPONSE_TYPE_CODE;
this.useLegacySameSiteCookie = true;
}
/**
* Customize certain aspects of the underlying HTTP client networking library, such as timeouts and proxy configuration.
*
* @param httpOptions a non-null {@code HttpOptions}
* @return this same builder instance.
*/
public Builder withHttpOptions(HttpOptions httpOptions) {
Validate.notNull(httpOptions);
this.httpOptions = httpOptions;
return this;
}
/**
* Specify that transient authentication-based cookies such as state and nonce are created with the specified
* {@code Path} cookie attribute.
*
* @param cookiePath the path to set on the cookie.
* @return this builder instance.
*/
public Builder withCookiePath(String cookiePath) {
Validate.notNull(cookiePath);
this.cookiePath = cookiePath;
return this;
}
/**
* Change the response type to request in the Authorization step. Default value is 'code'.
*
* @param responseType the response type to request. Any combination of 'code', 'token' and 'id_token' but 'token id_token' is allowed, using a space as separator.
* @return this same builder instance.
*/
public Builder withResponseType(String responseType) {
Validate.notNull(responseType);
this.responseType = responseType.trim().toLowerCase();
return this;
}
/**
* Sets the Jwk Provider that will return the Public Key required to verify the token in case of Implicit Grant flows.
* This is required if the Auth0 Application is signing the tokens with the RS256 algorithm.
*
* @param jwkProvider a valid Jwk provider.
* @return this same builder instance.
*/
public Builder withJwkProvider(JwkProvider jwkProvider) {
Validate.notNull(jwkProvider);
this.jwkProvider = jwkProvider;
return this;
}
/**
* Sets the clock-skew or leeway value to use in the ID Token verification. The value must be in seconds.
* Defaults to 60 seconds.
*
* @param clockSkew the clock-skew to use for ID Token verification, in seconds.
* @return this same builder instance.
*/
public Builder withClockSkew(Integer clockSkew) {
Validate.notNull(clockSkew);
this.clockSkew = clockSkew;
return this;
}
/**
* Sets the allowable elapsed time in seconds since the last time user was authenticated.
* By default there is no limit.
*
* @param maxAge the max age of the authentication, in seconds.
* @return this same builder instance.
*/
public Builder withAuthenticationMaxAge(Integer maxAge) {
Validate.notNull(maxAge);
this.authenticationMaxAge = maxAge;
return this;
}
/**
* Sets whether fallback cookies will be set for clients that do not support SameSite=None cookie attribute.
* The SameSite Cookie attribute will only be set to "None" if the reponseType includes "id_token".
* By default this is true.
* @param useLegacySameSiteCookie whether fallback auth-based cookies should be set.
* @return this same builder instance.
*/
public Builder withLegacySameSiteCookie(boolean useLegacySameSiteCookie) {
this.useLegacySameSiteCookie = useLegacySameSiteCookie;
return this;
}
/**
* Sets the organization query string parameter value used to login to an organization.
*
* @param organization The ID or name of the organization to log the user in to.
* @return the builder instance.
*/
public Builder withOrganization(String organization) {
Validate.notNull(organization);
this.organization = organization;
return this;
}
/**
* Sets the invitation query string parameter to join an organization. If using this, you must also specify the
* organization using {@linkplain Builder#withOrganization(String)}.
*
* @param invitation The ID of the invitation to accept. This is available on the URL that is provided when accepting an invitation.
* @return the builder instance.
*/
public Builder withInvitation(String invitation) {
Validate.notNull(invitation);
this.invitation = invitation;
return this;
}
/**
* Create a new {@link AuthenticationController} instance that will handle both Code Grant and Implicit Grant flows using either Code Exchange or Token Signature verification.
*
* @return a new instance of {@link AuthenticationController}.
* @throws UnsupportedOperationException if the Implicit Grant is chosen and the environment doesn't support UTF-8 encoding.
*/
public AuthenticationController build() throws UnsupportedOperationException {
AuthAPI apiClient = createAPIClient(domain, clientId, clientSecret, httpOptions);
setupTelemetry(apiClient);
final boolean expectedAlgorithmIsExplicitlySetAndAsymmetric = jwkProvider != null;
final SignatureVerifier signatureVerifier;
if (expectedAlgorithmIsExplicitlySetAndAsymmetric) {
signatureVerifier = new AsymmetricSignatureVerifier(jwkProvider);
} else if (responseType.contains(RESPONSE_TYPE_CODE)) {
// Old behavior: To maintain backwards-compatibility when
// no explicit algorithm is set by the user, we
// must skip ID Token signature check.
signatureVerifier = new AlgorithmNameVerifier();
} else {
signatureVerifier = new SymmetricSignatureVerifier(clientSecret);
}
String issuer = getIssuer(domain);
IdTokenVerifier.Options verifyOptions = createIdTokenVerificationOptions(issuer, clientId, signatureVerifier);
verifyOptions.setClockSkew(clockSkew);
verifyOptions.setMaxAge(authenticationMaxAge);
verifyOptions.setOrganization(this.organization);
RequestProcessor processor = new RequestProcessor.Builder(apiClient, responseType, verifyOptions)
.withLegacySameSiteCookie(useLegacySameSiteCookie)
.withOrganization(organization)
.withInvitation(invitation)
.withCookiePath(cookiePath)
.build();
return new AuthenticationController(processor);
}
@VisibleForTesting
IdTokenVerifier.Options createIdTokenVerificationOptions(String issuer, String audience, SignatureVerifier signatureVerifier) {
return new IdTokenVerifier.Options(issuer, audience, signatureVerifier);
}
@VisibleForTesting
AuthAPI createAPIClient(String domain, String clientId, String clientSecret, HttpOptions httpOptions) {
if (httpOptions != null) {
return new AuthAPI(domain, clientId, clientSecret, httpOptions);
}
return new AuthAPI(domain, clientId, clientSecret);
}
@VisibleForTesting
void setupTelemetry(AuthAPI client) {
Telemetry telemetry = new Telemetry("auth0-java-mvc-common", obtainPackageVersion());
client.setTelemetry(telemetry);
}
@VisibleForTesting
String obtainPackageVersion() {
//Value if taken from jar's manifest file.
//Call will return null on dev environment (outside of a jar)
return getClass().getPackage().getImplementationVersion();
}
private String getIssuer(String domain) {
if (!domain.startsWith("http://") && !domain.startsWith("https://")) {
domain = "https://" + domain;
}
if (!domain.endsWith("/")) {
domain = domain + "/";
}
return domain;
}
}
/**
* Whether to enable or not the HTTP Logger for every Request and Response.
* Enabling this can expose sensitive information.
*
* @param enabled whether to enable the HTTP logger or not.
*/
public void setLoggingEnabled(boolean enabled) {
requestProcessor.getClient().setLoggingEnabled(enabled);
}
/**
* Disable sending the Telemetry header on every request to the Auth0 API
*/
public void doNotSendTelemetry() {
requestProcessor.getClient().doNotSendTelemetry();
}
/**
* Process a request to obtain a set of {@link Tokens} that represent successful authentication or authorization.
*
* This method should be called when processing the callback request to your application. It will validate
* authentication-related request parameters, handle performing a Code Exchange request if using
* the "code" response type, and verify the integrity of the ID token (if present).
*
* <p><strong>Important:</strong> When using this API, you <strong>must</strong> also use {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, HttpServletResponse, String)}
* when building the {@link AuthorizeUrl} that the user will be redirected to to login. Failure to do so may result
* in a broken login experience for the user.</p>
*
* @param request the received request to process.
* @param response the received response to process.
* @return the Tokens obtained after the user authentication.
* @throws InvalidRequestException if the error is result of making an invalid authentication request.
* @throws IdentityVerificationException if an error occurred while verifying the request tokens.
*/
public Tokens handle(HttpServletRequest request, HttpServletResponse response) throws IdentityVerificationException {
Validate.notNull(request, "request must not be null");
Validate.notNull(response, "response must not be null");
return requestProcessor.process(request, response);
}
/**
* Process a request to obtain a set of {@link Tokens} that represent successful authentication or authorization.
*
* This method should be called when processing the callback request to your application. It will validate
* authentication-related request parameters, handle performing a Code Exchange request if using
* the "code" response type, and verify the integrity of the ID token (if present).
*
* <p><strong>Important:</strong> When using this API, you <strong>must</strong> also use the {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, String)}
* when building the {@link AuthorizeUrl} that the user will be redirected to to login. Failure to do so may result
* in a broken login experience for the user.</p>
*
* @deprecated This method uses the {@link jakarta.servlet.http.HttpSession} for auth-based data, and is incompatible
* with clients that are using the "id_token" or "token" responseType with browsers that enforce SameSite cookie
* restrictions. This method will be removed in version 2.0.0. Use
* {@link AuthenticationController#handle(HttpServletRequest, HttpServletResponse)} instead.
*
* @param request the received request to process.
* @return the Tokens obtained after the user authentication.
* @throws InvalidRequestException if the error is result of making an invalid authentication request.
* @throws IdentityVerificationException if an error occurred while verifying the request tokens.
*/
@Deprecated
public Tokens handle(HttpServletRequest request) throws IdentityVerificationException {
Validate.notNull(request, "request must not be null");
return requestProcessor.process(request, null);
}
/**
* Pre builds an Auth0 Authorize Url with the given redirect URI using a random state and a random nonce if applicable.
*
* <p><strong>Important:</strong> When using this API, you <strong>must</strong> also obtain the tokens using the
* {@link AuthenticationController#handle(HttpServletRequest)} method. Failure to do so may result in a broken login
* experience for users.</p>
*
* @deprecated This method stores data in the {@link jakarta.servlet.http.HttpSession}, and is incompatible with clients
* that are using the "id_token" or "token" responseType with browsers that enforce SameSite cookie restrictions.
* This method will be removed in version 2.0.0. Use
* {@link AuthenticationController#buildAuthorizeUrl(HttpServletRequest, HttpServletResponse, String)} instead.
*
* @param request the caller request. Used to keep the session context.
* @param redirectUri the url to call back with the authentication result.
* @return the authorize url builder to continue any further parameter customization.
*/
@Deprecated
public AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, String redirectUri) {
Validate.notNull(request, "request must not be null");
Validate.notNull(redirectUri, "redirectUri must not be null");
String state = StorageUtils.secureRandomString();
String nonce = StorageUtils.secureRandomString();
return requestProcessor.buildAuthorizeUrl(request, null, redirectUri, state, nonce);
}
/**
* Pre builds an Auth0 Authorize Url with the given redirect URI using a random state and a random nonce if applicable.
*
* <p><strong>Important:</strong> When using this API, you <strong>must</strong> also obtain the tokens using the
* {@link AuthenticationController#handle(HttpServletRequest, HttpServletResponse)} method. Failure to do so will result in a broken login
* experience for users.</p>
*
* @param request the HTTP request
* @param response the HTTP response. Used to store auth-based cookies.
* @param redirectUri the url to call back with the authentication result.
* @return the authorize url builder to continue any further parameter customization.
*/
public AuthorizeUrl buildAuthorizeUrl(HttpServletRequest request, HttpServletResponse response, String redirectUri) {
Validate.notNull(request, "request must not be null");
Validate.notNull(response, "response must not be null");
Validate.notNull(redirectUri, "redirectUri must not be null");
String state = StorageUtils.secureRandomString();
String nonce = StorageUtils.secureRandomString();
return requestProcessor.buildAuthorizeUrl(request, response, redirectUri, state, nonce);
}
}