Social Login with OAuth 2.0 in Java: Complete Implementation Guide

Introduction

Social login allows users to authenticate using their existing social media accounts, reducing registration friction and improving user experience. OAuth 2.0 is the industry standard protocol for secure delegated access. This guide explores how to implement social login in Java applications using Spring Security OAuth2, covering major providers like Google, Facebook, GitHub, and custom OAuth2 servers.


Article: Implementing Social Login with OAuth 2.0 in Java Applications

Social login implementation in Java involves integrating with OAuth 2.0 providers to authenticate users and obtain their profile information. Spring Security provides comprehensive support for OAuth2 flows, making it straightforward to add social authentication to your Java applications.

1. OAuth 2.0 Flow Overview

Authorization Code Flow:

1. User clicks "Login with [Provider]"
2. Redirect to OAuth provider → 3. User authenticates and consents
4. Redirect back with authorization code → 5. Exchange code for access token
6. Use access token to get user info → 7. Create application session

2. Maven Dependencies

pom.xml:

<properties>
<spring-boot.version>3.1.0</spring-boot.version>
<spring-security.version>6.1.0</spring-security.version>
<jjwt.version>0.11.5</jjwt.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- OAuth2 Support -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>${spring-security.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>

3. Application Configuration

application.yml:

spring:
security:
oauth2:
client:
registration:
# Google OAuth2 Configuration
google:
client-id: ${GOOGLE_CLIENT_ID:}
client-secret: ${GOOGLE_CLIENT_SECRET:}
scope:
- email
- profile
- openid
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
authorization-grant-type: authorization_code
client-name: Google
# Facebook OAuth2 Configuration
facebook:
client-id: ${FACEBOOK_CLIENT_ID:}
client-secret: ${FACEBOOK_CLIENT_SECRET:}
scope:
- email
- public_profile
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
authorization-grant-type: authorization_code
client-name: Facebook
# GitHub OAuth2 Configuration
github:
client-id: ${GITHUB_CLIENT_ID:}
client-secret: ${GITHUB_CLIENT_SECRET:}
scope:
- user:email
- read:user
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
authorization-grant-type: authorization_code
client-name: GitHub
# Custom OAuth2 Provider
my-custom-provider:
client-id: ${CUSTOM_CLIENT_ID:}
client-secret: ${CUSTOM_CLIENT_SECRET:}
scope:
- openid
- email
- profile
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
authorization-grant-type: authorization_code
client-name: MyCustomProvider
provider:
# Custom OAuth2 Provider Configuration
my-custom-provider:
authorization-uri: https://auth.mycompany.com/oauth2/authorize
token-uri: https://auth.mycompany.com/oauth2/token
user-info-uri: https://auth.mycompany.com/oauth2/userinfo
user-name-attribute: sub
# Database Configuration
datasource:
url: jdbc:postgresql://localhost:5432/social_login
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:password}
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
# Application Configuration
app:
auth:
jwt:
secret: ${JWT_SECRET:mySecretKey}
expiration-ms: 86400000 # 24 hours
cors:
allowed-origins: "http://localhost:3000,http://localhost:8080"

4. Domain Models

User Entity:

package com.myapp.auth.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users", uniqueConstraints = {
@UniqueConstraint(columnNames = "email"),
@UniqueConstraint(columnNames = "provider_id")
})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Email
@Column(nullable = false)
private String email;
private String imageUrl;
@Column(nullable = false)
private Boolean emailVerified = false;
@NotNull
@Enumerated(EnumType.STRING)
private AuthProvider provider;
@Column(name = "provider_id")
private String providerId;
private String password;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role")
private Set<String> roles = new HashSet<>();
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// Constructors
public User() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
public User(String name, String email, AuthProvider provider, String providerId) {
this();
this.name = name;
this.email = email;
this.provider = provider;
this.providerId = providerId;
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public Boolean getEmailVerified() { return emailVerified; }
public void setEmailVerified(Boolean emailVerified) { this.emailVerified = emailVerified; }
public AuthProvider getProvider() { return provider; }
public void setProvider(AuthProvider provider) { this.provider = provider; }
public String getProviderId() { return providerId; }
public void setProviderId(String providerId) { this.providerId = providerId; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Set<String> getRoles() { return roles; }
public void setRoles(Set<String> roles) { this.roles = roles; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
@PreUpdate
public void preUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
enum AuthProvider {
LOCAL, GOOGLE, FACEBOOK, GITHUB, CUSTOM
}

OAuth2 User Info:

package com.myapp.auth.model;
import java.util.Map;
public class OAuth2UserInfo {
private String id;
private String name;
private String email;
private String imageUrl;
private Map<String, Object> attributes;
public OAuth2UserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
// Factory methods for different providers
public static OAuth2UserInfo fromGoogle(Map<String, Object> attributes) {
OAuth2UserInfo userInfo = new OAuth2UserInfo(attributes);
userInfo.id = (String) attributes.get("sub");
userInfo.name = (String) attributes.get("name");
userInfo.email = (String) attributes.get("email");
userInfo.imageUrl = (String) attributes.get("picture");
return userInfo;
}
public static OAuth2UserInfo fromFacebook(Map<String, Object> attributes) {
OAuth2UserInfo userInfo = new OAuth2UserInfo(attributes);
userInfo.id = (String) attributes.get("id");
userInfo.name = (String) attributes.get("name");
userInfo.email = (String) attributes.get("email");
userInfo.imageUrl = (String) attributes.get("picture");
return userInfo;
}
public static OAuth2UserInfo fromGitHub(Map<String, Object> attributes) {
OAuth2UserInfo userInfo = new OAuth2UserInfo(attributes);
userInfo.id = String.valueOf(attributes.get("id"));
userInfo.name = (String) attributes.get("name");
userInfo.email = (String) attributes.get("email");
userInfo.imageUrl = (String) attributes.get("avatar_url");
return userInfo;
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getImageUrl() { return imageUrl; }
public Map<String, Object> getAttributes() { return attributes; }
}

5. Custom OAuth2 User Service

Custom OAuth2 User Service:

package com.myapp.auth.service;
import com.myapp.auth.model.AuthProvider;
import com.myapp.auth.model.OAuth2UserInfo;
import com.myapp.auth.model.User;
import com.myapp.auth.repository.UserRepository;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Optional;
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
public CustomOAuth2UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(oAuth2UserRequest);
try {
return processOAuth2User(oAuth2UserRequest, oAuth2User);
} catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex.getCause());
}
}
private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
OAuth2UserInfo oAuth2UserInfo = extractUserInfo(oAuth2UserRequest, oAuth2User);
if (!StringUtils.hasText(oAuth2UserInfo.getEmail())) {
throw new OAuth2AuthenticationException("Email not found from OAuth2 provider");
}
Optional<User> userOptional = userRepository.findByEmail(oAuth2UserInfo.getEmail());
User user;
if (userOptional.isPresent()) {
user = userOptional.get();
if (!user.getProvider().equals(AuthProvider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId().toUpperCase()))) {
throw new OAuth2AuthenticationException("Looks like you're signed up with " +
user.getProvider() + " account. Please use your " + user.getProvider() +
" account to login.");
}
user = updateExistingUser(user, oAuth2UserInfo);
} else {
user = registerNewUser(oAuth2UserRequest, oAuth2UserInfo);
}
return UserPrincipal.create(user, oAuth2User.getAttributes());
}
private OAuth2UserInfo extractUserInfo(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
String registrationId = oAuth2UserRequest.getClientRegistration().getRegistrationId();
Map<String, Object> attributes = oAuth2User.getAttributes();
return switch (registrationId.toLowerCase()) {
case "google" -> OAuth2UserInfo.fromGoogle(attributes);
case "facebook" -> OAuth2UserInfo.fromFacebook(attributes);
case "github" -> OAuth2UserInfo.fromGitHub(attributes);
case "my-custom-provider" -> extractCustomProviderInfo(attributes);
default -> throw new OAuth2AuthenticationException("Sorry! Login with " + registrationId + " is not supported yet.");
};
}
private OAuth2UserInfo extractCustomProviderInfo(Map<String, Object> attributes) {
OAuth2UserInfo userInfo = new OAuth2UserInfo(attributes);
userInfo.setId((String) attributes.get("sub"));
userInfo.setName((String) attributes.get("name"));
userInfo.setEmail((String) attributes.get("email"));
userInfo.setImageUrl((String) attributes.get("picture"));
return userInfo;
}
private User registerNewUser(OAuth2UserRequest oAuth2UserRequest, OAuth2UserInfo oAuth2UserInfo) {
User user = new User();
user.setProvider(AuthProvider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId().toUpperCase()));
user.setProviderId(oAuth2UserInfo.getId());
user.setName(oAuth2UserInfo.getName());
user.setEmail(oAuth2UserInfo.getEmail());
user.setImageUrl(oAuth2UserInfo.getImageUrl());
user.setEmailVerified(true);
user.getRoles().add("ROLE_USER");
return userRepository.save(user);
}
private User updateExistingUser(User existingUser, OAuth2UserInfo oAuth2UserInfo) {
existingUser.setName(oAuth2UserInfo.getName());
existingUser.setImageUrl(oAuth2UserInfo.getImageUrl());
return userRepository.save(existingUser);
}
}

6. User Principal Implementation

User Principal:

package com.myapp.auth.service;
import com.myapp.auth.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
public class UserPrincipal implements OAuth2User, UserDetails {
private Long id;
private String email;
private String password;
private Collection<? extends GrantedAuthority> authorities;
private Map<String, Object> attributes;
public UserPrincipal(Long id, String email, String password, 
Collection<? extends GrantedAuthority> authorities) {
this.id = id;
this.email = email;
this.password = password;
this.authorities = authorities;
}
public static UserPrincipal create(User user) {
Collection<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role))
.collect(Collectors.toList());
return new UserPrincipal(
user.getId(),
user.getEmail(),
user.getPassword(),
authorities
);
}
public static UserPrincipal create(User user, Map<String, Object> attributes) {
UserPrincipal userPrincipal = UserPrincipal.create(user);
userPrincipal.setAttributes(attributes);
return userPrincipal;
}
// UserDetails methods
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// OAuth2User methods
@Override
public String getName() {
return String.valueOf(id);
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
// Getters
public Long getId() { return id; }
public String getEmail() { return email; }
public String getPassword() { return password; }
}

7. JWT Token Provider

JWT Token Provider:

package com.myapp.auth.security;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
@Component
public class JwtTokenProvider {
private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
@Value("${app.auth.jwt.secret}")
private String jwtSecret;
@Value("${app.auth.jwt.expiration-ms}")
private int jwtExpirationMs;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
}
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationMs);
return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
.compact();
}
public Long getUserIdFromJWT(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
public boolean validateToken(String authToken) {
try {
Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(authToken);
return true;
} catch (SecurityException ex) {
logger.error("Invalid JWT signature");
} catch (MalformedJwtException ex) {
logger.error("Invalid JWT token");
} catch (ExpiredJwtException ex) {
logger.error("Expired JWT token");
} catch (UnsupportedJwtException ex) {
logger.error("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
logger.error("JWT claims string is empty");
}
return false;
}
}

8. Security Configuration

Security Configuration:

package com.myapp.auth.config;
import com.myapp.auth.security.JwtTokenProvider;
import com.myapp.auth.service.CustomOAuth2UserService;
import com.myapp.auth.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
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.method.configuration.EnableMethodSecurity;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private CustomOAuth2UserService customOAuth2UserService;
@Autowired
private JwtTokenProvider tokenProvider;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter(tokenProvider);
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/", "/error", "/api/auth/**", "/oauth2/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.successHandler(oauth2AuthenticationSuccessHandler())
.failureHandler(oauth2AuthenticationFailureHandler())
)
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("http://localhost:3000", "http://localhost:8080"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public OAuth2AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler() {
return new OAuth2AuthenticationSuccessHandler(tokenProvider);
}
@Bean
public OAuth2AuthenticationFailureHandler oauth2AuthenticationFailureHandler() {
return new OAuth2AuthenticationFailureHandler();
}
}

9. OAuth2 Authentication Handlers

OAuth2 Success Handler:

package com.myapp.auth.config;
import com.myapp.auth.security.JwtTokenProvider;
import com.myapp.auth.service.UserPrincipal;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JwtTokenProvider tokenProvider;
public OAuth2AuthenticationSuccessHandler(JwtTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String targetUrl = determineTargetUrl(request, response, authentication);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
clearAuthenticationAttributes(request);
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
String targetUrl = "http://localhost:3000/oauth2/redirect";
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
String token = tokenProvider.generateToken(authentication);
return UriComponentsBuilder.fromUriString(targetUrl)
.queryParam("token", token)
.queryParam("userId", userPrincipal.getId())
.build().toUriString();
}
}

OAuth2 Failure Handler:

package com.myapp.auth.config;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class OAuth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String targetUrl = "http://localhost:3000/oauth2/redirect";
targetUrl = UriComponentsBuilder.fromUriString(targetUrl)
.queryParam("error", exception.getLocalizedMessage())
.build().toUriString();
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}

10. JWT Authentication Filter

JWT Authentication Filter:

package com.myapp.auth.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserDetailsService userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(userId.toString());
UsernamePasswordAuthenticationToken authentication = 
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

11. REST Controllers

Auth Controller:

package com.myapp.auth.controller;
import com.myapp.auth.model.User;
import com.myapp.auth.repository.UserRepository;
import com.myapp.auth.security.JwtTokenProvider;
import com.myapp.auth.service.UserPrincipal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider tokenProvider;
@Autowired
private UserRepository userRepository;
@GetMapping("/user")
public ResponseEntity<?> getCurrentUser(@CurrentUser UserPrincipal userPrincipal) {
Optional<User> user = userRepository.findById(userPrincipal.getId());
return ResponseEntity.ok(user);
}
@PostMapping("/signin")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Map<String, Object> response = new HashMap<>();
response.put("accessToken", jwt);
response.put("tokenType", "Bearer");
response.put("user", userPrincipal);
return ResponseEntity.ok(response);
}
@GetMapping("/providers")
public ResponseEntity<?> getOAuthProviders() {
Map<String, Object> providers = new HashMap<>();
providers.put("google", true);
providers.put("facebook", true);
providers.put("github", true);
providers.put("custom", true);
return ResponseEntity.ok(providers);
}
// Login Request DTO
public static class LoginRequest {
private String email;
private String password;
// Getters and setters
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
}

User Controller:

package com.myapp.auth.controller;
import com.myapp.auth.model.User;
import com.myapp.auth.repository.UserRepository;
import com.myapp.auth.service.UserPrincipal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/profile")
public ResponseEntity<?> getUserProfile(@CurrentUser UserPrincipal userPrincipal) {
Optional<User> user = userRepository.findById(userPrincipal.getId());
return user.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/all")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userRepository.findAll();
return ResponseEntity.ok(users);
}
@PutMapping("/profile")
public ResponseEntity<?> updateUserProfile(@CurrentUser UserPrincipal userPrincipal,
@RequestBody User updatedUser) {
Optional<User> userOptional = userRepository.findById(userPrincipal.getId());
if (userOptional.isPresent()) {
User user = userOptional.get();
user.setName(updatedUser.getName());
user.setImageUrl(updatedUser.getImageUrl());
User savedUser = userRepository.save(user);
return ResponseEntity.ok(savedUser);
}
return ResponseEntity.notFound().build();
}
}

12. Repository Layer

User Repository:

package com.myapp.auth.repository;
import com.myapp.auth.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByProviderId(String providerId);
Boolean existsByEmail(String email);
// Custom queries for social login
Optional<User> findByEmailAndProvider(String email, String provider);
Optional<User> findByProviderIdAndProvider(String providerId, String provider);
}

13. Custom Annotation for Current User

Current User Annotation:

package com.myapp.auth.controller;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@AuthenticationPrincipal
public @interface CurrentUser {
}

14. Application Class

Spring Boot Application:

package com.myapp.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class SocialLoginApplication {
public static void main(String[] args) {
SpringApplication.run(SocialLoginApplication.class, args);
}
}

15. Frontend Integration Example

React Component Example:

import React from 'react';
const SocialLoginButtons = () => {
const handleSocialLogin = (provider) => {
window.location.href = `http://localhost:8080/oauth2/authorization/${provider}`;
};
return (
<div className="social-login-buttons">
<button 
className="btn btn-google"
onClick={() => handleSocialLogin('google')}
>
Sign in with Google
</button>
<button 
className="btn btn-facebook"
onClick={() => handleSocialLogin('facebook')}
>
Sign in with Facebook
</button>
<button 
className="btn btn-github"
onClick={() => handleSocialLogin('github')}
>
Sign in with GitHub
</button>
</div>
);
};
export default SocialLoginButtons;

Benefits of Social Login with OAuth 2.0

  1. Improved User Experience - Faster registration and login
  2. Reduced Password Fatigue - Users don't need to remember another password
  3. Enhanced Security - Leverage security measures of major providers
  4. Rich User Data - Access to profile information with user consent
  5. Scalability - Easy to add new social providers
  6. Trust - Users trust established identity providers

Conclusion

Implementing social login with OAuth 2.0 in Java applications provides a secure and user-friendly authentication experience. By leveraging Spring Security's OAuth2 client support, you can easily integrate multiple social providers while maintaining control over user data and application security.

The key to successful implementation is:

  • Proper OAuth2 provider configuration with correct scopes and redirect URIs
  • Secure JWT token management for stateless authentication
  • Comprehensive error handling for various OAuth2 flow scenarios
  • User data synchronization between social providers and your application
  • Security best practices including CSRF protection and secure token storage

Start with one social provider (like Google) and gradually add more as you validate the integration. Monitor authentication flows and user feedback to continuously improve the social login experience.


Call to Action: Begin by setting up Google OAuth2 credentials and implementing a single social provider. Test the complete authentication flow, then gradually add more providers and advanced features like account linking and multi-provider support.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper