This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Authorization Server 1.4.0!

How-to: Authenticate using Social Login

This guide shows how to configure Spring Authorization Server with a social login provider (such as Google, GitHub, etc.) for authentication. The purpose of this guide is to demonstrate how to replace Form Login with OAuth 2.0 Login.

Spring Authorization Server is built on Spring Security and we will be using Spring Security concepts throughout this guide.

Register with Social Login Provider

To get started, you will need to set up an application with your chosen social login provider. Common providers include:

Follow the steps for your provider until you are asked to specify a Redirect URI. To set up a Redirect URI, choose a registrationId (such as google, my-client or any other unique identifier you wish) which you will use to configure both Spring Security and your provider.

The registrationId is a unique identifier for the ClientRegistration in Spring Security. The default Redirect URI template is {baseUrl}/login/oauth2/code/{registrationId}. See Setting the Redirect URI in the Spring Security reference for more information.
For example, testing locally on port 9000 with a registrationId of google, your Redirect URI would be localhost:9000/login/oauth2/code/google. Enter this value as the Redirect URI when setting up the application with your provider.

Once you’ve completed the set-up process with your social login provider, you should have obtained credentials (a Client ID and Client Secret). In addition, you will need to reference the provider’s documentation and take note of the following values:

  • Authorization URI: The endpoint that is used to initiate the authorization_code flow at the provider.

  • Token URI: The endpoint that is used to exchange an authorization_code for an access_token and optionally an id_token.

  • JWK Set URI: The endpoint that is used to obtain keys for verifying the signature of a JWT, which is required when an id_token is available.

  • User Info URI: The endpoint that is used to obtain user information, which is required when an id_token is not available.

  • User Name Attribute: The claim in either the id_token or the User Info Response containing the username of the user.

Configure OAuth 2.0 Login

Once you’ve registered with a social login provider, you can proceed to configuring Spring Security for OAuth 2.0 Login.

Add OAuth2 Client Dependency

First, add the following dependency:

  • Maven

  • Gradle

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

Register a Client

Next, configure the ClientRegistration with the values obtained earlier. Using Okta as an example, configure the following properties:

application.yml
okta:
  base-url: ${OKTA_BASE_URL}

spring:
  security:
    oauth2:
      client:
        registration:
          my-client:
            provider: okta
            client-id: ${OKTA_CLIENT_ID}
            client-secret: ${OKTA_CLIENT_SECRET}
            scope:
              - openid
              - profile
              - email
        provider:
          okta:
            authorization-uri: ${okta.base-url}/oauth2/v1/authorize
            token-uri: ${okta.base-url}/oauth2/v1/token
            user-info-uri: ${okta.base-url}/oauth2/v1/userinfo
            jwk-set-uri: ${okta.base-url}/oauth2/v1/keys
            user-name-attribute: sub
The registrationId in the above example is my-client.
The above example demonstrates the recommended way to set the Provider URL, Client ID and Client Secret using environment variables (OKTA_BASE_URL, OKTA_CLIENT_ID and OKTA_CLIENT_SECRET). See Externalized Configuration in the Spring Boot reference for more information.

This simple example demonstrates a typical configuration, but some providers will require additional configuration. For more information about configuring the ClientRegistration, see Spring Boot Property Mappings in the Spring Security reference.

Configure Authentication

Finally, to configure Spring Authorization Server to use a social login provider for authentication, you can use oauth2Login() instead of formLogin(). You can also automatically redirect an unauthenticated user to the provider by configuring exceptionHandling() with an AuthenticationEntryPoint.

Continuing our earlier example, configure Spring Security using a @Configuration as in the following example:

Configure OAuth 2.0 Login
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean (1)
	@Order(1)
	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
			throws Exception {
		OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
				OAuth2AuthorizationServerConfigurer.authorizationServer();

		http
			.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
			.with(authorizationServerConfigurer, (authorizationServer) ->
				authorizationServer
					.oidc(Customizer.withDefaults())	// Enable OpenID Connect 1.0
			)
			.authorizeHttpRequests((authorize) ->
				authorize
					.anyRequest().authenticated()
			)
			// Redirect to the OAuth 2.0 Login endpoint when not authenticated
			// from the authorization endpoint
			.exceptionHandling((exceptions) -> exceptions
				.defaultAuthenticationEntryPointFor( (2)
					new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/my-client"),
					new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
				)
			);

		return http.build();
	}

	@Bean (3)
	@Order(2)
	public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
			throws Exception {
		http
			.authorizeHttpRequests((authorize) -> authorize
				.anyRequest().authenticated()
			)
			// OAuth2 Login handles the redirect to the OAuth 2.0 Login endpoint
			// from the authorization server filter chain
			.oauth2Login(Customizer.withDefaults()); (4)

		return http.build();
	}

}
1 A Spring Security filter chain for the Protocol Endpoints.
2 Configure an AuthenticationEntryPoint for redirecting to the OAuth 2.0 Login endpoint.
3 A Spring Security filter chain for authentication.
4 Configure OAuth 2.0 Login for authentication.

If you configured a UserDetailsService when getting started, you can remove it now.

Advanced Use Cases

The demo authorization server sample demonstrates advanced configuration options for federating identity providers. Select from the following use cases to see an example of each:

Capture Users in a Database

The following example AuthenticationSuccessHandler uses a custom component to capture users in a local database when they first log in:

FederatedIdentityAuthenticationSuccessHandler
import java.io.IOException;
import java.util.function.Consumer;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
public final class FederatedIdentityAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

	private final AuthenticationSuccessHandler delegate = new SavedRequestAwareAuthenticationSuccessHandler();

	private Consumer<OAuth2User> oauth2UserHandler = (user) -> {};

	private Consumer<OidcUser> oidcUserHandler = (user) -> this.oauth2UserHandler.accept(user);

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
		if (authentication instanceof OAuth2AuthenticationToken) {
			if (authentication.getPrincipal() instanceof OidcUser) {
				this.oidcUserHandler.accept((OidcUser) authentication.getPrincipal());
			} else if (authentication.getPrincipal() instanceof OAuth2User) {
				this.oauth2UserHandler.accept((OAuth2User) authentication.getPrincipal());
			}
		}

		this.delegate.onAuthenticationSuccess(request, response, authentication);
	}

	public void setOAuth2UserHandler(Consumer<OAuth2User> oauth2UserHandler) {
		this.oauth2UserHandler = oauth2UserHandler;
	}

	public void setOidcUserHandler(Consumer<OidcUser> oidcUserHandler) {
		this.oidcUserHandler = oidcUserHandler;
	}

}

Using the AuthenticationSuccessHandler above, you can plug in your own Consumer<OAuth2User> that can capture users in a database or other data store for concepts like Federated Account Linking or JIT Account Provisioning. Here is an example that simply stores users in-memory:

UserRepositoryOAuth2UserHandler
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import org.springframework.security.oauth2.core.user.OAuth2User;
public final class UserRepositoryOAuth2UserHandler implements Consumer<OAuth2User> {

	private final UserRepository userRepository = new UserRepository();

	@Override
	public void accept(OAuth2User user) {
		// Capture user in a local data store on first authentication
		if (this.userRepository.findByName(user.getName()) == null) {
			System.out.println("Saving first-time user: name=" + user.getName() + ", claims=" + user.getAttributes() + ", authorities=" + user.getAuthorities());
			this.userRepository.save(user);
		}
	}

	static class UserRepository {

		private final Map<String, OAuth2User> userCache = new ConcurrentHashMap<>();

		public OAuth2User findByName(String name) {
			return this.userCache.get(name);
		}

		public void save(OAuth2User oauth2User) {
			this.userCache.put(oauth2User.getName(), oauth2User);
		}

	}

}

Map Claims to an ID Token

The following example OAuth2TokenCustomizer maps a user’s claims from an authentication provider to the id_token produced by Spring Authorization Server:

FederatedIdentityIdTokenCustomizer
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
public final class FederatedIdentityIdTokenCustomizer implements OAuth2TokenCustomizer<JwtEncodingContext> {

	private static final Set<String> ID_TOKEN_CLAIMS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
			IdTokenClaimNames.ISS,
			IdTokenClaimNames.SUB,
			IdTokenClaimNames.AUD,
			IdTokenClaimNames.EXP,
			IdTokenClaimNames.IAT,
			IdTokenClaimNames.AUTH_TIME,
			IdTokenClaimNames.NONCE,
			IdTokenClaimNames.ACR,
			IdTokenClaimNames.AMR,
			IdTokenClaimNames.AZP,
			IdTokenClaimNames.AT_HASH,
			IdTokenClaimNames.C_HASH
	)));

	@Override
	public void customize(JwtEncodingContext context) {
		if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
			Map<String, Object> thirdPartyClaims = extractClaims(context.getPrincipal());
			context.getClaims().claims(existingClaims -> {
				// Remove conflicting claims set by this authorization server
				existingClaims.keySet().forEach(thirdPartyClaims::remove);

				// Remove standard id_token claims that could cause problems with clients
				ID_TOKEN_CLAIMS.forEach(thirdPartyClaims::remove);

				// Add all other claims directly to id_token
				existingClaims.putAll(thirdPartyClaims);
			});
		}
	}

	private Map<String, Object> extractClaims(Authentication principal) {
		Map<String, Object> claims;
		if (principal.getPrincipal() instanceof OidcUser) {
			OidcUser oidcUser = (OidcUser) principal.getPrincipal();
			OidcIdToken idToken = oidcUser.getIdToken();
			claims = idToken.getClaims();
		} else if (principal.getPrincipal() instanceof OAuth2User) {
			OAuth2User oauth2User = (OAuth2User) principal.getPrincipal();
			claims = oauth2User.getAttributes();
		} else {
			claims = Collections.emptyMap();
		}

		return new HashMap<>(claims);
	}

}

You can configure Spring Authorization Server to use this customizer by publishing it as a @Bean as in the following example:

Configure FederatedIdentityIdTokenCustomizer
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> idTokenCustomizer() {
    return new FederatedIdentityIdTokenCustomizer();
}