names = request.getHeaderNames();
+
+ if (names != null) {
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ headers.put(name.toLowerCase(Locale.ROOT), request.getHeader(name));
+ }
}
- static String buildHtu(HttpServletRequest request) {
- String scheme = request.getScheme().toLowerCase(Locale.ROOT);
- String host = request.getServerName().toLowerCase(Locale.ROOT);
+ return headers;
+ }
- int port = request.getServerPort();
- boolean defaultPort =
- (scheme.equals("http") && port == 80) ||
- (scheme.equals("https") && port == 443);
+ HttpRequestInfo extractRequestInfo(HttpServletRequest request) {
+ String htu = buildHtu(request);
+ return new HttpRequestInfo(request.getMethod(), htu, null);
+ }
- StringBuilder htu = new StringBuilder();
- htu.append(scheme).append("://").append(host);
+ static String buildHtu(HttpServletRequest request) {
+ String scheme = request.getScheme().toLowerCase(Locale.ROOT);
+ String host = request.getServerName().toLowerCase(Locale.ROOT);
- if (!defaultPort) {
- htu.append(":").append(port);
- }
+ int port = request.getServerPort();
+ boolean defaultPort =
+ (scheme.equals("http") && port == 80) || (scheme.equals("https") && port == 443);
- htu.append(request.getRequestURI());
+ StringBuilder htu = new StringBuilder();
+ htu.append(scheme).append("://").append(host);
- return htu.toString();
+ if (!defaultPort) {
+ htu.append(":").append(port);
}
+
+ htu.append(request.getRequestURI());
+
+ return htu.toString();
+ }
}
diff --git a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AuthenticationToken.java b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AuthenticationToken.java
index ed41b0b..8e62a8b 100644
--- a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AuthenticationToken.java
+++ b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AuthenticationToken.java
@@ -1,120 +1,117 @@
package com.auth0.spring.boot;
import com.auth0.models.AuthenticationContext;
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.AuthorityUtils;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
/**
* Spring Security Authentication object representing a successfully validated Auth0 JWT.
- *
- * Authorities are derived from the "scope" claim in the JWT, if present, and mapped
- * to {@code SCOPE_} prefixed {@link SimpleGrantedAuthority} instances. If no scopes
- * are present, a default {@code ROLE_USER} authority is assigned.
+ *
+ *
Authorities are derived from the "scope" claim in the JWT, if present, and mapped to {@code
+ * SCOPE_} prefixed {@link SimpleGrantedAuthority} instances. If no scopes are present, a default
+ * {@code ROLE_USER} authority is assigned.
*/
public class Auth0AuthenticationToken extends AbstractAuthenticationToken {
- private final AuthenticationContext authenticationContext;
- private final String principal;
-
- /**
- * Constructs a new {@code Auth0AuthenticationToken} from the given {@link AuthenticationContext}.
- *
- * Extracts authorities from the "scope" claim and sets the principal to the "sub" claim.
- *
- * @param authenticationContext the validated Auth0 authentication context
- */
- public Auth0AuthenticationToken(AuthenticationContext authenticationContext) {
- super(createAuthorities(authenticationContext));
- this.authenticationContext = authenticationContext;
- this.principal = (String) authenticationContext.getClaims().get("sub");
- setAuthenticated(true);
- }
+ private final AuthenticationContext authenticationContext;
+ private final String principal;
- static Collection extends GrantedAuthority> createAuthorities(AuthenticationContext ctx) {
- Object scopeClaim = ctx.getClaims().get("scope");
+ /**
+ * Constructs a new {@code Auth0AuthenticationToken} from the given {@link AuthenticationContext}.
+ *
+ *
Extracts authorities from the "scope" claim and sets the principal to the "sub" claim.
+ *
+ * @param authenticationContext the validated Auth0 authentication context
+ */
+ public Auth0AuthenticationToken(AuthenticationContext authenticationContext) {
+ super(createAuthorities(authenticationContext));
+ this.authenticationContext = authenticationContext;
+ this.principal = (String) authenticationContext.getClaims().get("sub");
+ setAuthenticated(true);
+ }
- if (scopeClaim instanceof String && !((String) scopeClaim).isBlank()) {
- String scopes = (String) scopeClaim;
- List authorities = List.of(scopes.trim().split("\\s+"));
+ static Collection extends GrantedAuthority> createAuthorities(AuthenticationContext ctx) {
+ Object scopeClaim = ctx.getClaims().get("scope");
- return authorities.stream()
- .map(scope -> "SCOPE_" + scope)
- .map(SimpleGrantedAuthority::new)
- .collect(Collectors.toList());
- }
+ if (scopeClaim instanceof String && !((String) scopeClaim).isBlank()) {
+ String scopes = (String) scopeClaim;
+ List authorities = List.of(scopes.trim().split("\\s+"));
- return AuthorityUtils.createAuthorityList("ROLE_USER");
+ return authorities.stream()
+ .map(scope -> "SCOPE_" + scope)
+ .map(SimpleGrantedAuthority::new)
+ .collect(Collectors.toList());
}
- /**
- * Returns the credentials for this authentication token.
- *
- * Always returns {@code null} as credentials are not exposed.
- *
- * @return {@code null}
- */
- @Override
- public Object getCredentials() {
- return null;
- }
+ return AuthorityUtils.createAuthorityList("ROLE_USER");
+ }
- /**
- * Returns the principal identifier for this authentication token.
- *
- * Typically the "sub" claim from the JWT.
- *
- * @return the principal identifier
- */
- @Override
- public Object getPrincipal() {
- return this.principal;
- }
+ /**
+ * Returns the credentials for this authentication token.
+ *
+ *
Always returns {@code null} as credentials are not exposed.
+ *
+ * @return {@code null}
+ */
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
- /**
- * Returns the JWT claims from the authenticated token.
- *
- * Provides access to all JWT claims without exposing internal authentication
- * context.
- *
- * @return a map containing all JWT claims
- */
- public Map getClaims() {
- return authenticationContext.getClaims();
- }
+ /**
+ * Returns the principal identifier for this authentication token.
+ *
+ * Typically the "sub" claim from the JWT.
+ *
+ * @return the principal identifier
+ */
+ @Override
+ public Object getPrincipal() {
+ return this.principal;
+ }
- /**
- * Returns the scopes from the JWT as a set of strings.
- *
- * Extracts and parses the "scope" claim into individual scope strings.
- *
- * @return a set of scope strings, or empty set if no scopes present
- */
- public Set getScopes() {
- Object scopeClaim = authenticationContext.getClaims().get("scope");
- if (scopeClaim instanceof String && !((String) scopeClaim).isBlank()) {
- String scopes = (String) scopeClaim;
- return Set.of(scopes.trim().split("\\s+"));
- }
- return Set.of();
- }
+ /**
+ * Returns the JWT claims from the authenticated token.
+ *
+ * Provides access to all JWT claims without exposing internal authentication context.
+ *
+ * @return a map containing all JWT claims
+ */
+ public Map getClaims() {
+ return authenticationContext.getClaims();
+ }
- /**
- * Returns a specific claim value from the JWT.
- *
- * Convenience method for accessing individual claims without getting the full
- * claims map.
- *
- * @param claimName the name of the claim to retrieve
- * @return the claim value, or null if not present
- */
- public Object getClaim(String claimName) {
- return authenticationContext.getClaims().get(claimName);
+ /**
+ * Returns the scopes from the JWT as a set of strings.
+ *
+ *
Extracts and parses the "scope" claim into individual scope strings.
+ *
+ * @return a set of scope strings, or empty set if no scopes present
+ */
+ public Set getScopes() {
+ Object scopeClaim = authenticationContext.getClaims().get("scope");
+ if (scopeClaim instanceof String && !((String) scopeClaim).isBlank()) {
+ String scopes = (String) scopeClaim;
+ return Set.of(scopes.trim().split("\\s+"));
}
+ return Set.of();
+ }
+
+ /**
+ * Returns a specific claim value from the JWT.
+ *
+ * Convenience method for accessing individual claims without getting the full claims map.
+ *
+ * @param claimName the name of the claim to retrieve
+ * @return the claim value, or null if not present
+ */
+ public Object getClaim(String claimName) {
+ return authenticationContext.getClaims().get(claimName);
+ }
}
diff --git a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AutoConfiguration.java b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AutoConfiguration.java
index e634fdc..d859a87 100644
--- a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AutoConfiguration.java
+++ b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0AutoConfiguration.java
@@ -7,55 +7,53 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
-/**
- * Autoconfiguration for Auth0 authentication and JWT validation.
- */
+/** Autoconfiguration for Auth0 authentication and JWT validation. */
@AutoConfiguration
@EnableConfigurationProperties(Auth0Properties.class)
public class Auth0AutoConfiguration {
- /**
- * Creates an {@link AuthOptions} bean from {@link Auth0Properties}.
- *
- * Builds the authentication options configuration.
- * @param properties the Auth0 configuration properties from application configuration
- * @return configured AuthOptions instance for creating AuthClient
- * @see AuthOptions.Builder
- * @see Auth0Properties
- */
- @Bean
- public AuthOptions authOptions(Auth0Properties properties) {
+ /**
+ * Creates an {@link AuthOptions} bean from {@link Auth0Properties}.
+ *
+ *
Builds the authentication options configuration.
+ *
+ * @param properties the Auth0 configuration properties from application configuration
+ * @return configured AuthOptions instance for creating AuthClient
+ * @see AuthOptions.Builder
+ * @see Auth0Properties
+ */
+ @Bean
+ public AuthOptions authOptions(Auth0Properties properties) {
- AuthOptions.Builder builder = new AuthOptions.Builder()
- .domain(properties.getDomain())
- .audience(properties.getAudience());
+ AuthOptions.Builder builder =
+ new AuthOptions.Builder().domain(properties.getDomain()).audience(properties.getAudience());
- if (properties.getDpopMode() != null) {
- builder.dpopMode(properties.getDpopMode());
- }
-
- if (properties.getDpopIatLeewaySeconds() != null) {
- builder.dpopIatLeewaySeconds(properties.getDpopIatLeewaySeconds());
- }
- if (properties.getDpopIatOffsetSeconds() != null) {
- builder.dpopIatOffsetSeconds(properties.getDpopIatOffsetSeconds());
- }
-
- return builder.build();
+ if (properties.getDpopMode() != null) {
+ builder.dpopMode(properties.getDpopMode());
}
- /**
- * Creates an {@link AuthClient} bean for request authentication and JWT validation.
- *
- * Serves as the main entry point for verifying HTTP requests containing
- * access tokens.
- * @param options the AuthOptions configuration for creating the client
- * @return AuthClient instance configured with the specified options
- * @see AuthClient#from(AuthOptions)
- * @see AuthClient#verifyRequest(java.util.Map, com.auth0.models.HttpRequestInfo)
- */
- @Bean
- @ConditionalOnMissingBean
- public AuthClient authClient(AuthOptions options) {
- return AuthClient.from(options);
+ if (properties.getDpopIatLeewaySeconds() != null) {
+ builder.dpopIatLeewaySeconds(properties.getDpopIatLeewaySeconds());
+ }
+ if (properties.getDpopIatOffsetSeconds() != null) {
+ builder.dpopIatOffsetSeconds(properties.getDpopIatOffsetSeconds());
}
+
+ return builder.build();
+ }
+
+ /**
+ * Creates an {@link AuthClient} bean for request authentication and JWT validation.
+ *
+ *
Serves as the main entry point for verifying HTTP requests containing access tokens.
+ *
+ * @param options the AuthOptions configuration for creating the client
+ * @return AuthClient instance configured with the specified options
+ * @see AuthClient#from(AuthOptions)
+ * @see AuthClient#verifyRequest(java.util.Map, com.auth0.models.HttpRequestInfo)
+ */
+ @Bean
+ @ConditionalOnMissingBean
+ public AuthClient authClient(AuthOptions options) {
+ return AuthClient.from(options);
+ }
}
diff --git a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0Properties.java b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0Properties.java
index 82896b5..e1018fb 100644
--- a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0Properties.java
+++ b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0Properties.java
@@ -5,11 +5,12 @@
/**
* Configuration properties for Auth0 authentication and token validation.
- *
- * This class binds Spring Boot configuration properties prefixed with {@code auth0} to provide
+ *
+ *
This class binds Spring Boot configuration properties prefixed with {@code auth0} to provide
* configuration for JWT validation, DPoP support, and API access control.
- *
- * Example configuration in {@code application.yml}:
+ *
+ *
Example configuration in {@code application.yml}:
+ *
*
* auth0:
* domain: "random-test.us.auth0.com"
@@ -18,94 +19,115 @@
* dpopIatOffsetSeconds: 300
* dpopIatLeewaySeconds: 60
*
+ *
* @see com.auth0.enums.DPoPMode
*/
@ConfigurationProperties(prefix = "auth0")
public class Auth0Properties {
- private String domain;
- private String audience;
- private DPoPMode dpopMode;
+ private String domain;
+ private String audience;
+ private DPoPMode dpopMode;
- private Long dpopIatOffsetSeconds;
- private Long dpopIatLeewaySeconds;
+ private Long dpopIatOffsetSeconds;
+ private Long dpopIatLeewaySeconds;
- /**
- * Gets the Auth0 domain configured for this application.
- * @return the Auth0 domain, or {@code null} if not configured
- */
- public String getDomain() { return domain; }
+ /**
+ * Gets the Auth0 domain configured for this application.
+ *
+ * @return the Auth0 domain, or {@code null} if not configured
+ */
+ public String getDomain() {
+ return domain;
+ }
- /**
- * Sets the Auth0 domain for this application.
- * @param domain the Auth0 domain to configure
- */
- public void setDomain(String domain) { this.domain = domain; }
+ /**
+ * Sets the Auth0 domain for this application.
+ *
+ * @param domain the Auth0 domain to configure
+ */
+ public void setDomain(String domain) {
+ this.domain = domain;
+ }
- /**
- * Gets the audience (API identifier) for token validation.
- * @return the configured audience, or {@code null} if not set
- */
- public String getAudience() { return audience; }
+ /**
+ * Gets the audience (API identifier) for token validation.
+ *
+ * @return the configured audience, or {@code null} if not set
+ */
+ public String getAudience() {
+ return audience;
+ }
- /**
- * Sets the audience (API identifier).
- * @param audience the audience to configure
- */
- public void setAudience(String audience) { this.audience = audience; }
+ /**
+ * Sets the audience (API identifier).
+ *
+ * @param audience the audience to configure
+ */
+ public void setAudience(String audience) {
+ this.audience = audience;
+ }
- /**
- * Gets the DPoP mode for token validation.
- * @return the configured DPoP mode ({@code DISABLED}, {@code ALLOWED}, or {@code REQUIRED}), or {@code null} if not set
- */
- public DPoPMode getDpopMode() {
- return dpopMode;
- }
+ /**
+ * Gets the DPoP mode for token validation.
+ *
+ * @return the configured DPoP mode ({@code DISABLED}, {@code ALLOWED}, or {@code REQUIRED}), or
+ * {@code null} if not set
+ */
+ public DPoPMode getDpopMode() {
+ return dpopMode;
+ }
- /**
- * Sets the DPoP mode for token validation.
- * @param dpopMode the DPoP mode to configure ({@code DISABLED}, {@code ALLOWED}, or {@code REQUIRED})
- */
- public void setDpopMode(DPoPMode dpopMode) {
- this.dpopMode = dpopMode;
- }
+ /**
+ * Sets the DPoP mode for token validation.
+ *
+ * @param dpopMode the DPoP mode to configure ({@code DISABLED}, {@code ALLOWED}, or {@code
+ * REQUIRED})
+ */
+ public void setDpopMode(DPoPMode dpopMode) {
+ this.dpopMode = dpopMode;
+ }
- /**
- * Gets the DPoP proof iat (issued-at) offset in seconds.
- * @return the configured offset in seconds, or {@code null} if not set
- */
- public Long getDpopIatOffsetSeconds() {
- return dpopIatOffsetSeconds;
- }
+ /**
+ * Gets the DPoP proof iat (issued-at) offset in seconds.
+ *
+ * @return the configured offset in seconds, or {@code null} if not set
+ */
+ public Long getDpopIatOffsetSeconds() {
+ return dpopIatOffsetSeconds;
+ }
- /**
- * Sets the DPoP proof iat (issued-at) offset in seconds.
- * @param dpopIatOffsetSeconds the offset in seconds to configure (must be non-negative)
- * @throws IllegalArgumentException if the value is negative
- */
- public void setDpopIatOffsetSeconds(Long dpopIatOffsetSeconds) {
- if (dpopIatOffsetSeconds != null && dpopIatOffsetSeconds < 0) {
- throw new IllegalArgumentException("DPoP iat offset seconds must be non-negative");
- }
- this.dpopIatOffsetSeconds = dpopIatOffsetSeconds;
+ /**
+ * Sets the DPoP proof iat (issued-at) offset in seconds.
+ *
+ * @param dpopIatOffsetSeconds the offset in seconds to configure (must be non-negative)
+ * @throws IllegalArgumentException if the value is negative
+ */
+ public void setDpopIatOffsetSeconds(Long dpopIatOffsetSeconds) {
+ if (dpopIatOffsetSeconds != null && dpopIatOffsetSeconds < 0) {
+ throw new IllegalArgumentException("DPoP iat offset seconds must be non-negative");
}
+ this.dpopIatOffsetSeconds = dpopIatOffsetSeconds;
+ }
- /**
- * Gets the DPoP proof iat (issued-at) leeway in seconds.
- * @return the configured leeway in seconds, or {@code null} if not set
- */
- public Long getDpopIatLeewaySeconds() {
- return dpopIatLeewaySeconds;
- }
+ /**
+ * Gets the DPoP proof iat (issued-at) leeway in seconds.
+ *
+ * @return the configured leeway in seconds, or {@code null} if not set
+ */
+ public Long getDpopIatLeewaySeconds() {
+ return dpopIatLeewaySeconds;
+ }
- /**
- * Sets the DPoP proof iat (issued-at) leeway in seconds.
- * @param dpopIatLeewaySeconds the leeway in seconds to configure (must be non-negative)
- * @throws IllegalArgumentException if the value is negative
- */
- public void setDpopIatLeewaySeconds(Long dpopIatLeewaySeconds) {
- if (dpopIatLeewaySeconds != null && dpopIatLeewaySeconds < 0) {
- throw new IllegalArgumentException("DPoP iat leeway seconds must be non-negative");
- }
- this.dpopIatLeewaySeconds = dpopIatLeewaySeconds;
+ /**
+ * Sets the DPoP proof iat (issued-at) leeway in seconds.
+ *
+ * @param dpopIatLeewaySeconds the leeway in seconds to configure (must be non-negative)
+ * @throws IllegalArgumentException if the value is negative
+ */
+ public void setDpopIatLeewaySeconds(Long dpopIatLeewaySeconds) {
+ if (dpopIatLeewaySeconds != null && dpopIatLeewaySeconds < 0) {
+ throw new IllegalArgumentException("DPoP iat leeway seconds must be non-negative");
}
+ this.dpopIatLeewaySeconds = dpopIatLeewaySeconds;
+ }
}
diff --git a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0SecurityAutoConfiguration.java b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0SecurityAutoConfiguration.java
index 4ce3ddb..49685c5 100644
--- a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0SecurityAutoConfiguration.java
+++ b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0SecurityAutoConfiguration.java
@@ -10,9 +10,10 @@
@ConditionalOnClass(AuthClient.class)
public class Auth0SecurityAutoConfiguration {
- @Bean
- @ConditionalOnMissingBean
- public Auth0AuthenticationFilter authAuthenticationFilter(AuthClient authClient, Auth0Properties auth0Properties) {
- return new Auth0AuthenticationFilter(authClient, auth0Properties);
- }
+ @Bean
+ @ConditionalOnMissingBean
+ public Auth0AuthenticationFilter authAuthenticationFilter(
+ AuthClient authClient, Auth0Properties auth0Properties) {
+ return new Auth0AuthenticationFilter(authClient, auth0Properties);
+ }
}
diff --git a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0SpringbootApiApplication.java b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0SpringbootApiApplication.java
index 5bac0ec..338fd8a 100644
--- a/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0SpringbootApiApplication.java
+++ b/auth0-springboot-api/src/main/java/com/auth0/spring/boot/Auth0SpringbootApiApplication.java
@@ -6,8 +6,7 @@
@SpringBootApplication
public class Auth0SpringbootApiApplication {
- public static void main(String[] args) {
- SpringApplication.run(Auth0SpringbootApiApplication.class, args);
- }
-
+ public static void main(String[] args) {
+ SpringApplication.run(Auth0SpringbootApiApplication.class, args);
+ }
}
diff --git a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AuthenticationFilterTest.java b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AuthenticationFilterTest.java
index 96471cf..7a8c584 100644
--- a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AuthenticationFilterTest.java
+++ b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AuthenticationFilterTest.java
@@ -1,10 +1,16 @@
package com.auth0.spring.boot;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+
import com.auth0.AuthClient;
import com.auth0.enums.DPoPMode;
import com.auth0.exception.MissingAuthorizationException;
import com.auth0.models.AuthenticationContext;
import com.auth0.models.HttpRequestInfo;
+import jakarta.servlet.FilterChain;
+import java.util.Enumeration;
+import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -13,417 +19,412 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
-
-import jakarta.servlet.FilterChain;
import org.springframework.security.core.context.SecurityContextHolder;
-import java.util.Enumeration;
-import java.util.Map;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.when;
-
-/**
- * Test cases for Auth0AuthenticationFilter
- */
+/** Test cases for Auth0AuthenticationFilter */
@ExtendWith(MockitoExtension.class)
class Auth0AuthenticationFilterTest {
- @Mock
- private Auth0Properties auth0Properties;
-
- @Mock
- private AuthClient authClient;
-
- @Mock
- private FilterChain filterChain;
-
- private Auth0AuthenticationFilter filter;
- private MockHttpServletRequest request;
- private MockHttpServletResponse response;
-
- @BeforeEach
- void setUp() {
- filter = new Auth0AuthenticationFilter(authClient, auth0Properties);
- request = new MockHttpServletRequest();
- response = new MockHttpServletResponse();
- }
-
- @Test
- @DisplayName("Should return empty map when request has no headers")
- void extractHeaders_shouldReturnEmptyMap_whenNoHeadersPresent() throws MissingAuthorizationException {
- Map headers = filter.extractHeaders(request);
-
- assertNotNull(headers);
- assertTrue(headers.isEmpty());
- }
-
- @Test
- @DisplayName("Should extract single header with normalized lowercase name")
- void extractHeaders_shouldExtractSingleHeader_withLowercaseName() throws MissingAuthorizationException {
- request.addHeader("Authorization", "Bearer token123");
-
- Map headers = filter.extractHeaders(request);
-
- assertEquals(1, headers.size());
- assertEquals("Bearer token123", headers.get("authorization"));
- }
-
- @Test
- @DisplayName("Should extract multiple headers with all names normalized to lowercase")
- void extractHeaders_shouldExtractMultipleHeaders_withNormalizedNames() throws MissingAuthorizationException {
- String bearerToken = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuYXV0aDAuY29tLyJ9.signature";
- String dpopProof = "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJqdGkiOiIxMjM0NSJ9.proof";
-
- request.addHeader("AuThOrIzAtIoN", bearerToken);
- request.addHeader("Content-Type", "application/json");
- request.addHeader("DPoP", dpopProof);
-
- Map headers = filter.extractHeaders(request);
-
- assertEquals(3, headers.size());
- assertEquals(bearerToken, headers.get("authorization"));
- assertEquals("application/json", headers.get("content-type"));
- assertEquals(dpopProof, headers.get("dpop"));
- }
-
- @Test
- @DisplayName("Should fail when multiple authorization headers are present")
- void extractHeaders_shouldThrowExceptionWhenMultipleAuthorizationHeadersArePresent(){
-
- when(auth0Properties.getDpopMode()).thenReturn(DPoPMode.REQUIRED);
- String dpopToken = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuYXV0aDAuY29tLyJ9.signature";
- String dpopProof = "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJqdGkiOiIxMjM0NSJ9.proof";
-
- request.addHeader("authorization", dpopToken);
- request.addHeader("Content-Type", "application/json");
- request.addHeader("DPoP", dpopProof);
- request.addHeader("Authorization", dpopToken);
-
- MissingAuthorizationException ex = assertThrows(
- MissingAuthorizationException.class,
- () -> filter.extractHeaders(request)
- );
-
- assertEquals(400, ex.getStatusCode());
- }
-
- @Test
- @DisplayName("Should return empty map when header enumeration is null")
- void extractHeaders_shouldReturnEmptyMap_whenHeaderEnumerationIsNull() throws MissingAuthorizationException {
- MockHttpServletRequest nullHeaderRequest = new MockHttpServletRequest() {
- @Override
- public Enumeration getHeaderNames() {
- return null;
- }
+ @Mock private Auth0Properties auth0Properties;
+
+ @Mock private AuthClient authClient;
+
+ @Mock private FilterChain filterChain;
+
+ private Auth0AuthenticationFilter filter;
+ private MockHttpServletRequest request;
+ private MockHttpServletResponse response;
+
+ @BeforeEach
+ void setUp() {
+ filter = new Auth0AuthenticationFilter(authClient, auth0Properties);
+ request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
+ }
+
+ @Test
+ @DisplayName("Should return empty map when request has no headers")
+ void extractHeaders_shouldReturnEmptyMap_whenNoHeadersPresent()
+ throws MissingAuthorizationException {
+ Map headers = filter.extractHeaders(request);
+
+ assertNotNull(headers);
+ assertTrue(headers.isEmpty());
+ }
+
+ @Test
+ @DisplayName("Should extract single header with normalized lowercase name")
+ void extractHeaders_shouldExtractSingleHeader_withLowercaseName()
+ throws MissingAuthorizationException {
+ request.addHeader("Authorization", "Bearer token123");
+
+ Map headers = filter.extractHeaders(request);
+
+ assertEquals(1, headers.size());
+ assertEquals("Bearer token123", headers.get("authorization"));
+ }
+
+ @Test
+ @DisplayName("Should extract multiple headers with all names normalized to lowercase")
+ void extractHeaders_shouldExtractMultipleHeaders_withNormalizedNames()
+ throws MissingAuthorizationException {
+ String bearerToken =
+ "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuYXV0aDAuY29tLyJ9.signature";
+ String dpopProof = "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJqdGkiOiIxMjM0NSJ9.proof";
+
+ request.addHeader("AuThOrIzAtIoN", bearerToken);
+ request.addHeader("Content-Type", "application/json");
+ request.addHeader("DPoP", dpopProof);
+
+ Map headers = filter.extractHeaders(request);
+
+ assertEquals(3, headers.size());
+ assertEquals(bearerToken, headers.get("authorization"));
+ assertEquals("application/json", headers.get("content-type"));
+ assertEquals(dpopProof, headers.get("dpop"));
+ }
+
+ @Test
+ @DisplayName("Should fail when multiple authorization headers are present")
+ void extractHeaders_shouldThrowExceptionWhenMultipleAuthorizationHeadersArePresent() {
+
+ when(auth0Properties.getDpopMode()).thenReturn(DPoPMode.REQUIRED);
+ String dpopToken =
+ "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuYXV0aDAuY29tLyJ9.signature";
+ String dpopProof = "eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0In0.eyJqdGkiOiIxMjM0NSJ9.proof";
+
+ request.addHeader("authorization", dpopToken);
+ request.addHeader("Content-Type", "application/json");
+ request.addHeader("DPoP", dpopProof);
+ request.addHeader("Authorization", dpopToken);
+
+ MissingAuthorizationException ex =
+ assertThrows(MissingAuthorizationException.class, () -> filter.extractHeaders(request));
+
+ assertEquals(400, ex.getStatusCode());
+ }
+
+ @Test
+ @DisplayName("Should return empty map when header enumeration is null")
+ void extractHeaders_shouldReturnEmptyMap_whenHeaderEnumerationIsNull()
+ throws MissingAuthorizationException {
+ MockHttpServletRequest nullHeaderRequest =
+ new MockHttpServletRequest() {
+ @Override
+ public Enumeration getHeaderNames() {
+ return null;
+ }
};
- Map headers = filter.extractHeaders(nullHeaderRequest);
-
- assertNotNull(headers);
- assertTrue(headers.isEmpty());
- }
-
- @Test
- @DisplayName("Should build HTTP URL with default port 80")
- void buildHtu_shouldBuildHttpUrl_withDefaultPort() {
- request.setScheme("http");
- request.setServerName("example.com");
- request.setServerPort(80);
- request.setRequestURI("/api/users");
-
- String htu = Auth0AuthenticationFilter.buildHtu(request);
-
- assertEquals("http://example.com/api/users", htu);
- }
-
- @Test
- @DisplayName("Should build HTTPS URL with default port 443")
- void buildHtu_shouldBuildHttpsUrl_withDefaultPort() {
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/v2/resource");
-
- String htu = Auth0AuthenticationFilter.buildHtu(request);
-
- assertEquals("https://api.example.com/v2/resource", htu);
- }
-
- @Test
- @DisplayName("Should build HTTP URL with non-default port")
- void buildHtu_shouldBuildHttpUrl_withNonDefaultPort() {
- request.setScheme("HtTp");
- request.setServerName("localhost");
- request.setServerPort(8080);
- request.setRequestURI("/test");
-
- String htu = Auth0AuthenticationFilter.buildHtu(request);
-
- assertEquals("http://localhost:8080/test", htu);
- }
-
- @Test
- @DisplayName("Should build HTTPS URL with non-default port")
- void buildHtu_shouldBuildHttpsUrl_withNonDefaultPort() {
- request.setScheme("https");
- request.setServerName("secure.example.com");
- request.setServerPort(8443);
- request.setRequestURI("/api/data");
-
- String htu = Auth0AuthenticationFilter.buildHtu(request);
-
- assertEquals("https://secure.example.com:8443/api/data", htu);
- }
-
- @Test
- @DisplayName("Should normalize scheme and host to lowercase")
- void buildHtu_shouldNormalizeSchemeAndHost_toLowerCase() {
- request.setScheme("HTTPS");
- request.setServerName("API.EXAMPLE.COM");
- request.setServerPort(443);
- request.setRequestURI("/Resource");
-
- String htu = Auth0AuthenticationFilter.buildHtu(request);
-
- assertEquals("https://api.example.com/Resource", htu);
- }
-
- @Test
- @DisplayName("Should create HttpRequestInfo with GET method and built HTU")
- void extractRequestInfo_shouldCreateHttpRequestInfo_withGetMethod() {
- request.setMethod("GET");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/users");
-
- HttpRequestInfo requestInfo = filter.extractRequestInfo(request);
-
- assertNotNull(requestInfo);
- assertEquals("GET", requestInfo.getHttpMethod());
- assertEquals("https://api.example.com/api/users", requestInfo.getHttpUrl());
- }
-
- @Test
- @DisplayName("Should authenticate successfully with valid Bearer token and set security context")
- void doFilterInternal_shouldAuthenticateSuccessfully_withValidBearerToken() throws Exception {
- request.setMethod("GET");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/users");
- request.addHeader("Authorization", "Bearer valid_token");
-
- AuthenticationContext mockContext = org.mockito.Mockito.mock(AuthenticationContext.class);
- when(authClient.verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- )).thenReturn(mockContext);
-
- filter.doFilterInternal(request, response, filterChain);
-
- org.mockito.Mockito.verify(authClient).verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- );
- org.mockito.Mockito.verify(filterChain).doFilter(request, response);
- assertNotNull(SecurityContextHolder.getContext().getAuthentication());
- assertTrue(SecurityContextHolder.getContext().getAuthentication() instanceof Auth0AuthenticationToken);
- }
-
- @Test
- @DisplayName("Should authenticate successfully with valid DPoP token and proof")
- void doFilterInternal_shouldAuthenticateSuccessfully_withValidDpopToken() throws Exception {
- request.setMethod("POST");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/resource");
- request.addHeader("Authorization", "DPoP dpop_token");
- request.addHeader("DPoP", "dpop_proof_jwt");
-
- AuthenticationContext mockContext = org.mockito.Mockito.mock(AuthenticationContext.class);
- when(authClient.verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- )).thenReturn(mockContext);
-
- filter.doFilterInternal(request, response, filterChain);
-
- org.mockito.Mockito.verify(filterChain).doFilter(request, response);
- assertNotNull(SecurityContextHolder.getContext().getAuthentication());
- }
-
- @Test
- @DisplayName("Should handle missing authorization header by returning 200 status")
- void doFilterInternal_shouldReturn200_whenAuthorizationHeaderMissing() throws Exception {
- request.setMethod("GET");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/users");
-
- filter.doFilterInternal(request, response, filterChain);
-
- assertEquals(200, response.getStatus());
- org.mockito.Mockito.verify(filterChain).doFilter(request, response);
- assertNull(SecurityContextHolder.getContext().getAuthentication());
- }
-
- @Test
- @DisplayName("Should handle invalid token by returning 401 status and clearing context")
- void doFilterInternal_shouldReturn401AndClearContext_withInvalidToken() throws Exception {
- request.setMethod("GET");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/users");
- request.addHeader("Authorization", "Bearer invalid_token");
-
- when(authClient.verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- )).thenThrow(new com.auth0.exception.VerifyAccessTokenException("Invalid JWT signature"));
-
- filter.doFilterInternal(request, response, filterChain);
-
- assertEquals(401, response.getStatus());
- org.mockito.Mockito.verify(filterChain, org.mockito.Mockito.never()).doFilter(request, response);
- assertNull(SecurityContextHolder.getContext().getAuthentication());
- }
-
- @Test
- @DisplayName("Should handle insufficient scope by returning 403 status")
- void doFilterInternal_shouldReturn403_withInsufficientScope() throws Exception {
- request.setMethod("POST");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/admin");
- request.addHeader("Authorization", "Bearer valid_token");
-
- when(authClient.verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- )).thenThrow(new com.auth0.exception.InsufficientScopeException("Insufficient scope"));
-
- filter.doFilterInternal(request, response, filterChain);
-
- assertEquals(403, response.getStatus());
- org.mockito.Mockito.verify(filterChain, org.mockito.Mockito.never()).doFilter(request, response);
- assertNull(SecurityContextHolder.getContext().getAuthentication());
- }
-
- @Test
- @DisplayName("Should add WWW-Authenticate header when present in exception")
- void doFilterInternal_shouldAddWwwAuthenticateHeader_whenPresentInException() throws Exception {
- request.setMethod("GET");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/users");
- request.addHeader("Authorization", "Bearer expired_token");
-
- Map exceptionHeaders = new java.util.HashMap<>();
- exceptionHeaders.put("WWW-Authenticate", "Bearer realm=\"api\", error=\"invalid_token\"");
-
- // Simulating an exception that would be thrown by the authClient
- com.auth0.exception.VerifyAccessTokenException exception =
- new com.auth0.exception.VerifyAccessTokenException("Token expired");
- exception.addHeader("WWW-Authenticate", "Bearer realm=\"api\", error=\"invalid_token\"");
-
- when(authClient.verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- )).thenThrow(exception);
-
- filter.doFilterInternal(request, response, filterChain);
-
- assertEquals(401, response.getStatus());
- assertEquals("Bearer realm=\"api\", error=\"invalid_token\"",
- response.getHeader("WWW-Authenticate"));
- assertNull(SecurityContextHolder.getContext().getAuthentication());
- }
-
- @Test
- @DisplayName("Should not add WWW-Authenticate header when not present in exception")
- void doFilterInternal_shouldNotAddWwwAuthenticateHeader_whenNotPresentInException() throws Exception {
- request.setMethod("GET");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/users");
- request.addHeader("Authorization", "Bearer malformed_token");
-
- com.auth0.exception.VerifyAccessTokenException exception =
- new com.auth0.exception.VerifyAccessTokenException("Malformed token");
-
- when(authClient.verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- )).thenThrow(exception);
-
- filter.doFilterInternal(request, response, filterChain);
-
- assertEquals(401, response.getStatus());
- assertNull(response.getHeader("WWW-Authenticate"));
- assertNull(SecurityContextHolder.getContext().getAuthentication());
- }
-
- @Test
- @DisplayName("Should set authentication details from request")
- void doFilterInternal_shouldSetAuthenticationDetails_fromRequest() throws Exception {
- request.setMethod("GET");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/users");
- request.setRemoteAddr("192.168.1.100");
- request.addHeader("Authorization", "Bearer valid_token");
-
- AuthenticationContext mockContext = org.mockito.Mockito.mock(AuthenticationContext.class);
- when(authClient.verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- )).thenReturn(mockContext);
-
- filter.doFilterInternal(request, response, filterChain);
-
- Auth0AuthenticationToken auth =
- (Auth0AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
- assertNotNull(auth.getDetails());
- }
-
- @Test
- @DisplayName("Should handle DPoP validation exception and return appropriate status")
- void doFilterInternal_shouldHandleDpopValidationException_withProperStatus() throws Exception {
- request.setMethod("POST");
- request.setScheme("https");
- request.setServerName("api.example.com");
- request.setServerPort(443);
- request.setRequestURI("/api/resource");
- request.addHeader("Authorization", "DPoP dpop_token");
- request.addHeader("DPoP", "invalid_proof");
-
- Map exceptionHeaders = new java.util.HashMap<>();
- exceptionHeaders.put("WWW-Authenticate", "DPoP error=\"invalid_dpop_proof\"");
-
- // Simulating an exception that would be thrown by the authClient
- com.auth0.exception.InvalidDpopProofException exception =
- new com.auth0.exception.InvalidDpopProofException("Invalid DPoP proof");
- exception.addHeader("WWW-Authenticate", "DPoP error=\"invalid_dpop_proof\"");
-
- when(authClient.verifyRequest(
- org.mockito.ArgumentMatchers.anyMap(),
- org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)
- )).thenThrow(exception);
-
- filter.doFilterInternal(request, response, filterChain);
-
- assertEquals(400, response.getStatus());
- assertEquals("DPoP error=\"invalid_dpop_proof\"", response.getHeader("WWW-Authenticate"));
- assertNull(SecurityContextHolder.getContext().getAuthentication());
- }
+ Map headers = filter.extractHeaders(nullHeaderRequest);
+
+ assertNotNull(headers);
+ assertTrue(headers.isEmpty());
+ }
+
+ @Test
+ @DisplayName("Should build HTTP URL with default port 80")
+ void buildHtu_shouldBuildHttpUrl_withDefaultPort() {
+ request.setScheme("http");
+ request.setServerName("example.com");
+ request.setServerPort(80);
+ request.setRequestURI("/api/users");
+
+ String htu = Auth0AuthenticationFilter.buildHtu(request);
+
+ assertEquals("http://example.com/api/users", htu);
+ }
+
+ @Test
+ @DisplayName("Should build HTTPS URL with default port 443")
+ void buildHtu_shouldBuildHttpsUrl_withDefaultPort() {
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/v2/resource");
+
+ String htu = Auth0AuthenticationFilter.buildHtu(request);
+
+ assertEquals("https://api.example.com/v2/resource", htu);
+ }
+
+ @Test
+ @DisplayName("Should build HTTP URL with non-default port")
+ void buildHtu_shouldBuildHttpUrl_withNonDefaultPort() {
+ request.setScheme("HtTp");
+ request.setServerName("localhost");
+ request.setServerPort(8080);
+ request.setRequestURI("/test");
+
+ String htu = Auth0AuthenticationFilter.buildHtu(request);
+
+ assertEquals("http://localhost:8080/test", htu);
+ }
+
+ @Test
+ @DisplayName("Should build HTTPS URL with non-default port")
+ void buildHtu_shouldBuildHttpsUrl_withNonDefaultPort() {
+ request.setScheme("https");
+ request.setServerName("secure.example.com");
+ request.setServerPort(8443);
+ request.setRequestURI("/api/data");
+
+ String htu = Auth0AuthenticationFilter.buildHtu(request);
+
+ assertEquals("https://secure.example.com:8443/api/data", htu);
+ }
+
+ @Test
+ @DisplayName("Should normalize scheme and host to lowercase")
+ void buildHtu_shouldNormalizeSchemeAndHost_toLowerCase() {
+ request.setScheme("HTTPS");
+ request.setServerName("API.EXAMPLE.COM");
+ request.setServerPort(443);
+ request.setRequestURI("/Resource");
+
+ String htu = Auth0AuthenticationFilter.buildHtu(request);
+
+ assertEquals("https://api.example.com/Resource", htu);
+ }
+
+ @Test
+ @DisplayName("Should create HttpRequestInfo with GET method and built HTU")
+ void extractRequestInfo_shouldCreateHttpRequestInfo_withGetMethod() {
+ request.setMethod("GET");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/users");
+
+ HttpRequestInfo requestInfo = filter.extractRequestInfo(request);
+
+ assertNotNull(requestInfo);
+ assertEquals("GET", requestInfo.getHttpMethod());
+ assertEquals("https://api.example.com/api/users", requestInfo.getHttpUrl());
+ }
+
+ @Test
+ @DisplayName("Should authenticate successfully with valid Bearer token and set security context")
+ void doFilterInternal_shouldAuthenticateSuccessfully_withValidBearerToken() throws Exception {
+ request.setMethod("GET");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/users");
+ request.addHeader("Authorization", "Bearer valid_token");
+
+ AuthenticationContext mockContext = org.mockito.Mockito.mock(AuthenticationContext.class);
+ when(authClient.verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)))
+ .thenReturn(mockContext);
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ org.mockito.Mockito.verify(authClient)
+ .verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class));
+ org.mockito.Mockito.verify(filterChain).doFilter(request, response);
+ assertNotNull(SecurityContextHolder.getContext().getAuthentication());
+ assertTrue(
+ SecurityContextHolder.getContext().getAuthentication() instanceof Auth0AuthenticationToken);
+ }
+
+ @Test
+ @DisplayName("Should authenticate successfully with valid DPoP token and proof")
+ void doFilterInternal_shouldAuthenticateSuccessfully_withValidDpopToken() throws Exception {
+ request.setMethod("POST");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/resource");
+ request.addHeader("Authorization", "DPoP dpop_token");
+ request.addHeader("DPoP", "dpop_proof_jwt");
+
+ AuthenticationContext mockContext = org.mockito.Mockito.mock(AuthenticationContext.class);
+ when(authClient.verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)))
+ .thenReturn(mockContext);
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ org.mockito.Mockito.verify(filterChain).doFilter(request, response);
+ assertNotNull(SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ @Test
+ @DisplayName("Should handle missing authorization header by returning 200 status")
+ void doFilterInternal_shouldReturn200_whenAuthorizationHeaderMissing() throws Exception {
+ request.setMethod("GET");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/users");
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ assertEquals(200, response.getStatus());
+ org.mockito.Mockito.verify(filterChain).doFilter(request, response);
+ assertNull(SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ @Test
+ @DisplayName("Should handle invalid token by returning 401 status and clearing context")
+ void doFilterInternal_shouldReturn401AndClearContext_withInvalidToken() throws Exception {
+ request.setMethod("GET");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/users");
+ request.addHeader("Authorization", "Bearer invalid_token");
+
+ when(authClient.verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)))
+ .thenThrow(new com.auth0.exception.VerifyAccessTokenException("Invalid JWT signature"));
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ assertEquals(401, response.getStatus());
+ org.mockito.Mockito.verify(filterChain, org.mockito.Mockito.never())
+ .doFilter(request, response);
+ assertNull(SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ @Test
+ @DisplayName("Should handle insufficient scope by returning 403 status")
+ void doFilterInternal_shouldReturn403_withInsufficientScope() throws Exception {
+ request.setMethod("POST");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/admin");
+ request.addHeader("Authorization", "Bearer valid_token");
+
+ when(authClient.verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)))
+ .thenThrow(new com.auth0.exception.InsufficientScopeException("Insufficient scope"));
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ assertEquals(403, response.getStatus());
+ org.mockito.Mockito.verify(filterChain, org.mockito.Mockito.never())
+ .doFilter(request, response);
+ assertNull(SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ @Test
+ @DisplayName("Should add WWW-Authenticate header when present in exception")
+ void doFilterInternal_shouldAddWwwAuthenticateHeader_whenPresentInException() throws Exception {
+ request.setMethod("GET");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/users");
+ request.addHeader("Authorization", "Bearer expired_token");
+
+ Map exceptionHeaders = new java.util.HashMap<>();
+ exceptionHeaders.put("WWW-Authenticate", "Bearer realm=\"api\", error=\"invalid_token\"");
+
+ // Simulating an exception that would be thrown by the authClient
+ com.auth0.exception.VerifyAccessTokenException exception =
+ new com.auth0.exception.VerifyAccessTokenException("Token expired");
+ exception.addHeader("WWW-Authenticate", "Bearer realm=\"api\", error=\"invalid_token\"");
+
+ when(authClient.verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)))
+ .thenThrow(exception);
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ assertEquals(401, response.getStatus());
+ assertEquals(
+ "Bearer realm=\"api\", error=\"invalid_token\"", response.getHeader("WWW-Authenticate"));
+ assertNull(SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ @Test
+ @DisplayName("Should not add WWW-Authenticate header when not present in exception")
+ void doFilterInternal_shouldNotAddWwwAuthenticateHeader_whenNotPresentInException()
+ throws Exception {
+ request.setMethod("GET");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/users");
+ request.addHeader("Authorization", "Bearer malformed_token");
+
+ com.auth0.exception.VerifyAccessTokenException exception =
+ new com.auth0.exception.VerifyAccessTokenException("Malformed token");
+
+ when(authClient.verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)))
+ .thenThrow(exception);
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ assertEquals(401, response.getStatus());
+ assertNull(response.getHeader("WWW-Authenticate"));
+ assertNull(SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ @Test
+ @DisplayName("Should set authentication details from request")
+ void doFilterInternal_shouldSetAuthenticationDetails_fromRequest() throws Exception {
+ request.setMethod("GET");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/users");
+ request.setRemoteAddr("192.168.1.100");
+ request.addHeader("Authorization", "Bearer valid_token");
+
+ AuthenticationContext mockContext = org.mockito.Mockito.mock(AuthenticationContext.class);
+ when(authClient.verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)))
+ .thenReturn(mockContext);
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ Auth0AuthenticationToken auth =
+ (Auth0AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
+ assertNotNull(auth.getDetails());
+ }
+
+ @Test
+ @DisplayName("Should handle DPoP validation exception and return appropriate status")
+ void doFilterInternal_shouldHandleDpopValidationException_withProperStatus() throws Exception {
+ request.setMethod("POST");
+ request.setScheme("https");
+ request.setServerName("api.example.com");
+ request.setServerPort(443);
+ request.setRequestURI("/api/resource");
+ request.addHeader("Authorization", "DPoP dpop_token");
+ request.addHeader("DPoP", "invalid_proof");
+
+ Map exceptionHeaders = new java.util.HashMap<>();
+ exceptionHeaders.put("WWW-Authenticate", "DPoP error=\"invalid_dpop_proof\"");
+
+ // Simulating an exception that would be thrown by the authClient
+ com.auth0.exception.InvalidDpopProofException exception =
+ new com.auth0.exception.InvalidDpopProofException("Invalid DPoP proof");
+ exception.addHeader("WWW-Authenticate", "DPoP error=\"invalid_dpop_proof\"");
+
+ when(authClient.verifyRequest(
+ org.mockito.ArgumentMatchers.anyMap(),
+ org.mockito.ArgumentMatchers.any(HttpRequestInfo.class)))
+ .thenThrow(exception);
+
+ filter.doFilterInternal(request, response, filterChain);
+
+ assertEquals(400, response.getStatus());
+ assertEquals("DPoP error=\"invalid_dpop_proof\"", response.getHeader("WWW-Authenticate"));
+ assertNull(SecurityContextHolder.getContext().getAuthentication());
+ }
}
-
diff --git a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AuthenticationTokenTest.java b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AuthenticationTokenTest.java
index 06bcb22..eec1072 100644
--- a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AuthenticationTokenTest.java
+++ b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AuthenticationTokenTest.java
@@ -1,192 +1,198 @@
package com.auth0.spring.boot;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import com.auth0.models.AuthenticationContext;
+import java.util.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import java.util.*;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-/**
- * Test cases for Auth0AuthenticationToken
- */
+/** Test cases for Auth0AuthenticationToken */
class Auth0AuthenticationTokenTest {
- @Test
- @DisplayName("Should create SCOPE_ prefixed authorities from single scope in scope claim")
- void createAuthorities_shouldCreateScopePrefixedAuthorities_withSingleScope() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("scope", "read:users");
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(1, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
- }
-
- @Test
- @DisplayName("Should create multiple SCOPE_ prefixed authorities from space-separated scopes")
- void createAuthorities_shouldCreateMultipleAuthorities_withSpaceSeparatedScopes() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("scope", "read:users write:users delete:users");
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(3, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_write:users")));
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_delete:users")));
- }
-
- @Test
- @DisplayName("Should return ROLE_USER when scope claim is missing")
- void createAuthorities_shouldReturnRoleUser_whenScopeClaimMissing() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(1, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("ROLE_USER")));
- }
-
- @Test
- @DisplayName("Should return ROLE_USER when scope claim is null")
- void createAuthorities_shouldReturnRoleUser_whenScopeClaimIsNull() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("scope", null);
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(1, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("ROLE_USER")));
- }
-
- @Test
- @DisplayName("Should return ROLE_USER when scope claim is not a String")
- void createAuthorities_shouldReturnRoleUser_whenScopeClaimIsNotString() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("scope", Arrays.asList("read:users", "write:users"));
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(1, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("ROLE_USER")));
- }
-
- @Test
- @DisplayName("Should return ROLE_USER when scope claim is empty string")
- void createAuthorities_shouldReturnRoleUser_whenScopeClaimIsEmptyString() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("scope", "");
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(1, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("ROLE_USER")));
- }
-
- @Test
- @DisplayName("Should handle multiple consecutive spaces between scopes")
- void createAuthorities_shouldHandleMultipleSpaces_betweenScopes() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("scope", "read:users write:users delete:users");
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(3, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_write:users")));
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_delete:users")));
- }
-
- @Test
- @DisplayName("Should set principal to sub claim value from authentication context")
- void constructor_shouldSetPrincipal_fromSubClaim() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("sub", "auth0|123456789");
- claims.put("scope", "read:users");
- when(context.getClaims()).thenReturn(claims);
-
- Auth0AuthenticationToken token = new Auth0AuthenticationToken(context);
-
- assertEquals("auth0|123456789", token.getPrincipal());
- }
-
- @Test
- @DisplayName("Should set authentication as authenticated on construction")
- void constructor_shouldSetAuthenticated_toTrue() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("sub", "auth0|123456789");
- claims.put("scope", "read:users");
- when(context.getClaims()).thenReturn(claims);
-
- Auth0AuthenticationToken token = new Auth0AuthenticationToken(context);
-
- assertTrue(token.isAuthenticated());
- }
-
- @Test
- @DisplayName("Should return null for credentials")
- void getCredentials_shouldReturnNull() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("sub", "auth0|123456789");
- when(context.getClaims()).thenReturn(claims);
-
- Auth0AuthenticationToken token = new Auth0AuthenticationToken(context);
-
- assertNull(token.getCredentials());
- }
-
- @Test
- @DisplayName("Should create authorities with scopes containing special characters")
- void createAuthorities_shouldHandleSpecialCharacters_inScopes() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("scope", "read:users write:admin-panel delete:resource/123");
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(3, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_write:admin-panel")));
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_delete:resource/123")));
- }
-
- @Test
- @DisplayName("Should handle scope claim with leading and trailing whitespace")
- void createAuthorities_shouldHandleWhitespace_aroundScopes() {
- AuthenticationContext context = mock(AuthenticationContext.class);
- Map claims = new HashMap<>();
- claims.put("scope", " read:users write:users ");
- when(context.getClaims()).thenReturn(claims);
-
- Collection extends GrantedAuthority> authorities = Auth0AuthenticationToken.createAuthorities(context);
-
- assertEquals(2, authorities.size());
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
- assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_write:users")));
- }
-}
\ No newline at end of file
+ @Test
+ @DisplayName("Should create SCOPE_ prefixed authorities from single scope in scope claim")
+ void createAuthorities_shouldCreateScopePrefixedAuthorities_withSingleScope() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("scope", "read:users");
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(1, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
+ }
+
+ @Test
+ @DisplayName("Should create multiple SCOPE_ prefixed authorities from space-separated scopes")
+ void createAuthorities_shouldCreateMultipleAuthorities_withSpaceSeparatedScopes() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("scope", "read:users write:users delete:users");
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(3, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_write:users")));
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_delete:users")));
+ }
+
+ @Test
+ @DisplayName("Should return ROLE_USER when scope claim is missing")
+ void createAuthorities_shouldReturnRoleUser_whenScopeClaimMissing() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(1, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("ROLE_USER")));
+ }
+
+ @Test
+ @DisplayName("Should return ROLE_USER when scope claim is null")
+ void createAuthorities_shouldReturnRoleUser_whenScopeClaimIsNull() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("scope", null);
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(1, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("ROLE_USER")));
+ }
+
+ @Test
+ @DisplayName("Should return ROLE_USER when scope claim is not a String")
+ void createAuthorities_shouldReturnRoleUser_whenScopeClaimIsNotString() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("scope", Arrays.asList("read:users", "write:users"));
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(1, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("ROLE_USER")));
+ }
+
+ @Test
+ @DisplayName("Should return ROLE_USER when scope claim is empty string")
+ void createAuthorities_shouldReturnRoleUser_whenScopeClaimIsEmptyString() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("scope", "");
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(1, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("ROLE_USER")));
+ }
+
+ @Test
+ @DisplayName("Should handle multiple consecutive spaces between scopes")
+ void createAuthorities_shouldHandleMultipleSpaces_betweenScopes() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("scope", "read:users write:users delete:users");
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(3, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_write:users")));
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_delete:users")));
+ }
+
+ @Test
+ @DisplayName("Should set principal to sub claim value from authentication context")
+ void constructor_shouldSetPrincipal_fromSubClaim() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("sub", "auth0|123456789");
+ claims.put("scope", "read:users");
+ when(context.getClaims()).thenReturn(claims);
+
+ Auth0AuthenticationToken token = new Auth0AuthenticationToken(context);
+
+ assertEquals("auth0|123456789", token.getPrincipal());
+ }
+
+ @Test
+ @DisplayName("Should set authentication as authenticated on construction")
+ void constructor_shouldSetAuthenticated_toTrue() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("sub", "auth0|123456789");
+ claims.put("scope", "read:users");
+ when(context.getClaims()).thenReturn(claims);
+
+ Auth0AuthenticationToken token = new Auth0AuthenticationToken(context);
+
+ assertTrue(token.isAuthenticated());
+ }
+
+ @Test
+ @DisplayName("Should return null for credentials")
+ void getCredentials_shouldReturnNull() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("sub", "auth0|123456789");
+ when(context.getClaims()).thenReturn(claims);
+
+ Auth0AuthenticationToken token = new Auth0AuthenticationToken(context);
+
+ assertNull(token.getCredentials());
+ }
+
+ @Test
+ @DisplayName("Should create authorities with scopes containing special characters")
+ void createAuthorities_shouldHandleSpecialCharacters_inScopes() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("scope", "read:users write:admin-panel delete:resource/123");
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(3, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_write:admin-panel")));
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_delete:resource/123")));
+ }
+
+ @Test
+ @DisplayName("Should handle scope claim with leading and trailing whitespace")
+ void createAuthorities_shouldHandleWhitespace_aroundScopes() {
+ AuthenticationContext context = mock(AuthenticationContext.class);
+ Map claims = new HashMap<>();
+ claims.put("scope", " read:users write:users ");
+ when(context.getClaims()).thenReturn(claims);
+
+ Collection extends GrantedAuthority> authorities =
+ Auth0AuthenticationToken.createAuthorities(context);
+
+ assertEquals(2, authorities.size());
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_read:users")));
+ assertTrue(authorities.contains(new SimpleGrantedAuthority("SCOPE_write:users")));
+ }
+}
diff --git a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AutoConfigurationTest.java b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AutoConfigurationTest.java
index d13fe08..a823fc6 100644
--- a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AutoConfigurationTest.java
+++ b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0AutoConfigurationTest.java
@@ -1,8 +1,10 @@
package com.auth0.spring.boot;
+import static org.junit.jupiter.api.Assertions.*;
+
import com.auth0.AuthClient;
-import com.auth0.models.AuthOptions;
import com.auth0.enums.DPoPMode;
+import com.auth0.models.AuthOptions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -11,129 +13,124 @@
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestPropertySource;
-import static org.junit.jupiter.api.Assertions.*;
-
/**
* Test cases for Auth0AutoConfiguration
+ *
*
*/
@SpringBootTest
-@TestPropertySource(properties = {
- "auth0.domain=test-domain.auth0.com",
- "auth0.audience=https://api.example.com"
-})
+@TestPropertySource(
+ properties = {"auth0.domain=test-domain.auth0.com", "auth0.audience=https://api.example.com"})
class Auth0AutoConfigurationTest {
- @Autowired
- private ApplicationContext context;
-
- @Autowired
- private AuthOptions authOptions;
-
- @Autowired
- private AuthClient authClient;
+ @Autowired private ApplicationContext context;
+
+ @Autowired private AuthOptions authOptions;
+
+ @Autowired private AuthClient authClient;
+
+ @Test
+ @DisplayName("Should create AuthOptions bean with required domain and audience properties")
+ void shouldCreateAuthOptionsBean() {
+ assertNotNull(authOptions);
+ assertEquals("test-domain.auth0.com", authOptions.getDomain());
+ assertEquals("https://api.example.com", authOptions.getAudience());
+ }
+
+ @Test
+ @DisplayName("Should create AuthClient bean configured with AuthOptions")
+ void shouldCreateAuthClientBean() {
+ assertNotNull(authClient);
+ assertTrue(context.containsBean("authClient"));
+ }
+
+ @Test
+ @DisplayName("Should register all auto-configuration beans in application context")
+ void shouldRegisterAllBeansInContext() {
+ assertTrue(context.containsBean("authOptions"));
+ assertTrue(context.containsBean("authClient"));
+ }
+
+ @Nested
+ @SpringBootTest
+ @TestPropertySource(
+ properties = {
+ "auth0.domain=dpop-test.auth0.com",
+ "auth0.audience=https://api.dpop.com",
+ "auth0.dpop-mode=REQUIRED",
+ "auth0.dpop-iat-leeway-seconds=10",
+ "auth0.dpop-iat-offset-seconds=300"
+ })
+ class DPoPConfigurationTest {
+
+ @Autowired private AuthOptions authOptions;
@Test
- @DisplayName("Should create AuthOptions bean with required domain and audience properties")
- void shouldCreateAuthOptionsBean() {
- assertNotNull(authOptions);
- assertEquals("test-domain.auth0.com", authOptions.getDomain());
- assertEquals("https://api.example.com", authOptions.getAudience());
+ @DisplayName("Should configure AuthOptions with DPoP mode when dpop-mode property is set")
+ void shouldConfigureDPoPMode() {
+ assertNotNull(authOptions);
+ assertEquals(DPoPMode.REQUIRED, authOptions.getDpopMode());
}
@Test
- @DisplayName("Should create AuthClient bean configured with AuthOptions")
- void shouldCreateAuthClientBean() {
- assertNotNull(authClient);
- assertTrue(context.containsBean("authClient"));
+ @DisplayName("Should configure AuthOptions with DPoP IAT leeway seconds when property is set")
+ void shouldConfigureDPoPIatLeewaySeconds() {
+ assertNotNull(authOptions);
+ assertEquals(10, authOptions.getDpopIatLeewaySeconds());
}
@Test
- @DisplayName("Should register all auto-configuration beans in application context")
- void shouldRegisterAllBeansInContext() {
- assertTrue(context.containsBean("authOptions"));
- assertTrue(context.containsBean("authClient"));
+ @DisplayName("Should configure AuthOptions with DPoP IAT offset seconds when property is set")
+ void shouldConfigureDPoPIatOffsetSeconds() {
+ assertNotNull(authOptions);
+ assertEquals(300, authOptions.getDpopIatOffsetSeconds());
}
+ }
- @Nested
- @SpringBootTest
- @TestPropertySource(properties = {
- "auth0.domain=dpop-test.auth0.com",
- "auth0.audience=https://api.dpop.com",
- "auth0.dpop-mode=REQUIRED",
- "auth0.dpop-iat-leeway-seconds=10",
- "auth0.dpop-iat-offset-seconds=300"
- })
- class DPoPConfigurationTest {
-
- @Autowired
- private AuthOptions authOptions;
-
- @Test
- @DisplayName("Should configure AuthOptions with DPoP mode when dpop-mode property is set")
- void shouldConfigureDPoPMode() {
- assertNotNull(authOptions);
- assertEquals(DPoPMode.REQUIRED, authOptions.getDpopMode());
- }
-
- @Test
- @DisplayName("Should configure AuthOptions with DPoP IAT leeway seconds when property is set")
- void shouldConfigureDPoPIatLeewaySeconds() {
- assertNotNull(authOptions);
- assertEquals(10, authOptions.getDpopIatLeewaySeconds());
- }
-
- @Test
- @DisplayName("Should configure AuthOptions with DPoP IAT offset seconds when property is set")
- void shouldConfigureDPoPIatOffsetSeconds() {
- assertNotNull(authOptions);
- assertEquals(300, authOptions.getDpopIatOffsetSeconds());
- }
- }
+ @Nested
+ @SpringBootTest
+ @TestPropertySource(
+ properties = {
+ "auth0.domain=minimal-test.auth0.com",
+ "auth0.audience=https://api.minimal.com"
+ })
+ class MinimalConfigurationTest {
+ @Autowired private AuthOptions authOptions;
- @Nested
- @SpringBootTest
- @TestPropertySource(properties = {
- "auth0.domain=minimal-test.auth0.com",
- "auth0.audience=https://api.minimal.com"
- })
- class MinimalConfigurationTest {
-
- @Autowired
- private AuthOptions authOptions;
-
- @Test
- @DisplayName("Should configure AuthOptions with default DPoP settings when no DPoP properties are set")
- void shouldUseDefaultDPoPSettings() {
- assertNotNull(authOptions);
- assertEquals(DPoPMode.ALLOWED, authOptions.getDpopMode());
- assertEquals(30, authOptions.getDpopIatLeewaySeconds());
- assertEquals(300, authOptions.getDpopIatOffsetSeconds());
- }
+ @Test
+ @DisplayName(
+ "Should configure AuthOptions with default DPoP settings when no DPoP properties are set")
+ void shouldUseDefaultDPoPSettings() {
+ assertNotNull(authOptions);
+ assertEquals(DPoPMode.ALLOWED, authOptions.getDpopMode());
+ assertEquals(30, authOptions.getDpopIatLeewaySeconds());
+ assertEquals(300, authOptions.getDpopIatOffsetSeconds());
}
+ }
- @Nested
- @SpringBootTest
- @TestPropertySource(properties = {
- "auth0.domain=partial-dpop.auth0.com",
- "auth0.audience=https://api.partial.com",
- "auth0.dpop-mode=ALLOWED"
- })
- class PartialDPoPConfigurationTest {
-
- @Autowired
- private AuthOptions authOptions;
-
- @Test
- @DisplayName("Should configure AuthOptions with only DPoP mode when other DPoP properties are not set")
- void shouldConfigureOnlyDPoPMode() {
- assertNotNull(authOptions);
- assertEquals(DPoPMode.ALLOWED, authOptions.getDpopMode());
-
- // Others should be set to their respective defaults
- assertEquals(30, authOptions.getDpopIatLeewaySeconds());
- assertEquals(300, authOptions.getDpopIatOffsetSeconds());
- }
+ @Nested
+ @SpringBootTest
+ @TestPropertySource(
+ properties = {
+ "auth0.domain=partial-dpop.auth0.com",
+ "auth0.audience=https://api.partial.com",
+ "auth0.dpop-mode=ALLOWED"
+ })
+ class PartialDPoPConfigurationTest {
+
+ @Autowired private AuthOptions authOptions;
+
+ @Test
+ @DisplayName(
+ "Should configure AuthOptions with only DPoP mode when other DPoP properties are not set")
+ void shouldConfigureOnlyDPoPMode() {
+ assertNotNull(authOptions);
+ assertEquals(DPoPMode.ALLOWED, authOptions.getDpopMode());
+
+ // Others should be set to their respective defaults
+ assertEquals(30, authOptions.getDpopIatLeewaySeconds());
+ assertEquals(300, authOptions.getDpopIatOffsetSeconds());
}
-}
\ No newline at end of file
+ }
+}
diff --git a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0PropertiesTest.java b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0PropertiesTest.java
index bdca5f1..01687b1 100644
--- a/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0PropertiesTest.java
+++ b/auth0-springboot-api/src/test/java/com/auth0/spring/boot/Auth0PropertiesTest.java
@@ -1,126 +1,120 @@
package com.auth0.spring.boot;
+import static org.junit.jupiter.api.Assertions.*;
+
import com.auth0.enums.DPoPMode;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Test cases for Auth0Properties
- */
+/** Test cases for Auth0Properties */
class Auth0PropertiesTest {
- private Auth0Properties properties;
-
- @BeforeEach
- void setUp() {
- properties = new Auth0Properties();
- }
-
- @Test
- @DisplayName("Should set and get domain property")
- void shouldSetAndGetDomain() {
- String domain = "dev-tenant.us.auth0.com";
- properties.setDomain(domain);
-
- assertEquals(domain, properties.getDomain());
- }
-
- @Test
- @DisplayName("Should set and get audience property")
- void shouldSetAndGetAudience() {
- String audience = "https://api.example.com/v2/";
- properties.setAudience(audience);
-
- assertEquals(audience, properties.getAudience());
- }
-
- @Test
- @DisplayName("Should set and get dpopMode property")
- void shouldSetAndGetDpopMode() {
- properties.setDpopMode(DPoPMode.REQUIRED);
- assertEquals(DPoPMode.REQUIRED, properties.getDpopMode());
-
- properties.setDpopMode(DPoPMode.ALLOWED);
- assertEquals(DPoPMode.ALLOWED, properties.getDpopMode());
-
- properties.setDpopMode(DPoPMode.DISABLED);
- assertEquals(DPoPMode.DISABLED, properties.getDpopMode());
- }
-
- @Test
- @DisplayName("Should set and get dpopIatOffsetSeconds property")
- void shouldSetAndGetDpopIatOffsetSeconds() {
- Long offsetSeconds = 300L;
- properties.setDpopIatOffsetSeconds(offsetSeconds);
-
- assertEquals(offsetSeconds, properties.getDpopIatOffsetSeconds());
- }
-
- @Test
- @DisplayName("Should set and get dpopIatLeewaySeconds property")
- void shouldSetAndGetDpopIatLeewaySeconds() {
- Long leewaySeconds = 60L;
- properties.setDpopIatLeewaySeconds(leewaySeconds);
-
- assertEquals(leewaySeconds, properties.getDpopIatLeewaySeconds());
- }
-
- @Test
- @DisplayName("Should have null default values for all properties")
- void shouldHaveNullDefaultValues() {
- assertNull(properties.getDomain());
- assertNull(properties.getAudience());
- assertNull(properties.getDpopMode());
- assertNull(properties.getDpopIatOffsetSeconds());
- assertNull(properties.getDpopIatLeewaySeconds());
- }
-
- @Test
- @DisplayName("Should handle null values for all properties")
- void shouldHandleNullValues() {
- properties.setDomain("test.com");
- properties.setDomain(null);
- assertNull(properties.getDomain());
-
- properties.setAudience("https://api.test.com");
- properties.setAudience(null);
- assertNull(properties.getAudience());
-
- properties.setDpopMode(DPoPMode.REQUIRED);
- properties.setDpopMode(null);
- assertNull(properties.getDpopMode());
-
- properties.setDpopIatOffsetSeconds(100L);
- properties.setDpopIatOffsetSeconds(null);
- assertNull(properties.getDpopIatOffsetSeconds());
-
- properties.setDpopIatLeewaySeconds(50L);
- properties.setDpopIatLeewaySeconds(null);
- assertNull(properties.getDpopIatLeewaySeconds());
- }
-
- @Test
- @DisplayName("Should allow zero values for DPoP timing properties")
- void shouldAllowZeroValuesForDpopTimingProperties() {
- properties.setDpopIatOffsetSeconds(0L);
- assertEquals(0L, properties.getDpopIatOffsetSeconds());
-
- properties.setDpopIatLeewaySeconds(0L);
- assertEquals(0L, properties.getDpopIatLeewaySeconds());
- }
-
- @Test
- @DisplayName("Should reject negative values for DPoP timing properties")
- void shouldRejectNegativeValuesForDpopTimingProperties() {
- assertThrows(IllegalArgumentException.class, () ->
- properties.setDpopIatOffsetSeconds(-100L)
- );
-
- assertThrows(IllegalArgumentException.class, () ->
- properties.setDpopIatLeewaySeconds(-50L)
- );
- }
-}
\ No newline at end of file
+ private Auth0Properties properties;
+
+ @BeforeEach
+ void setUp() {
+ properties = new Auth0Properties();
+ }
+
+ @Test
+ @DisplayName("Should set and get domain property")
+ void shouldSetAndGetDomain() {
+ String domain = "dev-tenant.us.auth0.com";
+ properties.setDomain(domain);
+
+ assertEquals(domain, properties.getDomain());
+ }
+
+ @Test
+ @DisplayName("Should set and get audience property")
+ void shouldSetAndGetAudience() {
+ String audience = "https://api.example.com/v2/";
+ properties.setAudience(audience);
+
+ assertEquals(audience, properties.getAudience());
+ }
+
+ @Test
+ @DisplayName("Should set and get dpopMode property")
+ void shouldSetAndGetDpopMode() {
+ properties.setDpopMode(DPoPMode.REQUIRED);
+ assertEquals(DPoPMode.REQUIRED, properties.getDpopMode());
+
+ properties.setDpopMode(DPoPMode.ALLOWED);
+ assertEquals(DPoPMode.ALLOWED, properties.getDpopMode());
+
+ properties.setDpopMode(DPoPMode.DISABLED);
+ assertEquals(DPoPMode.DISABLED, properties.getDpopMode());
+ }
+
+ @Test
+ @DisplayName("Should set and get dpopIatOffsetSeconds property")
+ void shouldSetAndGetDpopIatOffsetSeconds() {
+ Long offsetSeconds = 300L;
+ properties.setDpopIatOffsetSeconds(offsetSeconds);
+
+ assertEquals(offsetSeconds, properties.getDpopIatOffsetSeconds());
+ }
+
+ @Test
+ @DisplayName("Should set and get dpopIatLeewaySeconds property")
+ void shouldSetAndGetDpopIatLeewaySeconds() {
+ Long leewaySeconds = 60L;
+ properties.setDpopIatLeewaySeconds(leewaySeconds);
+
+ assertEquals(leewaySeconds, properties.getDpopIatLeewaySeconds());
+ }
+
+ @Test
+ @DisplayName("Should have null default values for all properties")
+ void shouldHaveNullDefaultValues() {
+ assertNull(properties.getDomain());
+ assertNull(properties.getAudience());
+ assertNull(properties.getDpopMode());
+ assertNull(properties.getDpopIatOffsetSeconds());
+ assertNull(properties.getDpopIatLeewaySeconds());
+ }
+
+ @Test
+ @DisplayName("Should handle null values for all properties")
+ void shouldHandleNullValues() {
+ properties.setDomain("test.com");
+ properties.setDomain(null);
+ assertNull(properties.getDomain());
+
+ properties.setAudience("https://api.test.com");
+ properties.setAudience(null);
+ assertNull(properties.getAudience());
+
+ properties.setDpopMode(DPoPMode.REQUIRED);
+ properties.setDpopMode(null);
+ assertNull(properties.getDpopMode());
+
+ properties.setDpopIatOffsetSeconds(100L);
+ properties.setDpopIatOffsetSeconds(null);
+ assertNull(properties.getDpopIatOffsetSeconds());
+
+ properties.setDpopIatLeewaySeconds(50L);
+ properties.setDpopIatLeewaySeconds(null);
+ assertNull(properties.getDpopIatLeewaySeconds());
+ }
+
+ @Test
+ @DisplayName("Should allow zero values for DPoP timing properties")
+ void shouldAllowZeroValuesForDpopTimingProperties() {
+ properties.setDpopIatOffsetSeconds(0L);
+ assertEquals(0L, properties.getDpopIatOffsetSeconds());
+
+ properties.setDpopIatLeewaySeconds(0L);
+ assertEquals(0L, properties.getDpopIatLeewaySeconds());
+ }
+
+ @Test
+ @DisplayName("Should reject negative values for DPoP timing properties")
+ void shouldRejectNegativeValuesForDpopTimingProperties() {
+ assertThrows(IllegalArgumentException.class, () -> properties.setDpopIatOffsetSeconds(-100L));
+
+ assertThrows(IllegalArgumentException.class, () -> properties.setDpopIatLeewaySeconds(-50L));
+ }
+}