Spring Boot HandBook

    Introduction :#

    OAuth2 Client Authentication is a process in which a client application (like a web or mobile app) proves its identity to an OAuth2 authorization server to access resources securely. This prevents unauthorized access, ensures proper permissions, and validates token exchanges. Here’s a quick overview:

    Key Benefits :#

    1. Security: Ensures only legitimate clients can access resources.
    2. Authorization: Confirms that the client app has the right permissions.
    3. Token Exchange: Validates the legitimacy of a client before issuing access tokens.

    Common OAuth2 Flows :#

    1. Authorization Code Flow: For apps needing user authentication (e.g., social login).
    2. Client Credentials Flow: For machine-to-machine communication using client credentials.
    3. Token Introspection: Verifies if an access token is still valid.

    Setting Up with Google OAuth2 :#

    1. Dependencies: Add spring-boot-starter-oauth2-client in the project.
    2. Google Cloud Console:
      • Create a project.
      • Generate OAuth2.0 credentials.
      • Configure the consent screen for user permissions.
    3. Scopes:
      • Non-sensitive scopes: Basic profile info (e.g., email).
      • Sensitive scopes: More private data (e.g., calendar access).
    4. Testing and Publishing:
      • Test with up to 100 users before publishing.
      • Set up authorized JavaScript origins and redirect URIs to control where OAuth requests are allowed.

    This setup process involves configuring client secrets, authorized origins, and redirect URIs to ensure secure and controlled OAuth2 flows for applications.

    Now you need to configure the Client ID and Client Secret in your application. 

    Configure Client and Secret

    To avoid repeating same thing again and again, let’s use the another resource file that is called application.yml file. Go to your application’s resource package and create a new file (named application.yml). And put that particular value of client-id and client secret.

    Note: Indentation is crucial in the application.yml file for Spring Boot to read the configuration properly. Here’s how it should look with correct indentation:

    spring: security: oauth2: client: registration: google: client-id: 234512600655-7u1u31s9oor02glldhpk9om40hl3kndf.apps.googleusercontent.com client-secret: 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


    Each level should be indented with two spaces to maintain proper YAML formatting, which ensures Spring Boot will parse the configuration correctly.

    Configure the WebSecurityConfig

    After all of these we have to configure our config class as well. Basically we have to add this ‘.oauth2Login()’ in our config class.

    .oauth2Login(oauth2Login -> oauth2Login .failureUrl("/login?error=true") .successHandler(oAuth2SuccessHandler) )

    import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity //by adding this annotation we are telling springboot that we want to we want to configure this SpringSecurityFilterChain @RequiredArgsConstructor public class WebSecurityConfig { private final JWTAuthFilter jwtAuthFilter; @Bean SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity .authorizeHttpRequests(auth-> auth .requestMatchers("/endpoint","/error","/auth/**","/home.html").permitAll() .anyRequest().authenticated()) // All other requests require authentication .csrf(csrfConfig-> // Disable CSRF for simplicity, not recommended for production csrfConfig .disable()) .sessionManagement(sessionConfig -> // Disable JSESSIONID for simplicity, not recommended for production sessionConfig .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) .oauth2Login(oauth2Config->oauth2Config .failureUrl("/login?error=true") .successHandler(oAuth2SuccessHandler) // to handle the error ); return httpSecurity.build(); //when you add build this throws an exception } @Bean AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }

    For OAuth2SuccessHandler we have to create a custom handler class where we extends the SimpleUrlAuthenticationSuccessHandler class

    and override the methodonAuthenticationSuccess .

    import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import java.io.IOException; @Component @RequiredArgsConstructor @Slf4j public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final UserRepo userRepo; private final UserServiceImpl userService; private final JWTService jwtService; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; DefaultOAuth2User defaultOAuth2User = (DefaultOAuth2User) token.getPrincipal(); String email =defaultOAuth2User.getAttribute("email"); UserEntity user= userService.getUserByEmail(email); //have to create a method in service if(user == null){ //user with email id not present UserEntity newUser = UserEntity .builder() //have to add @Builder in UserEntity .name(defaultOAuth2User.getAttribute("name")) .email(email) .build(); user = userService.save(newUser); //have to create a save method in service } String accessToken = jwtService.createAccessToken(user); String refreshToken = jwtService.createRefreshToken(user); Cookie cookie = new Cookie("refreshToken",refreshToken); cookie.setHttpOnly(true); //always add response.addCookie(cookie); String frontendUrl = "http://localhost:8080/home.html?token="+accessToken; //to send the access token // getRedirectStrategy().sendRedirect(request, response, frontendUrl); //or you can use this response.sendRedirect(frontendUrl); } }

    Make some changes in UserService.

    import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class UserServiceImpl implements UserDetailsService { private final UserRepo userRepo; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepo .findByEmail(username) // .orElse(null); //or you can throw an exception here .orElseThrow(()->new ResourceNotFoundException("USER WITH EMAIL "+username+" NOT FOUND")); } public UserEntity getUserById(Long userId){ return userRepo.findById(userId).orElseThrow(()->new ResourceNotFoundException("USER ID NOT FOUND: "+userId)); } public UserEntity getUserByEmail(String mail){ //add this return userRepo.findByEmail(mail).orElse(null); } public UserEntity save(UserEntity newUser) { // add this return userRepo.save(newUser); } }

    Create a home.html page

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Home</title> <style> body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f0f0; margin: 0; } .container { text-align: center; background-color: white; padding: 2rem; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); } </style> </head> <body> <div class="container"> <h1>Welcome to the Home Page</h1> <p>You have successfully logged in using Google OAuth2.</p> </div> </body> </html>

    Output

    After run this application successfully if we go to “/login” (http://localhost:8080/login) in browser login with one email id we can see this. Press ‘Google’.

    Here you can see the application and you have to login with your provided email id.

    After selecting an email, if you press 'Continue,' Google will share your name and email with the app.

    This will take you to the next error page.

    When we are not using any SuccessHandler in our code. Basically you can test it if you run your configure class with out adding any SuccessHandler you can see the error.

    When we use SuccessHandler then we can fix these errors. You can check that accessToken in jwt.io . Here if we are not using any separate home.html file.

    When we create the home.html page it’s shown this.

    Third Party Authentication

     

    Last updated on Dec 09, 2024