OAuth2 Login with Spring Security

1 minute read

OAuth2 lets users sign in with their Google, GitHub, or other accounts — no passwords to manage.


OAuth2 Flow

1. User clicks "Login with Google"
2. App redirects to Google's consent screen
3. User approves → Google redirects back with authorization code
4. App exchanges code for access token (server-side)
5. App fetches user info from Google
6. App creates/updates local user → issues JWT

Setup

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

Google OAuth2

Register at console.cloud.google.com:

  1. Create project → APIs & Services → Credentials
  2. Create OAuth 2.0 Client ID
  3. Authorized redirect URI: http://localhost:8080/login/oauth2/code/google
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope: openid, profile, email

GitHub OAuth2

Register at github.com/settings/developers:

  1. New OAuth App
  2. Callback URL: http://localhost:8080/login/oauth2/code/github
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: ${GITHUB_CLIENT_ID}
            client-secret: ${GITHUB_CLIENT_SECRET}
            scope: read:user, user:email

Security Config

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final CustomOAuth2UserService oAuth2UserService;
    private final OAuth2AuthenticationSuccessHandler successHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**", "/oauth2/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(info -> info.userService(oAuth2UserService))
                .successHandler(successHandler)
            );

        return http.build();
    }
}

Custom OAuth2 User Service

@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest request) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(request);

        String provider = request.getClientRegistration().getRegistrationId();  // "google" or "github"
        String email = oAuth2User.getAttribute("email");
        String name = oAuth2User.getAttribute("name");

        // Find or create local user
        AppUser user = userRepository.findByEmail(email)
            .orElseGet(() -> {
                AppUser newUser = new AppUser();
                newUser.setEmail(email);
                newUser.setUsername(name);
                newUser.setProvider(provider);
                newUser.setRoles(Set.of("USER"));
                return userRepository.save(newUser);
            });

        return oAuth2User;
    }
}

Success Handler — Issue JWT After OAuth2

@Component
public class OAuth2AuthenticationSuccessHandler
        extends SimpleUrlAuthenticationSuccessHandler {

    private final JwtService jwtService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
        String email = oAuth2User.getAttribute("email");

        // Generate JWT
        String token = jwtService.generateTokenForEmail(email);

        // Redirect to frontend with token
        String redirectUrl = "http://localhost:3000/oauth2/callback?token=" + token;
        getRedirectStrategy().sendRedirect(request, response, redirectUrl);
    }
}

Key Takeaways

  1. spring-boot-starter-oauth2-client + YAML config = OAuth2 login.
  2. Override DefaultOAuth2UserService to create/update local users.
  3. Issue a JWT after OAuth2 login for subsequent API calls.
  4. Store client secrets in environment variables, never in code.
  5. Common providers: Google, GitHub, Microsoft, Facebook, Okta.

What’s Next?

Next: Role-Based Access Control — @PreAuthorize, @Secured, and role hierarchies.

Updated: