In this post we will see how we can implement OAuth2 authentication in a Java Spring Boot application.

We will create a working website that can allow a user to sign in using Github authentication.

banner

If you just want to see the code, you can view the full example on Github

How OAuth2 Works

Let’s take a brief look at the OAuth protocol before we jump into implementation.

If you’ve ever seen a dialog like this, then you’ve probably used OAuth before:

gitlab using github oauth

Here, we are trying to login to Gitlab using Github to authenticate.

There are three parties in any OAuth mechanism:

  1. The client - The person, or user who is trying to log in
  2. The consumer - The application that the client wants to log into (which is Gitlab in this example)
  3. The service provider - The external application that authenticates the users identity. (which is Github in this example). OAuth service providers normally have a portal in which you can register your consumer. On registration, you will receive a client ID (which we are using here as myclientid123), and a client secret (which we will use later on). For Github, the portal to register new applications can be found on https://github.com/settings/applications/new.

In this post, we’ll create an HTTP server (consumer) using the Spring Boot framework, that uses Github’s OAuth2 API (service provider) to authenticate the user (client).

Let’s look at an overview of how this would work in practice.

oauth flow diagram

Creating the Spring Boot Application

Now that we’ve seen an overview of the OAuth2 mechanism, let’s implement it in a new Spring Boot server.

First, we need to add our dependencies to our pom.xml file:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>3.0.0</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>3.0.0</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

You can see the full pom.xml file on Github

Creating the HTTP Server

Let’s start by initializing the HTTP server with Spring boot:

// Declare the main application class. We will define our HTTP routes here
@SpringBootApplication
@RestController
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

	// We will implement this later
	@RequestMapping("/welcome")
	public String simpleRequest(HttpServletResponse response) {
		return "" 
	}

}

Currently, we have a single /welcome route - ideally, we want to send a personalized message to the logged in user, like "welcome <username>".

Let’s see what we need to do to achieve this:

  1. The user needs to authenticate via Github.
  2. We have to redirect the user to the /welcome page after successful authentication.
  3. We should retrieve information about the user (like their username) from Github and display a welcome message.

oauth login flow

We can configure our application to perform all these steps using the Spring security library.

Setting the Security Configuration

Let’s create a new class - SecurityConfiguration.java, that will define the code configuration for our OAuth2 application:

/**
 * Defines the spring security configuration for our application via the `getSecurityFilterChain`
 * method
 */
@Configuration
@EnableWebSecurity
class SecurityConfiguration {

    /**
     * @param httpSecurity is injected by spring security
     */
    @Bean
    @Autowired
    SecurityFilterChain getSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
            // Disable CSRF (not required for this demo)
            .csrf().disable();

        // authorize all requests coming through, and ensure that they are
        // authenticated
        httpSecurity.authorizeHttpRequests().anyRequest().authenticated();

        // enable oauth2 login, and forward successful logins to the `/welcome` route
        // Here `true` ensures that the user is forwarded to `/welcome` irrespective of
        // the original route that they entered through our application
        httpSecurity.oauth2Login()
            .defaultSuccessUrl("/welcome", true);

        return httpSecurity.build();
    }


    // enabling oauth2 login will not work without defining a
    // `ClientRegistrationRepository` bean. FOr simplicity, we're defining it in the same class
    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {

        // Create a new client registration
        return new InMemoryClientRegistrationRepository(
            // spring security provides us pre-populated builders for popular
            // oauth2 providers, like Github, Google, or Facebook.
            // The `CommonOAuth2Provider` contains the definitions for these
            CommonOAuth2Provider.GITHUB.getBuilder("github")
                // In this case, most of the common configuration, like the token and user endpoints
                // are pre-populated. We only need to supply our client ID and secret
                .clientId("a6f4d49ed380c40aa0")
                .clientSecret("0a3a74b5d6b55aba18fb618693d371c86c07")
                // ^ these credentials are fake, so you'll have to use your own :)
                .build());
    }
}

Let’s summarize what we just did:

  1. The getSecurityFilterChain method defines the core Spring security configuration, and configures our application to enable OAuth2 login. If we opt for OAuth2 login, we need to define a ClientRegistrationRepository bean, otherwise we would get a runtime error.
  2. The clientRegistrationRepository method exposes a ClientRegistrationRepository instance as a bean, which is used by spring security to get information about the OAuth2 provider.

Using a Custom OAuth2 Provider

In the above example, we used the CommonOAuth2Provider class, which gave us a pre-populated client registration for Github OAuth2 login, among others.

If we want to use a provider other than the pre-populated options, we can define our own client registration as well.

For example, we can define the OAuth2 configuration for our Github client using our own ClientRegistration builder:

@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
    ClientRegistration.Builder builder = ClientRegistration.withRegistrationId("github");
    builder.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
    builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
    builder.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}");
    builder.scope("read:user");
    builder.authorizationUri("https://github.com/login/oauth/authorize");
    builder.tokenUri("https://github.com/login/oauth/access_token");
    builder.userInfoUri("https://api.github.com/user");
    builder.userNameAttributeName("id");
    builder.clientName("GitHub");
    builder.clientId("a6f4d49ed380c40aa0");
    builder.clientSecret("0a3a74b5d6b55aba18fb618693d371c86c07");
    // Create a new client registration
    return new InMemoryClientRegistrationRepository(
        builder.build());
}

The result is the same as using the CommonOAuth2Provider.GITHUB instance in this case, but it’s useful if your OAuth2 provider is not covered in the predefined settings.

Retrieving User Information

Once the user is logged in, their information is stored in the SecurityContext of the current execution thread, which we can get by calling the SecurityContextHolder.getContext() method.

Let’s define the /welcome route in our Application class to fetch and display the information of the authenticated user:

@SpringBootApplication
@RestController
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

	// All requests to '/welcome' will be handled by this method
	@RequestMapping("/welcome")
	public String simpleRequest(HttpServletResponse response) {
		// Get the username of the currently logged in user
		// this method is defined below
		Optional<String> username = getUsernameFromSecurityContext();
		if (username.isEmpty()) {
			// if user information cannot be obtained, return
			// a 403 status
			response.setStatus(HttpStatus.FORBIDDEN.value());
			return "error";
		}

    // show the welcome messages with the users Github username
		return "welcome " + username.get();
	}

	private Optional<String> getUsernameFromSecurityContext() {
		// Get the security context for this request thread
		// and get the principal object from the context
		SecurityContext context = SecurityContextHolder.getContext();
		Object principal = context.getAuthentication().getPrincipal();

		// If the user is authenticated, the principal should be an
		// instance of DefaultOAuth2User
		if (!(principal instanceof DefaultOAuth2User)) {
			// if not, return an empty value
			return Optional.empty();
		}

		// Get the username from the DefaultOAuth2User and return
		// the welcome message
		String username =
			((DefaultOAuth2User) principal).getAttributes().get("login").toString();
		return Optional.of(username);
	}

}

Running Our Application

We can run our application with Maven:

mvn -DMAIN_CLASS=com.sohamkamani.oauth2.Application clean compile package && java -jar ./target/spring-security-examples-0.0.1-SNAPSHOT.jar

Once our application has started, we can navigate to http://localhost:8080/ and go through the authentication flow:

oauth flow

You can view the complete example code, along with instructions on how to run the application on Github

Additional Security Measures

Although this post demonstrated the basics of OAuth2, there is a lot more that can be done to further secure your application.

Session Tokens

In this example, we passed the access token to the client so that it can make requests as the authorized user. To make your app more secure, the access token should not be passed directly to the user. Instead, create a session token that is sent to the user as a cookie.

The app will maintain a mapping of the session tokens to access tokens on the server side (most likely a database).

Instead of making requests to github, the user will make requests to the node server (with the session token), which will in turn use the provided session token to look up the access token and make the request to github on the server side.

session token is mapped to the access token in a database and used to return user info

I have written more about sessions and cookies here.

OAuth Query State

While sending the user to the authorization URL, there is a provision to provide a value for a query parameter called state. The value of this should be a random un-guessable string provided by the application.

When github calls the redirect url, it will attach this state variable to the request params. The new URL would now look like:

https://github.com/login/oauth/authorize?client_id=myclientid123&redirect_uri=http://localhost:8080/oauth/redirect&state=somerandomstring

The application can now compare this value with the value it originally generated. If they are not the same, that means the request came from some third party, and should be rejected.

the redirect URL now contains a state that the client can compare with localStorage

This helps protect our application against cross site request forgery attacks.