Skip to content

OAuth2 Authentication

Comprehensive guide to OAuth2 authentication in Spring Security. Learn how to integrate with social login providers like Google, GitHub, and Facebook for modern authentication flows.

๐ŸŒ OAuth2 Overview

OAuth2 enables secure third-party authentication without exposing user credentials to your application. Users authenticate with trusted providers and grant limited access to your app.

sequenceDiagram
    participant U as User
    participant A as Application
    participant P as OAuth2 Provider
    participant R as Resource Server

    Note over U,R: OAuth2 Authorization Code Flow
    U->>A: Click "Login with Provider"
    A->>P: Redirect to authorization URL
    U->>P: Login with provider credentials
    P->>P: User grants permissions
    P->>A: Redirect with authorization code
    A->>P: Exchange code for access token
    P-->>A: Access token + refresh token
    A->>R: Request user info with token
    R-->>A: User profile data
    A->>A: Create user session
    A-->>U: Authentication successful

๐Ÿ”ง OAuth2 Configuration

OAuth2 Client Setup

@Configuration
@EnableOAuth2Client
@Profile("oauth2")
public class OAuth2Config {

    private static final Logger logger = LogManager.getLogger(OAuth2Config.class);

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        logger.info("๐ŸŒ [OAUTH2-CONFIG] Setting up OAuth2 client registrations");
        logger.debug("๐Ÿ“š [LEARNING] OAuth2 allows third-party authentication providers");

        return new InMemoryClientRegistrationRepository(
                googleClientRegistration(),
                githubClientRegistration(),
                facebookClientRegistration()
        );
    }

    @Bean
    public OAuth2AuthorizedClientService authorizedClientService() {
        return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository());
    }

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager() {
        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .authorizationCode()
                        .refreshToken()
                        .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultOAuth2AuthorizedClientManager(
                        clientRegistrationRepository(),
                        authorizedClientService()
                );

        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        logger.debug("โœ… [OAUTH2-CONFIG] OAuth2 client manager configured");

        return authorizedClientManager;
    }
}

OAuth2 Client Registrations

private ClientRegistration googleClientRegistration() {
    logger.debug("๐Ÿ” [OAUTH2-CONFIG] Configuring Google OAuth2 client");

    return ClientRegistration.withRegistrationId("google")
            .clientId("${oauth2.google.client-id}")
            .clientSecret("${oauth2.google.client-secret}")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
            .scope("openid", "profile", "email")
            .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
            .tokenUri("https://oauth2.googleapis.com/token")
            .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
            .userNameAttributeName(IdTokenClaimNames.SUB)
            .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
            .clientName("Google")
            .build();
}

private ClientRegistration githubClientRegistration() {
    logger.debug("๐Ÿ” [OAUTH2-CONFIG] Configuring GitHub OAuth2 client");

    return ClientRegistration.withRegistrationId("github")
            .clientId("${oauth2.github.client-id}")
            .clientSecret("${oauth2.github.client-secret}")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
            .scope("read:user")
            .authorizationUri("https://github.com/login/oauth/authorize")
            .tokenUri("https://github.com/login/oauth/access_token")
            .userInfoUri("https://api.github.com/user")
            .userNameAttributeName("id")
            .clientName("GitHub")
            .build();
}

private ClientRegistration facebookClientRegistration() {
    logger.debug("๐Ÿ” [OAUTH2-CONFIG] Configuring Facebook OAuth2 client");

    return ClientRegistration.withRegistrationId("facebook")
            .clientId("${oauth2.facebook.client-id}")
            .clientSecret("${oauth2.facebook.client-secret}")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
            .scope("email", "public_profile")
            .authorizationUri("https://www.facebook.com/v18.0/dialog/oauth")
            .tokenUri("https://graph.facebook.com/v18.0/oauth/access_token")
            .userInfoUri("https://graph.facebook.com/me?fields=id,name,email")
            .userNameAttributeName("id")
            .clientName("Facebook")
            .build();
}

๐Ÿ” Security Configuration

OAuth2 Security Filter Chain

```java @Configuration @EnableWebSecurity @Profile("oauth2") public class OAuth2SecurityConfig {

private static final Logger logger = LogManager.getLogger(OAuth2SecurityConfig.class);

@Autowired
private OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService;

@Bean
public SecurityFilterChain oauth2FilterChain(HttpSecurity http) throws Exception {
    logger.info("๐Ÿ” [OAUTH2-SECURITY] Configuring OAuth2 security filter chain");
    logger.debug("๐Ÿ“š [LEARNING] OAuth2 login with social providers");

    return http
        .authorizeHttpRequests(authz -> authz
            .requestMatchers("/", "/login", "/oauth2/**", "/api/public/**").permitAll()
            .requestMatchers("/api/oauth2/**").authenticated()
            .requestMatchers("/api/admin/**").hasRole("ADMIN")
            .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
            .anyRequest().authenticated()
        )

        .oauth2Login(oauth2 -> oauth2
            .loginPage("/login")
            .defaultSuccessUrl("/api/oauth2/success")
            .failureUrl("/login?error")
            .userInfoEndpoint(userInfo -> userInfo
                .userService(customOAuth2UserService)
            )\n            )\n            \n            .logout(logout -> logout\n                .logoutUrl(\"/logout\")\n                .logoutSuccessUrl(\"/\")\n                .invalidateHttpSession(true)\n                .clearAuthentication(true)\n            )\n            \n            .sessionManagement(session -> session\n                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)\n                .maximumSessions(1)\n                .maxSessionsPreventsLogin(false)\n            )\n            \n            .csrf(csrf -> csrf.disable())\n            \n            .build();\n    }\n}\n```\n\n## ๐ŸŽฏ **Custom OAuth2 User Service**\n\n### **OAuth2UserService Implementation**\n\n```java\n@Service\npublic class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {\n    \n    private static final Logger logger = LogManager.getLogger(CustomOAuth2UserService.class);\n    \n    private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();\n    \n    @Override\n    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {\n        OAuth2User oauth2User = delegate.loadUser(userRequest);\n        \n        String registrationId = userRequest.getClientRegistration().getRegistrationId();\n        String userNameAttributeName = userRequest.getClientRegistration()\n                .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();\n                \n        logger.info(\"๐ŸŒ [OAUTH2-USER] Loading OAuth2 user from provider: {}\", registrationId);\n        logger.debug(\"๐Ÿ“Š [OAUTH2-USER] User name attribute: {}\", userNameAttributeName);\n        \n        // Extract user information based on provider\n        OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(\n                registrationId, \n                oauth2User.getAttributes()\n        );\n        \n        logger.debug(\"๐Ÿ‘ค [OAUTH2-USER] User info extracted:\");\n        logger.debug(\"   โ€ข ID: {}\", userInfo.getId());\n        logger.debug(\"   โ€ข Name: {}\", userInfo.getName());\n        logger.debug(\"   โ€ข Email: {}\", userInfo.getEmail());\n        \n        // Check if user exists in database, create if not\n        User user = createOrUpdateUser(userInfo, registrationId);\n        \n        // Create custom principal with user details\n        return new CustomOAuth2User(\n                oauth2User.getAuthorities(),\n                oauth2User.getAttributes(),\n                userNameAttributeName,\n                user\n        );\n    }\n    \n    private User createOrUpdateUser(OAuth2UserInfo userInfo, String provider) {\n        logger.debug(\"๐Ÿ”„ [OAUTH2-USER] Creating/updating user for provider: {}\", provider);\n        \n        // Implementation for user creation/update logic\n        User user = User.builder()\n                .providerId(userInfo.getId())\n                .provider(provider)\n                .name(userInfo.getName())\n                .email(userInfo.getEmail())\n                .role(\"ROLE_USER\") // Default role\n                .enabled(true)\n                .build();\n                \n        logger.debug(\"โœ… [OAUTH2-USER] User created/updated successfully\");\n        return user;\n    }\n}\n```\n\n### **OAuth2 User Info Factory**\n\n```java\npublic class OAuth2UserInfoFactory {\n    \n    public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, \n                                                   Map<String, Object> attributes) {\n        \n        return switch (registrationId.toLowerCase()) {\n            case \"google\" -> new GoogleOAuth2UserInfo(attributes);\n            case \"github\" -> new GitHubOAuth2UserInfo(attributes);\n            case \"facebook\" -> new FacebookOAuth2UserInfo(attributes);\n            default -> throw new OAuth2AuthenticationProcessingException(\n                    \"Login with \" + registrationId + \" is not supported\");\n        };\n    }\n}\n\n// Provider-specific user info classes\npublic class GoogleOAuth2UserInfo extends OAuth2UserInfo {\n    \n    public GoogleOAuth2UserInfo(Map<String, Object> attributes) {\n        super(attributes);\n    }\n    \n    @Override\n    public String getId() {\n        return (String) attributes.get(\"sub\");\n    }\n    \n    @Override\n    public String getName() {\n        return (String) attributes.get(\"name\");\n    }\n    \n    @Override\n    public String getEmail() {\n        return (String) attributes.get(\"email\");\n    }\n    \n    @Override\n    public String getImageUrl() {\n        return (String) attributes.get(\"picture\");\n    }\n}\n```\n\n## ๐Ÿš€ **Usage Examples**\n\n### **1. OAuth2 Login Endpoints**\n\n```bash\n# Initiate Google OAuth2 login\ncurl -v http://localhost:8080/oauth2/authorization/google\n\n# Initiate GitHub OAuth2 login\ncurl -v http://localhost:8080/oauth2/authorization/github\n\n# Initiate Facebook OAuth2 login\ncurl -v http://localhost:8080/oauth2/authorization/facebook\n```\n\n### **2. OAuth2 User Info Endpoint**\n\n```java\n@RestController\n@RequestMapping(\"/api/oauth2\")\npublic class OAuth2Controller {\n    \n    @GetMapping(\"/user\")\n    public ResponseEntity<Map<String, Object>> getCurrentUser(\n            @AuthenticationPrincipal OAuth2User principal) {\n        \n        Map<String, Object> userInfo = new HashMap<>();\n        userInfo.put(\"name\", principal.getAttribute(\"name\"));\n        userInfo.put(\"email\", principal.getAttribute(\"email\"));\n        userInfo.put(\"provider\", getProvider(principal));\n        userInfo.put(\"authorities\", principal.getAuthorities());\n        \n        return ResponseEntity.ok(userInfo);\n    }\n    \n    @GetMapping(\"/success\")\n    public ResponseEntity<Map<String, Object>> loginSuccess(\n            Authentication authentication) {\n            \n        OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal();\n        \n        return ResponseEntity.ok(Map.of(\n            \"message\", \"OAuth2 login successful\",\n            \"user\", oauth2User.getAttribute(\"name\"),\n            \"email\", oauth2User.getAttribute(\"email\"),\n            \"provider\", getProvider(oauth2User)\n        ));\n    }\n}\n```\n\n### **3. OAuth2 Response Example**\n\n```json\n{\n  \"message\": \"OAuth2 login successful\",\n  \"user\": \"John Doe\",\n  \"email\": \"john.doe@example.com\",\n  \"provider\": \"google\",\n  \"authorities\": [\n    {\n      \"authority\": \"ROLE_USER\"\n    },\n    {\n      \"authority\": \"OAUTH2_USER\"\n    }\n  ],\n  \"attributes\": {\n    \"sub\": \"123456789\",\n    \"name\": \"John Doe\",\n    \"email\": \"john.doe@example.com\",\n    \"picture\": \"https://example.com/avatar.jpg\"\n  }\n}\n```\n\n## ๐Ÿ”ง **Configuration Properties**\n\n### **Application Properties**\n\n```yaml\n# application-oauth2.yml\nspring:\n  security:\n    oauth2:\n      client:\n        registration:\n          google:\n            client-id: ${GOOGLE_CLIENT_ID}\n            client-secret: ${GOOGLE_CLIENT_SECRET}\n            scope:\n              - openid\n              - profile\n              - email\n            redirect-uri: \"{baseUrl}/login/oauth2/code/{registrationId}\"\n          \n          github:\n            client-id: ${GITHUB_CLIENT_ID}\n            client-secret: ${GITHUB_CLIENT_SECRET}\n            scope:\n              - read:user\n              - user:email\n            redirect-uri: \"{baseUrl}/login/oauth2/code/{registrationId}\"\n          \n          facebook:\n            client-id: ${FACEBOOK_CLIENT_ID}\n            client-secret: ${FACEBOOK_CLIENT_SECRET}\n            scope:\n              - email\n              - public_profile\n            redirect-uri: \"{baseUrl}/login/oauth2/code/{registrationId}\"\n        \n        provider:\n          facebook:\n            authorization-uri: https://www.facebook.com/v18.0/dialog/oauth\n            token-uri: https://graph.facebook.com/v18.0/oauth/access_token\n            user-info-uri: https://graph.facebook.com/me?fields=id,name,email\n            user-name-attribute: id\n\nlogging:\n  level:\n    org.springframework.security.oauth2: DEBUG\n    com.example.oauth2: DEBUG\n```\n\n## ๐Ÿงช **Testing OAuth2 Authentication**\n\n### **Integration Tests**\n\n```java\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\n@ActiveProfiles(\"oauth2\")\nclass OAuth2AuthenticationTest {\n    \n    @Autowired\n    private TestRestTemplate restTemplate;\n    \n    @Autowired\n    private ClientRegistrationRepository clientRegistrationRepository;\n    \n    @Test\n    void shouldHaveOAuth2ClientRegistrations() {\n        // Test OAuth2 client registrations are configured\n        ClientRegistration google = clientRegistrationRepository.findByRegistrationId(\"google\");\n        ClientRegistration github = clientRegistrationRepository.findByRegistrationId(\"github\");\n        ClientRegistration facebook = clientRegistrationRepository.findByRegistrationId(\"facebook\");\n        \n        assertThat(google).isNotNull();\n        assertThat(github).isNotNull();\n        assertThat(facebook).isNotNull();\n        \n        assertThat(google.getClientName()).isEqualTo(\"Google\");\n        assertThat(github.getClientName()).isEqualTo(\"GitHub\");\n        assertThat(facebook.getClientName()).isEqualTo(\"Facebook\");\n    }\n    \n    @Test\n    void shouldRedirectToOAuth2Provider() {\n        // Test OAuth2 authorization redirection\n        ResponseEntity<String> response = restTemplate.getForEntity(\n                \"/oauth2/authorization/google\", \n                String.class\n        );\n        \n        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FOUND);\n        assertThat(response.getHeaders().getLocation().toString())\n                .contains(\"accounts.google.com/o/oauth2/v2/auth\");\n    }\n}\n```\n\n### **Mock OAuth2 User Tests**\n\n```java\n@Test\n@WithMockOAuth2User(value = \"123456789\", attributes = {\n    \"sub\", \"123456789\",\n    \"name\", \"Test User\",\n    \"email\", \"test@example.com\"\n})\nvoid shouldAuthenticateWithMockOAuth2User() {\n    ResponseEntity<Map> response = restTemplate.getForEntity(\n            \"/api/oauth2/user\", \n            Map.class\n    );\n    \n    assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);\n    assertThat(response.getBody().get(\"name\")).isEqualTo(\"Test User\");\n    assertThat(response.getBody().get(\"email\")).isEqualTo(\"test@example.com\");\n}\n```\n\n## ๐Ÿ”„ **OAuth2 Token Management**\n\n### **Token Refresh**\n\n```java\n@Component\npublic class OAuth2TokenRefreshService {\n    \n    private final OAuth2AuthorizedClientManager authorizedClientManager;\n    \n    public String refreshAccessToken(String clientRegistrationId, String principalName) {\n        OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest\n                .withClientRegistrationId(clientRegistrationId)\n                .principal(principalName)\n                .build();\n                \n        OAuth2AuthorizedClient authorizedClient = \n                authorizedClientManager.authorize(authorizeRequest);\n                \n        if (authorizedClient != null && authorizedClient.getAccessToken() != null) {\n            return authorizedClient.getAccessToken().getTokenValue();\n        }\n        \n        throw new IllegalStateException(\"Unable to refresh OAuth2 token\");\n    }\n}\n```\n\n### **Token Storage**\n\n```java\n@Bean\npublic OAuth2AuthorizedClientService authorizedClientService() {\n    // Use JdbcOAuth2AuthorizedClientService for persistent token storage\n    return new JdbcOAuth2AuthorizedClientService(\n            jdbcTemplate,\n            clientRegistrationRepository()\n    );\n}\n```\n\n## โšก **OAuth2 Best Practices**\n\n### **โœ… Do's**\n\n1. **Use HTTPS in production** - OAuth2 requires secure communication\n2. **Validate redirect URIs** - Prevent authorization code interception\n3. **Implement proper scopes** - Request minimal required permissions\n4. **Store tokens securely** - Use encrypted storage for access/refresh tokens\n5. **Handle token expiration** - Implement automatic token refresh\n\n### **โŒ Don'ts**\n\n1. **Don't store client secrets** in source code or client-side applications\n2. **Don't use implicit flow** - Use authorization code flow with PKCE\n3. **Don't ignore state parameters** - Prevent CSRF attacks\n4. **Don't trust client-side tokens** - Always validate tokens server-side\n5. **Don't expose sensitive scopes** - Limit access to necessary data only\n\n### **๐Ÿ”ง Production Configuration**\n\n```yaml\n# application-production.yml\nspring:\n  security:\n    oauth2:\n      client:\n        registration:\n          google:\n            client-id: ${GOOGLE_OAUTH2_CLIENT_ID}\n            client-secret: ${GOOGLE_OAUTH2_CLIENT_SECRET}\n            \nlogging:\n  level:\n    org.springframework.security.oauth2: INFO # Reduce debug logging\n    \n# Environment variables for production\n# GOOGLE_OAUTH2_CLIENT_ID=your-google-client-id\n# GOOGLE_OAUTH2_CLIENT_SECRET=your-google-client-secret\n# GITHUB_OAUTH2_CLIENT_ID=your-github-client-id\n# GITHUB_OAUTH2_CLIENT_SECRET=your-github-client-secret\n```\n\n## ๐Ÿš€ **Next Steps**\n\n- **[SSO Integration โ†’](sso-integration.md)** - Enterprise single sign-on\n- **[JWT Tokens โ†’](jwt-tokens.md)** - Combine OAuth2 with JWT\n- **[LDAP Authentication โ†’](ldap-auth.md)** - Directory-based authentication\n- **[API Reference โ†’](../api/auth-flow.md)** - OAuth2 API patterns\n- **[Security Configuration โ†’](../security/index.md)** - OAuth2 security setup\n\n---\n\n**๐ŸŒ OAuth2 authentication enables modern social login flows while maintaining security and user privacy. Understanding provider integration and token management is essential for contemporary web applications.**