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 anaccess_token
and optionally anid_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:
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:
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:
-
I want to Capture Users in a Database
-
I want to Map Claims to an ID Token
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:
FederatedIdentityIdTokenCustomizer
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> idTokenCustomizer() {
return new FederatedIdentityIdTokenCustomizer();
}