OAuth2 Social Login
Social login lets users sign in with an existing Google or GitHub account instead of creating yet another password. Spring Boot’s spring-boot-starter-oauth2-client implements the full authorization code + PKCE flow for you: add a dependency, register the provider’s client id and secret, and call .oauth2Login() in your SecurityFilterChain. This page wires up Google and GitHub and maps the authenticated user to a local account.
Dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
This starter brings Spring Security plus the OAuth2/OIDC client support. Just having it on the classpath enables a login page listing your configured providers.
Registering providers in application.yml
Each provider is a client registration under spring.security.oauth2.client.registration. Google and GitHub are well-known providers, so Spring already knows their endpoints — you only supply credentials and scopes.
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
scope: openid, profile, email
github:
client-id: ${GITHUB_CLIENT_ID}
client-secret: ${GITHUB_CLIENT_SECRET}
scope: read:user, user:email
The google and github keys are recognized common providers — Spring Boot supplies the authorization, token, user-info, and JWK endpoints automatically. For any other provider, add a matching spring.security.oauth2.client.provider.<name> block with issuer-uri (or the individual endpoints).
Note: The default redirect URI is
{baseUrl}/login/oauth2/code/{registrationId}— for Google that ishttp://localhost:8080/login/oauth2/code/google. Register exactly that URI in the Google Cloud / GitHub OAuth app settings, or the provider rejects the callback.
Warning: Never hardcode the client secret. Inject it from environment variables or a secret manager as shown with
${GOOGLE_CLIENT_SECRET}.
Enabling login in the filter chain
Add .oauth2Login() to the lambda DSL. With zero further config, Spring Security renders a provider-selection page and handles the entire redirect dance.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/error", "/webjars/**").permitAll()
.anyRequest().authenticated())
.oauth2Login(oauth -> oauth
.defaultSuccessUrl("/home", true))
.logout(logout -> logout.logoutSuccessUrl("/"))
.build();
}
}
Hitting any protected URL now redirects unauthenticated users to /oauth2/authorization/google (or GitHub), which forwards them to the provider’s consent screen.
Accessing the authenticated user
After login, Spring Security stores an OAuth2AuthenticationToken. Because Google uses OIDC, its principal is an OidcUser (with verified ID-token claims); GitHub is plain OAuth2 so its principal is an OAuth2User. Inject whichever you need with @AuthenticationPrincipal.
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class ProfileController {
@GetMapping("/me")
public Map<String, Object> me(@AuthenticationPrincipal OAuth2User principal) {
return Map.of(
"name", principal.getAttribute("name"),
"email", principal.getAttribute("email"),
"attributes", principal.getAttributes());
}
@GetMapping("/me/oidc")
public Map<String, Object> oidc(@AuthenticationPrincipal OidcUser user) {
return Map.of(
"subject", user.getSubject(),
"email", user.getEmail(),
"claims", user.getClaims());
}
}
Output for a Google login:
{
"name": "Alice Doe",
"email": "[email protected]",
"attributes": { "sub": "1147...", "name": "Alice Doe", "email": "[email protected]", "email_verified": true }
}
Tip: GitHub does not return the email in the default profile if it is private — request the
user:emailscope (as above) and Spring fetches it from GitHub’s/user/emailsendpoint.
Mapping to a local user
You rarely want to live with only the provider’s profile — you need a local user row to attach orders, roles, and preferences. Provision (or look up) a local user the first time someone logs in by extending DefaultOAuth2UserService and returning an OAuth2User carrying your own authorities.
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class LocalOAuth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest request) {
OAuth2User oauthUser = super.loadUser(request);
String email = oauthUser.getAttribute("email");
// Provision on first login, otherwise reuse the existing account.
AppUser user = userRepository.findByEmail(email)
.orElseGet(() -> userRepository.save(AppUser.fromOAuth(oauthUser)));
var authority = new SimpleGrantedAuthority("ROLE_" + user.getRole());
return new DefaultOAuth2User(List.of(authority),
oauthUser.getAttributes(), "email");
}
}
Register it in the filter chain so it runs after the provider returns the profile:
.oauth2Login(oauth -> oauth
.userInfoEndpoint(info -> info.userService(localOAuth2UserService))
.defaultSuccessUrl("/home", true))
The third argument to DefaultOAuth2User ("email") is the name attribute key — the claim Spring uses as principal.getName(). For Google’s OIDC flow, extend OidcUserService and return an OidcUser instead, so the ID-token claims are preserved.
How it differs from JWT auth
| OAuth2 social login (this page) | Custom JWT | |
|---|---|---|
| Who authenticates | External provider (Google/GitHub) | Your own /auth/login |
| Session | Server-side HttpSession cookie | Stateless bearer token |
| Best for | Browser-facing web apps | APIs, SPAs, mobile clients |
Social login is session-based by default (the user gets a cookie). If you need stateless tokens for an SPA, combine login with token issuance, or front your API with an OAuth2 resource server.