1

I have a Spring Boot project and am using Firebase Auth with Spring Security.

I am also configuring Swagger for API documentation. My code is:

package com.ayushsingh.doc_helper.commons.config.security;

import java.time.Instant;

import com.ayushsingh.doc_helper.commons.constants.AuthConstants;
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.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import jakarta.servlet.http.HttpServletResponse;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    private final FirebaseAuthFilter firebaseAuthFilter;
    private final FirebaseAuthenticationProvider firebaseAuthenticationProvider;

    public SecurityConfig(FirebaseAuthFilter firebaseAuthFilter,
                          FirebaseAuthenticationProvider firebaseAuthenticationProvider) {
        this.firebaseAuthFilter = firebaseAuthFilter;
        this.firebaseAuthenticationProvider = firebaseAuthenticationProvider;
    }

    @Bean
    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(AuthConstants.AUTH_API_PATTERN,
                                "/swagger-ui/**",
                                "/swagger-ui.html",
                                "/webjars/**",
                                "/configuration/security",
                                "/swagger-resources/**",
                                "/swagger-resources",
                                "/v3/api-docs/**",
                                "/v3/api-docs"
                                )
                        .permitAll()
                        .anyRequest().authenticated())
                .authenticationProvider(firebaseAuthenticationProvider)
                .addFilterBefore(firebaseAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling(ex -> ex
                        .authenticationEntryPoint(firebaseAuthenticationEntryPoint())
                        .accessDeniedHandler(accessDeniedHandler()));

        return http.build();
    }

    @Bean
    public AuthenticationEntryPoint firebaseAuthenticationEntryPoint() {
        return (request, response, authException) -> {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");

            String jsonResponse = """
                    {
                        "success": false,
                        "error": {
                            "code": "UNAUTHORIZED",
                            "message": "Authentication required. Please provide a valid Firebase token.",
                            "timestamp": "%s"
                        }
                    }
                    """.formatted(Instant.now());

            response.getWriter().write(jsonResponse);
        };
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return (request, response, accessDeniedException) -> {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");

            String jsonResponse = """
                    {
                        "success": false,
                        "error": {
                            "code": "FORBIDDEN",
                            "message": "Insufficient privileges to access this resource.",
                            "timestamp": "%s"
                        }
                    }
                    """.formatted(Instant.now());

            response.getWriter().write(jsonResponse);
        };
    }
}

I have also created a AuthFilter for Firebase-

package com.ayushsingh.doc_helper.commons.config.security;

import java.io.IOException;

import com.ayushsingh.doc_helper.commons.constants.AuthConstants;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.ayushsingh.doc_helper.features.auth.domain.AuthUser;
import com.ayushsingh.doc_helper.features.user.domain.User;
import com.ayushsingh.doc_helper.features.user.service.UserService;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseToken;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class FirebaseAuthFilter extends OncePerRequestFilter {

    private final FirebaseAuth firebaseAuth;
    private final UserService userService;

    public FirebaseAuthFilter(FirebaseAuth firebaseAuth, UserService userService) {
        this.firebaseAuth = firebaseAuth;
        this.userService = userService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        String token = getTokenFromRequest(request);

        if (token != null) {
            try {
                FirebaseToken decodedToken = firebaseAuth.verifyIdToken(token);
                String firebaseUid = decodedToken.getUid();
                User user = userService.findByFirebaseUid(firebaseUid);

                if (user != null) {
                    AuthUser authUser = new AuthUser(user);
                    FirebaseAuthenticationToken authentication = new FirebaseAuthenticationToken(authUser,
                            authUser.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                } else {
                    log.warn("User not found for Firebase UID: {}", firebaseUid);
                    SecurityContextHolder.clearContext();
                }
            } catch (FirebaseAuthException e) {
                log.error("Firebase token verification failed: {}", e.getMessage());
                SecurityContextHolder.clearContext();
            }
        }

        filterChain.doFilter(request, response);
    }

    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(AuthConstants.AUTHORIZATION_HEADER);
        if (bearerToken != null && bearerToken.startsWith(AuthConstants.BEARER)) {
            return bearerToken.substring(7);
        }
        return null;
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String path = request.getServletPath();
        String method = request.getMethod();

        // Skip filter for public endpoints
        var skipFilter = (path.startsWith(AuthConstants.AUTH_API_PREFIX) && "POST".equals(method))
                || path.startsWith("/api/auth/")
                || path.startsWith("/swagger-ui")
                || path.startsWith("/swagger-resources")
                || path.startsWith("/v3/api-docs")
                || path.startsWith("/webjars")
                || path.equals("/swagger-ui.html")
                || path.endsWith(".js")
                || path.endsWith(".css")
                || path.endsWith(".html")
                || path.endsWith(".png")
                || path.endsWith(".ico")
                || path.endsWith(".map");

        System.out.println("Skip filter: " + skipFilter+" request: "+request.getRequestURI());
        return skipFilter;
    }
}

When I hit any of the swagger endpoint, I get the response:

{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication required. Please provide a valid Firebase token.",
    "timestamp": "2025-08-05T06:12:36.329330400Z"
  }
}

I checked my public endpoint config using a /test endpoint and it works fine, but I am unable to access the swagger endpoints.

2
  • 2
    Please don't post screenshots of textual output, post it at code-formatted text (marked as json for syntax highlighting). Commented Aug 2 at 8:54
  • What is the URL you tried with your request? As an aside, I think those endsWith conditions will clash with your Spring Security filtering. Commented Aug 2 at 8:59

2 Answers 2

0

The list of endpoints in shouldNotFilter is incomplete and does not perfectly match the configuration in SecurityConfig. For instance, it is missing paths like /configuration/security, which are required for Swagger UI to function correctly.

When a request for such a path is made, shouldNotFilter returns false, causing your filter to run. Since the request to a Swagger endpoint does not contain an authentication token, your filter does nothing to the security context.

However, the request is then processed by Spring Security's filter chain, which ultimately denies access because it's treated as an unauthenticated request to a protected resource, triggering your firebaseAuthenticationEntryPoint.

New contributor
shailesh patil is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
0

I ran into the same issue recently and managed to fix it.

The problem seems to be that Spring Security was still hitting the FirebaseAuthFilter for Swagger paths OR the paths in .requestMatchers(...) and shouldNotFilter() don’t exactly match the incoming request URI

Here’s what worked for me:

1. I updated shouldNotFilter() method in AuthFilter to match full Swagger paths correctly like this

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
    String path = request.getServletPath();
    return path.startsWith("/swagger-ui")
            || path.startsWith("/swagger-resources")
            || path.startsWith("/v3/api-docs")
            || path.startsWith("/webjars/")
            || path.equals("/swagger-ui.html")
            || path.endsWith(".js")
            || path.endsWith(".css")
            || path.endsWith(".html")
            || path.endsWith(".png")
            || path.endsWith(".ico")
            || path.endsWith(".map")
            || path.startsWith("/api/auth/") // your auth prefix
            || (path.startsWith("/api/public/"));  

2. I also made sure that SecurityConfig has exact matches like this:

.requestMatchers(   
    "/swagger-ui/\*\*",
    "/swagger-ui.html",
    "/webjars/\*\*",
    "/configuration/security",
    "/swagger-resources/\*\*",
    "/v3/api-docs/\*\*",
    "/v3/api-docs"
).permitAll()

3. After these fixes, rebuild the project

4. Restart the server

5. Open Swagger at: http://localhost:8080/swagger-ui/index.html

Pro Tip: Use AntPathRequestMatcher for cleaner code instead of string matching.

I hope this helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.