Issue
I have a Springboot REST application that I'm trying to secure with firebase, with a user authentication flow that goes like:
- Users authenticate on a separate Vue frontend using firebase auth, gets a Firebase JWT id token.
- Frontend sends this token in the auth header to my backend.
- My backend should then get the token, and authenticate by sending the token to the Firebase server.
- if the token is valid then return whatever resource the user was requesting, it not return a 401.
I have already configured the front end code, and am now sending the JWT token with every request. I'm trying to setup the backend but am running into some problems.
I have added the firebase SDK admin to my app, initialized the Firebaseapp, and implemented a 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 whats 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 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);
}
}
Solution
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.
Answered By - Michal Trojanowski
Answer Checked By - Robin (JavaFixing Admin)