5

I am trying to implement authentication spring boot in backend and vue Js in front end, the problem is that I have ly backkend connected readonly to a database, so used authentication using vue js and firebase authentication feature.

The problem is that my endpoints still accessible and anyone can send requests and fetch data using postman for example !

If anyone has an idea how to resolve that please go ahead, thanks !

PS: i don't think that i may help, but here is my login code anyway, @Renaud Tarnec

import firebase from 'firebase'

export default {
  name: 'login',
  data: function() {
    return {
      email: '',
      password: ''
    }
  },
  methods: {
    signIn: function() {
      firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(
        function(user) {
          alert('You are connected')
        },
        function(err) {
          aler('Ooops,' + err.message)
        }
      );
    }
  }
}
</script>

and here is for example a part of my repo and there is list of events :

@RequestMapping("api/events")
public class EventController {
    @Autowired
    private EventRepository eventrepository;

    @GetMapping
    public ArrayList<Event> find() {
        ArrayList<Event> events = new ArrayList<Event>();
        for (Event e : eventrepository.findAll()) {
            System.out.println(e);
            events.add(e);
        }
        return events;
    }
3
  • You should share your entire code (i.e. add it to your post) for the community to be able to help you. Commented Aug 7, 2018 at 9:13
  • Ok, thanks for having updated you post. One question: have you set some security rules in your database? If it is Firestore see firebase.google.com/docs/firestore/security/overview. If it is the Real Time database see firebase.google.com/docs/database/security. As a matter of fact, implementing an authentication mechanism (i.e. with signInWithEmailAndPassword(this.email, this.password)) is one part but you have to implement the second part which is authorisation, through a set of rules that restrict the access to your resources to certain users. Commented Aug 7, 2018 at 9:55
  • i did that part on VueJs, i will implement it also in spring, will update later , thanks @RenaudTarnec Commented Aug 7, 2018 at 10:23

1 Answer 1

8

This is normal behaviour, because you send your request against firestore with the admin sdk credentials.

You need to put some authentication into your spring boot application.

I put some code together that put all your requests behind firebase authentication.

FirebaseConfig.java

@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix="firebase")
public class FirebaseConfig {

    private static final Logger logger = LoggerFactory.getLogger(FirebaseConfig.class);

    private String databaseURL;
    private String serviceAccount;

    @Bean
    public DatabaseReference firebaseDatabse() {
        DatabaseReference firebase = FirebaseDatabase.getInstance().getReference();
        return firebase;
    }

    @PostConstruct
    public void init() {

        try {
            FirebaseApp.getInstance();
        } catch (IllegalStateException e) {
            try {
                InputStream inputStream = FirebaseConfig.class.getClassLoader().getResourceAsStream(serviceAccount);

                try {
                    FirebaseOptions options = new FirebaseOptions.Builder().setCredentials(GoogleCredentials.fromStream(inputStream))
                            .setDatabaseUrl(databaseURL).build();

                    FirebaseApp.initializeApp(options);
                } catch (IOException ioE) {
                    ioE.printStackTrace();
                }
            } catch (NullPointerException nullE) {
                nullE.printStackTrace();
            }
        }

    }

    public String getDatabaseURL() {
        return databaseURL;
    }

    public void setDatabaseURL(String databaseURL) {
        this.databaseURL = databaseURL;
    }

    public String getServiceAccount() {
        return serviceAccount;
    }

    public void setServiceAccount(String serviceAccount) {
        this.serviceAccount = serviceAccount;
    }
}

Then you need to enable web security:

WebSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final Logger logger = LoggerFactory.getLogger(WebSecurityConfiguration.class);

    /**
     * Use to create instance of {@link FirebaseAuthenticationTokenFilter}.
     *
     * @return instance of {@link FirebaseAuthenticationTokenFilter}
     */
    public FirebaseAuthenticationTokenFilter firebaseAuthenticationFilterBean() throws Exception {
        logger.debug(
                "firebaseAuthenticationFilterBean():: creating instance of FirebaseAuthenticationFilter.");

        FirebaseAuthenticationTokenFilter authenticationTokenFilter = new FirebaseAuthenticationTokenFilter();

        return authenticationTokenFilter;
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
                .cors()
                .and()
                .csrf()
                .disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // Custom security filter
        httpSecurity.addFilterBefore(firebaseAuthenticationFilterBean(),
                UsernamePasswordAuthenticationFilter.class);
    }

}

Lastly you add a request filter that validates the access token every time you make a request against the api.

FirebaseAuthenticationTokenFilter.java

@Component
public class FirebaseAuthenticationTokenFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(FirebaseAuthenticationTokenFilter.class);
    private final static String TOKEN_HEADER = "Authorization";

    /**
     *
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        logger.debug("doFilter:: authenticating...");

        HttpServletRequest httpRequest = request;
        String authToken = httpRequest.getHeader(TOKEN_HEADER);

        if (Strings.isNullOrEmpty(authToken)) {
            filterChain.doFilter(request, response);
            return;
        }

        try {
            Authentication authentication = getAndValidateAuthentication(authToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            logger.debug("doFilter():: successfully authenticated.");
        } catch (Exception ex) {
            HttpServletResponse httpResponse = response;
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            logger.debug("Fail to authenticate.", ex);
        }

        filterChain.doFilter(request, response);
    }

    /**
     *
     * @param authToken Firebase access token string
     * @return the computed result
     * @throws Exception
     */
    private Authentication getAndValidateAuthentication(String authToken) throws Exception {
        Authentication authentication;

        FirebaseToken firebaseToken = authenticateFirebaseToken(authToken);
        authentication = new UsernamePasswordAuthenticationToken(firebaseToken, authToken, new ArrayList<>());

        return authentication;
    }

    /**
     * @param authToken Firebase access token string
     * @return the computed result
     * @throws Exception
     */
    private FirebaseToken authenticateFirebaseToken(String authToken) throws Exception {
        ApiFuture<FirebaseToken> app = FirebaseAuth.getInstance().verifyIdTokenAsync(authToken);

        return app.get();
    }

    @Override
    public void destroy() {
        logger.debug("destroy():: invoke");
    }

}

Now your API endpoints are save against unauthorized requests.

In you web application you handle the authorization as normally with firebase. On every request to the spring-boot application you pass the access token as Authorization header.

Keep in mind that this is not really save, because the spring boot API acts as an admin against the firebase SDK.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for your reply, i knew that that's what should be done theoriquement but things were somehow mysterious when i implemented it, your answer seems to be way clearer and organised.
@Kersten, thanks a bunch for this; for months of hitting a deadlock I came across this post and this was very useful. I have made these changes to the codebase, but when I try to access the API through swagger I hit a 403 error. I have added security configuration as per this link - stackoverflow.com/questions/37671125/… even then it is not working. Any leads ?
Also just wondering since we have this in the filter how scalable would this be since for every API call we would ping firebase to authenticate the id token.
@bhavs Sorry, this was just a short mockup from me :) I actually don't use Java so much in my webprojects. You are right with your performance things. Maybe a good idea would be to check the accesstoken only if its not valid anymore. Then it would only call firebase when the token timedout.

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.