Securing Microservices with OAuth 2.0

As MicroServices are becoming the new norm for the enterprise application development, securing those services is also becoming a challenging task. Thanks to OAuth 2.0 protocol which greatly simplified the work involved in developing and managing security services. This blog talks about the steps we need to follow to secure a microservice using OAuth 2.0
Technology/Tools

Spring Boot
Maven
What is OAuth 2?
OAuth 2 is an authorization framework, a security concept for rest API( Read as MicroService), about how you authorize a user to get access to a resource from your resource server by using token.

OAuth 2 has 4 different roles in this process.

Resource Owner
Client
Authorization Server
Resource Server
Resource Owner: Resource owner is the user, who authorizes an application to access their account.

Client: Client is the application, which is used by the user to get resources from the resource server.

Authorization Server: Authorization server will issue access tokens by authenticating the user and obtain authorization grant.

Authorization server issues two type of tokens, access_token and referesh_token.

The responsibility of access token is to access resource before it gets expired.
The responsibility of Refresh Token is to request for a new access token when the access token is expired. An authorization grant is a credential representing the resource owner’s authorization (to access its protected resources) used by the client to obtain an access token.
The specification defines 4 grant types:

Authorization code
Implicit
Resource owner password credentials
Client credentials
Generally, we will use implicit grant flow If the Client is a Single Page Application (meaning an application running in a browser using a scripting language such as Javascript). Please refer the following link to decide which grant type is suitable for your case. In this article, we are using implicit grant type and password grant type.

https://auth0.com/docs/api-auth/which-oauth-flow-to-use

Implicit grant type will not give refresh token. So you have to handle token renewal process, at the client side. Please check below link for how to implement silent authentication for different client applications(Angular, React,etc..).

https://auth0.com/docs/api-auth/tutorials/silent-authentication

Resource Server: Resource server will be the host, where resources are deployed. Sometimes authorization and resource server will be the same server. The following diagram will tell about the flow.

Maven Dependency Configuration
In your spring boot application pom.xml, please add the following dependency.

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-oauth2</artifactId>

</dependency>

If you are using the in-memory client to store tokens using Redis server, then add the following dependency.

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

Authorization Server Configuration
The authorization server is the one responsible for verifying credentials and if credentials are OK, providing the tokens. It also contains information about registered clients and possible access scopes and grant types. In this article, tokens are saving to an in-memory token store using Redis server. @EnableAuthorizationServer annotation enables an Authorization Server (i.e. an Authorization Endpoint and a TokenEndpoint) in the current application context. Class AuthServerConfig extends AuthorizationServerConfigurerAdapter which provides all the necessary methods to configure an Authorization server. CustomTokenEnricher class will provide the facility to enrich the response of OAuth/token endpoint. CustomAuthenticationProvider will be the authentication provider where you will authenticate the user.

@EnableAuthorizationServer

@Configuration

public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
TokenStore tokenStore;

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private CustomTokenEnricher customTokenEnricher;

@Autowired
CustomUserDetailService userDetailsService;

@Value(“${oauth.redirectUrl}”)
String redirectUri;

@Value(“${oauth.clientId}”)
String clientId;

@Value(“${oauth.scope}”)
String scope;

@Value(“${oauth.grantType}”)
String grantType;

@Value(“${oauth.accessTokenValidity.inSeconds}”)
String accessTokenValidityInSeconds;

@Value(“${oauth.secret}”)
String secret;

@Value(“${oauth.authorties}”)
String authroties;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

endpoints.tokenStore(tokenStore).authenticationManager(authenticationManager)

.userDetailsService(userDetailsService).tokenEnhancer(customTokenEnricher);

}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

security.tokenKeyAccess(“permitAll()”).checkTokenAccess(“hasRole(‘TRUSTED_CLIENT’)”)

.allowFormAuthenticationForClients();

}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

clients.inMemory().withClient(clientId).secret(secret).authorizedGrantTypes(grantType.split(“,”))

.scopes(scope.split(“,”)).redirectUris(redirectUri).authorities(authroties)

.accessTokenValiditySeconds(Integer.parseInt(accessTokenValidityInSeconds)).autoApprove(true);

}

public class CustomAuthenticationProvider implements AuthenticationProvider {

@Autowired
UserService userService;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

UserTO user;

String userName = authentication.getName();

String password = authentication.getCredentials().toString();

if (userName.contains(“@”) && userName.contains(“.”)) {

user = userService.getUserByEmailId(userName);

} else {

user = userService.getUserByUsername(userName);

}

if (user == null) {
throw new OAuth2Exception(“Invalid login credentials”);
}

BCryptPasswordEncoder bcrypt=new BCryptPasswordEncoder();

if (bcrypt.matches(password, user.getPassword())) {

List<GrantedAuthority> grantedAuths = new ArrayList<>();

SecureUser secureUser=new SecureUser(user);

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(secureUser, password,

grantedAuths);

return usernamePasswordAuthenticationToken;

}

return null;

}

@Override
public boolean supports(final Class<?> authentication) {

return authentication.equals(UsernamePasswordAuthenticationToken.class);

}

}
Resource Server configuration
Create a bean ResourceServerConfig that extends ResourceServerConfigurerAdapter and override configure(HttpSecurity security) method. Annotate it with @EnableResourceServer annotation. Here I’ve configured resource server for this endpoints starting with /user. To skip authentication for user creation, override configure(WebSecurity security) method.

@Configuration

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

@EnableResourceServer

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
CustomAuthentcationProvider authenticationProvider;

@Override
public void configure(AuthenticationManagerBuilder auth) {

auth.authenticationProvider(authenticationProvider);
}

@Override
public void configure(WebSecurity security) throws Exception {

security.ignoring().antMatchers(“/user/create”);

}

@Override
protected void configure(HttpSecurity http) throws Exception {

http.requestMatchers().antMatchers(“/login”, “/oauth/authorize”,”/oauth/token”).and()
.authorizeRequests().anyRequest().authenticated().and().formLogin().permitAll();
}

}

Source: https://walkingtree.tech/securing-microservices-oauth2/

WalkingTree is an IT software and service provider recognized for its passion for technology.