We continue our series on microservices architecture patterns, and this time we’ll focus on security. If you missed any of the previous posts in this series, we encourage you to check them out 😉:

  1. Microservices Architecture Patterns: What Are They and What Benefits Do They Offer?
  2. Architecture Patterns: Organization and Structure of Microservices.
  3. Architecture Patterns: Microservices Communication and Coordination.
  4. Microservices Architecture Patterns: SAGA, API Gateway, and Service Discovery.
  5. Microservices Architecture Patterns: Event Sourcing and Event-Driven Architecture (EDA).
  6. Microservices Architecture Patterns: Communication and Coordination with CQRS, BFF, and Outbox.
  7. Microservices Patterns: Scalability and Resource Management with Auto Scaling.
  8. Architecture Patterns: From Monolith to Microservices.
  9. Externalized Configuration.
  10. Architecture Patterns in Microservices: Consumer-Driven Contract Testing

General Introduction to Security in Microservices

In a microservices environment, the functionality of a large application is broken down into multiple independent services, each with specific responsibilities.

We continue with the common thread of all our previous posts, using the example of an e-commerce, where we could have:

Each of these services will communicate with others (or with a gateway) to complete business flows. However, when building a distributed system, the challenge arises of how to ensure that each request comes from a legitimate client and that the operations to be performed are properly authorized.

Traditionally, in monolithic systems, security was usually handled through:

In microservices, this becomes more complex because:

Importance of Data

Security is critical in any application, especially if (using the e-commerce example):

Therefore, ensuring that communications are properly authenticated and that each user only accesses their own resources is essential.

Security Patterns in Microservices

In this article, we will focus on three patterns commonly used in practice:

  1. Token-based Authentication
  2. OAuth (Open Authorization)
  3. JWT (JSON Web Tokens)

While there are other approaches (SAML, Basic Auth over TLS, Kerberos, etc.), these three have become particularly popular in modern microservices and REST API architectures.

Note. As you’ll see, we will split these patterns into two parts. In the first, we’ll look at Token-based Authentication and OAuth, and in the second, JWT and a few more interesting comparisons.

Note 2. The code examples are only intended to illustrate the concepts. Some parts may be incomplete or contain demo snippets that wouldn’t apply in real environments.

1 Token-Based Authentication

General Description

In token-based authentication, a client (e.g., a JavaScript web app, a mobile app, etc.) sends credentials (username, password) to an authentication service. After validating the credentials, this service issues a token (typically a unique string, like a UUID or hash). This token is then used for all subsequent requests: the client usually includes it in the Authorization header (typically Bearer ), and the receiving service validates the token to allow or deny access.

Token-based authentication

Advantages:

Disadvantages:

E-commerce Scenario

Let's suppose we have:

The flow would look like this:

  1. Login: The client sends username and password to the Auth Service via a POST /auth/login endpoint.
  2. Token Generation
  1. Token Use

The flow diagram would look like this:

Flow diagram

The sequence diagram:

Sequence diagram

Detailed Implementation in Spring Boot

Let’s look at an example of how to configure this authentication in a Java project using Spring Boot.

  1. Recommended dependencies

In your pom.xml (assuming Maven), you might include:

<dependencies>

    <!-- For basic security in Spring -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- To manage data in Redis, if we use Redis for tokens -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- Other dependencies needed for your e-commerce -->
    <!-- ... -->
</dependencies>
  1. Redis Configuration (optional but common)

If we choose to store tokens in Redis, we configure it in application.yml:

spring:
  redis:
    host: localhost
    port: 6379
  1. Token Management Service Class

This service centralizes the logic for:

@Service
public class TokenService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // Token expiration duration in seconds, e.g., 2 hours
    private final long TOKEN_EXPIRATION_SEC = 7200;

    public String generateTokenForUser(String username) {
        String token = UUID.randomUUID().toString();

        // Store in Redis with expiration
        // We could store an object with roles, name, email, etc.
        redisTemplate.opsForValue().set(token, username, TOKEN_EXPIRATION_SEC, TimeUnit.SECONDS);
        return token;
    }

    public boolean isValidToken(String token) {
        String username = (String) redisTemplate.opsForValue().get(token);
        return username != null; // Token exists and has not expired
    }

    public String getUsernameFromToken(String token) {
        return (String) redisTemplate.opsForValue().get(token);
    }

    public void revokeToken(String token) {
        // Manually delete token from Redis if you want to revoke it
        redisTemplate.delete(token);
    }

    public void revokeAllTokensForUser(String username) {
        Set<String> keys = redisTemplate.keys("*"); // Search all keys
        if (keys != null) {
            for (String key : keys) {
                String storedUsername = (String) redisTemplate.opsForValue().get(key);
                if (username.equals(storedUsername)) {
                    redisTemplate.delete(key); // Delete the user's tokens
                }
            }
        }
    }
}

Here, each token is associated with a username, but in a real e-commerce system we would include more details (roles, user ID, etc.). Every time we generate a token, we use a UUID and store it in Redis with a defined expiration time.

  1. Authentication Filter (TokenAuthenticationFilter)

The filter is responsible for intercepting each HTTP request, extracting the token (if present), and validating its legitimacy. Only if the token is valid, the user is marked as authenticated in the Spring Security context.

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
                                    throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        String token = null;

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            token = authHeader.substring(7);
        }

        if (token != null && tokenService.isValidToken(token)) {
            String username = tokenService.getUsernameFromToken(token);

            // Additional user info could be loaded here to assign roles
            UserDetails userDetails = new User(username, "",
                    Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));

            // Spring Security requires an Authentication object to "log in" the user
            UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

            // Set it in Spring Security's context
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        // Continue with the filter chain
        filterChain.doFilter(request, response);
    }
}
  1. Security Configuration

The SecurityConfig class uses the Spring Security infrastructure to define:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, 
    TokenAuthenticationFilter tokenAuthenticationFilter) throws Exception {
        return http
            .csrf(csrf -> csrf.ignoringRequestMatchers("/auth/login")) // Best practice
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/login", "/public/**").permitAll() // Public section
                .anyRequest().authenticated()
            )
            .addFilterBefore(tokenAuthenticationFilter, 
                UsernamePasswordAuthenticationFilter.class)
            .build();
    }
}
  1. Authentication Controller

We could create a controller to handle the login:

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private TokenService tokenService;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        // Validate credentials: this could involve querying a database
        // We'll assume a user "demo" with the password "password" for this example.

        if ("demo".equals(loginRequest.getUsername())
                && "password".equals(loginRequest.getPassword())) {
            String token = tokenService.generateTokenForUser(loginRequest.getUsername());
            return ResponseEntity.ok(Collections.singletonMap("token", token));
        } else {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                                 .body("Invalid credentials");
        }
    }
}

class LoginRequest {
    private String username;
    private String password;
    // Getters and Setters
}
  1. Process Flow
Token-based authentication diagram

The diagram illustrates two main phases of token-based authentication:

Phase 1: Login Flow

Phase 2: Request to a Protected Resource

Conclusion on Token-Based Authentication

This pattern is fairly straightforward and easier to implement than others, but it requires careful management of token storage and revocation. If your e-commerce system needs to revoke tokens quickly (e.g., for blocked accounts), you’ll need either a central approach or a broadcast mechanism to invalidate tokens across all nodes.

OAuth (Open Authorization)

General Overview

OAuth is an authorization protocol that allows users to share protected resources with third-party apps without exposing their credentials. OAuth 2.0 introduced several flows (grants), one of the most common being the Authorization Code Grant, used when delegating authentication to an external provider (Google, Facebook, GitHub, etc.) or a deployed authorization server (such as Keycloak, Okta, etc.).

Note: OAuth is not strictly an authentication protocol (even if used as such in “Login with Google/Facebook”) but an authorization one. For true authentication based on OAuth2, OpenID Connect (OIDC) is used, which adds an identity layer on top of OAuth2, providing an ID Token with user info.

E-commerce Scenario

In the latter case, instead of storing the user's password directly, we redirect the user to Google’s login page for authentication. Once Google verifies the identity, it returns a code that we exchange for an access token. Using that token, we query Google’s API to obtain profile info (name, email, photo, etc.). We then create or find the user in our internal database and grant access.

Spring Boot Configuration (OAuth2 Client)

Spring Security 5 (and later) greatly simplifies OAuth2 configuration. To integrate with Google, we add to pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
  1. application.yml

In the application.yml (or application.properties) file, you define the registration information and the provider.

An example using Google:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: TU_CLIENT_ID_DE_GOOGLE
            client-secret: TU_CLIENT_SECRET_DE_GOOGLE
            scope: 
              - email
              - profile
            redirect-uri: "{baseUrl}/login/oauth2/code/google"
        provider:
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
            token-uri: https://www.googleapis.com/oauth2/v4/token
            user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
  1. Security Configuration

Once the above properties are defined, you create a configuration class that extends WebSecurityConfigurerAdapter (for Spring Security versions < 6.x) or use the new declarative approach (starting with Spring Boot 3).

Here is the traditional approach:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // Authorize certain endpoints
            .authorizeRequests()
            .antMatchers("/", "/login", "/error").permitAll()
            .anyRequest().authenticated()
            .and()
            // Configure OAuth2 login
            .oauth2Login()
                .defaultSuccessUrl("/home")
                .failureUrl("/login?error=true");
    }
}

This way, Spring Security handles the OAuth cycle:

oauth cycle spring security

In a flow diagram, it would look like this:

OAuth flow diagram

Usage in Microservices

We could have our own Authorization Server, such as Keycloak, to handle both authentication and authorization. In a microservices environment, each service could:

In an e-commerce system, this is especially useful if we want to:

Conclusion on OAuth

OAuth is powerful and flexible, but can be more complex to configure and maintain, especially if managing your own authorization server. However, when integrating with external identity providers or delegating authentication to trusted services (e.g., Google), OAuth2 is an almost universal standard.

In the Next Post

Next, we'll dive into JWT details, a hybrid architecture example (OAuth2 + JWT), best practices, and a compelling comparison.

Let me know your thoughts in the comments 👇.

Tell us what you think.

Comments are moderated and will only be visible if they add to the discussion in a constructive way. If you disagree with a point, please, be polite.

Subscribe