I am currently working on a Spring Boot REST application with Spring Security. My workplace use Auth0 (external third-party service providing user management) for their authentication and have requested me to implement it in this application. Authentication occurs in the front end application written in React. The frontend application shows a login form and sends the username and password to Auth0, Auth0 verifies the credentials and returns a JWT token when the user is validated.
After this, the frontend application will call the REST services from my application passing a JWT token in the Authorize header. Using an Auth0 plugin, Spring Security verifies this token and the request is allowed to execute. I have tested this much to be working as expected. The code is as follows:
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import com.auth0.spring.security.api.JwtWebSecurityConfigurer;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
@Value(value = "${auth0.apiAudience}")
private String apiAudience;
@Value(value = "${auth0.issuer}")
private String issuer;
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
configuration.setAllowCredentials(true);
configuration.addAllowedHeader("Authorization");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
JwtWebSecurityConfigurer //Auth0 provided class performs per-authentication using JWT token
.forRS256(apiAudience, issuer)
.configure(http)
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/Test/public").permitAll()
.antMatchers(HttpMethod.GET, "/Test/authenticated").authenticated();
}
}
Now, once this authentication is done, I have observed that the principal in the security context gets updated with user id from Auth0. I have verified this by this code snippet:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String name = authentication.getName(); // Returns the Auth0 user id.
The next step I expect to do is to use this user id to match the user with roles and permissions in my existing database schema. Therefore, I need to implement a custom authorization mechanism that plugs into Spring Security as well. In other words the user's roles must be loaded into the security context shortly after the (pre)authentication is done. How do I implement this? Is there some class that I need to extend or implement some interface?
Ok, I found a solution though I think it's a bit dirty. Going by the weird way that the official Auth0 classes are structured, what I've done could possibly be described as a hack. Anyway, here goes:
First of all, I a custom user details service by implementing the AuthenticationUserDetailsService interface:
@Service
public class VUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationJsonWebToken> {
@Autowired
UserRepository userRepository;
Logger logger = LoggerFactory.getLogger(VUserDetailsService.class);
@Override
@Transactional(readOnly = true)
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationJsonWebToken token) throws UsernameNotFoundException {
logger.debug("User id: "+token.getName());
// Verify whether there is an entry for this id in the database.
User user = userRepository.findByAuxillaryId(token.getName());
if(user == null)
throw new UsernameNotFoundException("The user with id "+token.getName()+" not found in database.");
logger.debug("Obtained user details from db: "+user.toString());
List<GrantedAuthority> authoritiesList = new ArrayList<>();
// Get user roles
List<UserRole> userRoles = user.getUserRoles();
if(userRoles != null) logger.debug("Number of user roles:"+userRoles.size());
for(UserRole userRole : userRoles) {
logger.debug(userRole.getCompositeKey().getRole());
authoritiesList.add(new SimpleGrantedAuthority(userRole.getCompositeKey().getRole()));
}
return new org.springframework.security.core.userdetails.User(token.getName(), "TEMP", authoritiesList);
}
}
Here auxillary id is the user id assigned when a user is created in Auth0. Note that PreAuthenticatedAuthenticationJsonWebToken is a class provided by Auth0 as well.
After this, I created a custom authentication provider extending the Auth0 provided JwtAuthenticationProvider:
public class VAuthenticationProvider extends JwtAuthenticationProvider {
public VAuthenticationProvider(JwkProvider jwkProvider, String issuer, String audience) {
super(jwkProvider, issuer, audience);
}
@Autowired
VUserDetailsService vUserDetailsService;
Logger logger = LoggerFactory.getLogger(VAuthenticationProvider.class);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.debug("*** Processing authentication for token: "+authentication.getName());
logger.debug("*** Current granted authorities: "+authentication.getAuthorities());
UserDetails userDetails = vUserDetailsService.loadUserDetails((PreAuthenticatedAuthenticationJsonWebToken) authentication);
authentication = new PreAuthenticatedAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
return authentication;
}
@Override
public boolean supports(Class<?> authentication) {
//com.auth0.spring.security.api.authentication.PreAuthenticatedAuthenticationJsonWebToken
return authentication.equals(PreAuthenticatedAuthenticationJsonWebToken.class);
}
}
Then I used this authentication provider in my security configuration class:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Value(value = "${auth0.apiAudience}")
private String apiAudience;
@Value(value = "${auth0.issuer}")
private String issuer;
@Autowired
VUserDetailsService vUserDetailsService;
Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
@Bean
public VAuthenticationProvider authProvider() {
JwkProvider jwkProvider = new JwkProviderBuilder(issuer).build(); //Auth0 provided class
VAuthenticationProvider vAuthProvider = new VAuthenticationProvider(jwkProvider, issuer, apiAudience);
return vAuthProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
JwtWebSecurityConfigurer.forRS256(apiAudience, issuer, authProvider())
.configure(http)
.authorizeRequests().antMatchers(HttpMethod.GET, "/Test/public").permitAll()
.antMatchers(HttpMethod.GET, "/Test/authenticated").authenticated()
.antMatchers(HttpMethod.GET, "/admin/*").hasRole("ADMIN") //Not Auth0 role, defined in my DB.
.antMatchers(HttpMethod.GET, "/Test/root").hasRole("ROOT"); //Not Auth0 role, defined in my DB.
}
/* Code ommitted */
Now, all my requests are getting filtered based on the roles in my database. Thus, Auth0 is only being used for authentication and authorization is based on roles in my database.
If anyone thinks this solution could be improved, please let me know.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With