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;
    }
KENdi
  • 7,576
  • 2
  • 16
  • 31
Aleb
  • 85
  • 1
  • 8
  • You should share your entire code (i.e. add it to your post) for the community to be able to help you. – Renaud Tarnec Aug 07 '18 at 09:13
  • Ok, thanks for having updated you post. One question: have you set some security rules in your database? If it is Firestore see https://firebase.google.com/docs/firestore/security/overview. If it is the Real Time database see https://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. – Renaud Tarnec Aug 07 '18 at 09:55
  • i did that part on VueJs, i will implement it also in spring, will update later , thanks @RenaudTarnec – Aleb Aug 07 '18 at 10:23

1 Answers1

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.

Kersten
  • 1,662
  • 3
  • 16
  • 28
  • 1
    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. – Aleb Feb 20 '19 at 08:14
  • 1
    @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 - https://stackoverflow.com/questions/37671125/how-to-configure-spring-security-to-allow-swagger-url-to-be-accessed-without-aut even then it is not working. Any leads ? – bhavs May 11 '20 at 18:29
  • 1
    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 May 11 '20 at 18:43
  • @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. – Kersten Jul 24 '20 at 21:18