Using Auth0 as an OAuth service provider we know that roles can be affected to users (Staying just at Auth0 level for now). However those roles aren't directly considered as granted authorities when the user logs in. I wanted to be able to fetch the user roles using Auth0 management api to get user roles and define authorities based on the roles that will be granted to my athenticated user. Is that a good practice overall and if yes how should i proceed ? I have tried some specific implementations but i still face the issue.
I implemented a Customer OAuthUser, a CustomOAuthUserService and added my service in the Spring Security Configuration file.
Here is my Custom OAuthUser:
package com.interco.reconciliation.model;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class CustomOAuth2User extends DefaultOAuth2User {
private final OAuth2User delegate;
private final Collection<GrantedAuthority> authorities;
public CustomOAuth2User(OAuth2User delegate, Collection<GrantedAuthority> authorities) {
super(authorities, delegate.getAttributes(), "name");
this.delegate = delegate;
this.authorities = authorities;
}
@Override
public Map<String, Object> getAttributes() {
return this.delegate.getAttributes();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getName() {
return this.delegate.getAttribute("name");
}
}
Here is my Custom OAuth User Service:
package com.interco.reconciliation.service;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import com.interco.reconciliation.model.CustomOAuth2User;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.exceptions.UnirestException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Autowired
private Auth0ApiManagementService auth0ApiManagementService;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
OAuth2User oAuth2User = super.loadUser(userRequest);
List<GrantedAuthority> authorities = new ArrayList<>(oAuth2User.getAuthorities());
String userId = oAuth2User.getAttribute("sub");
try {
HttpResponse<JsonNode> roles = this.auth0ApiManagementService.getUserRoles(userId);
JsonNode rolesBody = roles.getBody();
for(int i = 0; i < rolesBody.getArray().length(); i++) {
JSONObject role = rolesBody.getArray().getJSONObject(i);
String roleName = role.getString("name");
authorities.add(new SimpleGrantedAuthority("ROLE_" + roleName));
}
} catch (UnirestException | UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
System.out.println("User retrieved and authorities assigned");
return new CustomOAuth2User(oAuth2User, authorities);
}
}
Here is my Security Configuration file:
package com.interco.reconciliation.config;
import static org.springframework.security.config.Customizer.withDefaults;
import com.interco.reconciliation.service.Auth0ApiManagementService;
import com.interco.reconciliation.service.CustomOAuth2UserService;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.exceptions.UnirestException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.io.IOException;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
@Value("${okta.oauth2.issuer}")
private String issuer;
@Value("${okta.oauth2.client-id}")
private String clientId;
@Autowired
private CustomOAuth2UserService customOAuth2UserService;
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // remove later or configure appropriately in production
.cors(withDefaults()) // remove later or configure appropriately in production
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2Login ->
oauth2Login
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint.userService(customOAuth2UserService)))
.logout(logout -> logout
.addLogoutHandler(logoutHandler()));
return http.build();
}
private LogoutHandler logoutHandler() {
return (request, response, authentication) -> {
try {
String baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString();
response.sendRedirect(issuer + "v2/logout?client_id=" + clientId + "&returnTo=" + baseUrl);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
}
}
You are configuring a Spring OAuth2 client (with oauth2Login
). You need to:
issuer-uri
, the userinfo endpoint is used when issuer-uri
is not set.GrantedAuthoritiesMapper
is simpler.If you were configuring an oauth2ResourceServer
(a REST API authorized using bearer tokens) with a JWT decoder, then you'd have to:
I maintain an additional Spring Boot starter that can, among many aother things:
GrantedAuthoritiesMapper
or Converter<Jwt, AbstractAuthenticationToken>
for you.oauth2Login
:
audience
parameter required on the authorization request.oauth2Login
is based on sessions.