I have a Spring Boot REST application that I'm trying to secure with Firebase, with a user authentication flow that goes like:
I have already configured the front-end code, and am now sending the JWT token with every request. I'm trying to set up the backend but am running into some problems.
I have added the Firebase SDK admin to my app, initialized the Firebase app, and implemented an HTTP filter to filter all requests to check and authenticate the token. I have been following this tutorial from Firebase on authenticating tokens. For some reason, all my requests come back with a 403. I'm not really sure what's going wrong with my implementation, as there are no errors to fix.
My pom.xml snippet:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>7.3.0</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client</artifactId>
<version>1.39.2</version>
</dependency>
My firebase class to initialize firebase SDK:
@Service
public class FirebaseInitialization {
@PostConstruct
public void initialization() {
try {
FileInputStream inputStream = new FileInputStream("src/main/resources/serviceAccountKey.json");
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(inputStream))
.build();
FirebaseApp.initializeApp(options);
} catch (Exception error) {
error.printStackTrace();
}
}
}
My security config class:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String[] AUTH_WHITELIST = {"/publicEndpoint"};
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.addFilterAfter(new FireBaseTokenFilter(), FireBaseTokenFilter.class)
.sessionManagement().sessionCreationPolicy(STATELESS).and()
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated();
http.headers().frameOptions().disable();
}
}
My HTTP filter to check the Firebase token:
public class FireBaseTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authenticationHeader = request.getHeader("Authorization");
//checks if token is there
if (authenticationHeader == null || !authenticationHeader.startsWith("Bearer "))
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing token!");
FirebaseToken decodedToken = null;
try {
//Extracts token from header
String token = authenticationHeader.substring(7, authenticationHeader.length());
//verifies token to firebase server
decodedToken = FirebaseAuth.getInstance().verifyIdToken(token);
} catch (FirebaseAuthException e) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Error! " + e.toString());
}
//if token is invalid
if (decodedToken == null) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid token!");
}
chain.doFilter(request, response);
}
}
You get 403 response which means that your filter doesn't run (all the responses in the filter are 401 responses). If you look at your configuration, in this line:
.addFilterAfter(new FireBaseTokenFilter(), FireBaseTokenFilter.class)
you're telling Spring to register a new FireBaseTokenFilter which should be called after a FireBaseTokenFilter. This won't work, as you don't have a FireBase filter registered yet.
If you change that line to addFilterBefore(new FireBaseTokenFilter(), UsernamePasswordAuthenticationFilter.class)
the filter will now be called. You still need a way of telling Spring that the user is authenticated though. Your filter will reject requests with invalid tokens but I think it will still reject requests with valid tokens as well. Have a look at this tutorial on securing APIs in Spring to check how to configure a resource server to accept JWTs.
Note also that the FireBase SDK validates the ID token but there are no calls made to FireBase server. The SDK verifies the token's signature and some claims inside of it.