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.
endsWith
conditions will clash with your Spring Security filtering.