HelloKoding

Practical coding guides

Login with OAuth2 and OpenId Connect in Spring Boot with Spring Security

This tutorial walks you through the steps of creating Spring Security OAuth2 and OpenId Connect web clients in Spring Boot with Google, Github, Facebook and Okta

The tech stack

  • OAuth represents Open Authorization. It is an authorization framework enabling a third-party application to obtain limited access to an HTTP service on behalf of a resource owner
  • OpenId Connect is built on top of OAuth2 for authentication only. While OAuth2 has no definition on the format of the token, OpenId Connect uses JWT (JSON Web Token)

What you’ll build

An index page with the options to allow user login to OAuth2 and OpenId Connect providers

OAuth2 and OpenId Connect Login

Redirect user to Login form of respective provider when the Login link is clicked

OAuth2 and OpenId Connect Login

Redirect user back to the index page when user log in successfully, show user full name and the log out function on the same page

OAuth2 and OpenId Connect Login

When user clicks log out, clear session data and show again the login options

What you’ll need

  • Your favorite IDE or Editor
  • JDK 8+ or OpenJDK 8+
  • Maven 3+

Init project structure

Besides using IDE, you can create and init a new Spring Boot project with Spring CLI or Spring Initializr. Learn more about using these tools here

The final project structure as below

├── src
│   └── main
│       ├── java
│       │   └── com
│       │       └── hellokoding
│       │           └── springboot
│       │               └── oauth2
│       │                   └── OAuth2Application.java
│       └── resources
│           ├── static
│           │   └── index.html
│           └── application.properties
└── pom.xml

Project dependencies

Add spring-boot-starter-web and spring-boot-starter-oauth2-client into your project as a dependency on pom.xml or build.gradle file. The library versions can be omitted as it is resolved by the parent pom provided by Spring Boot

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

The spring-boot-starter-oauth2-client dependency provides auto-configuration for Spring Security OAuth2 Client and Spring Security’s support for the JOSE (Javascript Object Signing and Encryption) framework to securely transfer claims between parties including JWT (JSON Web Token), JWS (JSON Web Signature), JWE (JSON Web Signature) and JWK (JSON Web Key)

You can find the full pom.xml file as below

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hellokoding.springboot</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Required OAuth2 Properties

The following are required properties you need to configure an OAuth2 Client

  • Client id, client secret and scopes of user data access

    Scopes are predefined strings by providers and may be different across them

    You need a developer account to create an OAuth2 application on the provider site to obtain client id, client secret, and scopes

  • Redirect URI for forwarding authorization code and state from server to client

    The OAuth client is required to provide the Redirect URI and declare it on the OAuth application. Spring Security provides it for you by default at path {baseUrl}/{action}/oauth2/code/{registrationId}, for example, http://localhost:8081/login/oauth2/code/github or http://localhost:8081/login/oauth2/code/google

  • Provider authorization URI, token URI, and user info URI

    You can find provider URIs on its documentation. If the provider supports well-known metadata, Spring Security can explore them via an issuer URI

Configure OAuth2 Properties in Spring Boot

Spring Boot provides auto-configure most of OAuth2 properties for common providers

  • With Github, Google, Facebook providers, you are only required to fill in the client id and client secret
  • With Okta, you need to provide issuer URI as an additional property
  • With other providers, LinkedIn for example, you have to provide all the required properties

application.yml

server:
  port: 8081

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: ${GITHUB_CLIENT_ID}
            clientSecret: ${GITHUB_CLIENT_SECRET}

          google:
            clientId: ${GOOGLE_CLIENT_ID}
            clientSecret: ${GOOGLE_CLIENT_SECRET}

          facebook:
            clientId: ${FACEBOOK_CLIENT_ID}
            clientSecret: ${FACEBOOK_CLIENT_SECRET}

          okta:
            clientId: ${OKTA_CLIENT_ID}
            clientSecret: ${OKTA_CLIENT_SECRET}
            clientName: Okta

          linkedin:
            clientId: ${LINKEDIN_CLIENT_ID}
            clientSecret: ${LINKEDIN_CLIENT_SECRET}
            clientName: LinkedIn
            authorizationGrantType: authorization_code
            redirectUri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope: r_liteprofile, r_emailaddress

        provider:
          okta:
            issuerUri: https://${YOUR_OKTA_SUBDOMAIN:example}.okta.com/oauth2/default

          linkedin:
            authorizationUri: https://www.linkedin.com/oauth/v2/authorization
            tokenUri: https://www.linkedin.com/oauth/v2/accessToken
            userInfoUri: https://api.linkedin.com/v2/me
            userNameAttribute: id

Configure Spring Security

Create a class and extends WebSecurityConfigurerAdapter to configure security for your application

SecurityConfig.java

package com.hellokoding.springboot.oauth2;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(a -> a
                .antMatchers("/").permitAll()
                .anyRequest().authenticated())
            .csrf(c -> c.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
            .logout(l -> l.logoutSuccessUrl("/").permitAll())
            .oauth2Login(o -> o
                .successHandler((request, response, authentication) -> {
                    response.sendRedirect("/");
                }));
    }
}
  • The authorizeRequests(…) method permits home page request but requires authentication for any other URIs
  • The csrf(…) method generates a CSRF token cookie with an HttpOnly flag as false to allow access from JavaScript. You can find more details in the web client implementation below
  • The logout(l -> l.logoutSuccessUrl(”/“).permitAll()) provides a log out GET end point at ‘/logout’ URI and redirect to home page after success
  • The oauth2Login(…) enables the OAuth2 flow and redirects user to home page after the flow succeed

Create OAuth2 user controller

Create a GET REST API endpoint for retrieving authenticated user info

OAuth2Controller.java

package com.hellokoding.springboot.oauth2;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.Collections;
import java.util.Map;

@RestController
public class OAuth2Controller {
    @GetMapping("/user")
    public Map<String, Object> user(@AuthenticationPrincipal OAuth2User principal) {
        return Collections.singletonMap("name", principal.getAttribute("name"));
    }
}

Create OAuth2 web clients

Create index.html file inside src/main/resources/static

index.html

<!doctype html>
<html lang="en">
<head>
    <title>Login with OAuth2 / OpenId Connect</title>
    <style>
        body {
            margin: 50px 50px;
        }

        a {
            display: block;
            line-height: 40px;
        }
    </style>
</head>
<body>
    <h1>Login with OAuth2 / OpenId Connect</h1>
    <div class="container">
        <div id="login" style="display:block">
            <a href="/oauth2/authorization/github">Login with Github</a>
            <a href="/oauth2/authorization/google">Login with Google</a>
            <a href="/oauth2/authorization/facebook">Login with Facebook</a>
            <a href="/oauth2/authorization/okta">Login with Okta</a>
            <a href="/oauth2/authorization/linkedin">Login with LinkedIn</a>
        </div>
        <div id="welcome" style="display:none">
            Welcome <span id="name"></span> | <button onClick="logout()">Logout</button>
        </div>
    </div>
    <script>
        fetch('/user')
            .then(response => {
                if (response.ok) {
                    return response.json();
                } else {
                    throw new Error('Something went wrong');
                }
            })
            .then(data => {
                document.getElementById('name').innerText = data.name;
                document.getElementById('login').style.display = 'none';
                document.getElementById('welcome').style.display = 'block';
            })
            .catch((error) => {
                console.error('Error: ', error);
            });

        function logout() {
            fetch('/logout', {
                    method: 'POST',
                    headers: {
                        'X-XSRF-TOKEN': getCookie('XSRF-TOKEN')
                    }
                })
                .then((response) => {
                    if (response.ok) {
                        document.getElementById('login').style.display = 'block';
                        document.getElementById('welcome').style.display = 'none';
                    } else {
                        throw new Error('Something went wrong');
                    }
                })
                .catch((error) => {
                    console.error('Error: ', error);
                });
        }

        function getCookie(name) {
            var v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
            return v ? v[2] : null;
        }
    </script>
</body>
</html>

Spring Security provides the login endpoint to OAuth2 service at /oauth2/authorization/{registrationId} URI with the {registrationId} defined in the above application.yml

  • In index.html, we defined login to Github, Google, Facebook, Okta, and LinkedIn options with a link point to /oauth2/authorization/github, /oauth2/authorization/google, /oauth2/authorization/facebook, /oauth2/authorization/okta, and /oauth2/authorization/linkedin URIs respectively
  • For CSRF protection, Spring Security compares the token value of XSRF-TOKEN cookie and X-XSRF-TOKEN header. If they are matched then it’s safe to go

Create Spring Boot application entry point

Create OAuth2Application.java as a Spring Boot application entry point

OAuth2Application.java

package com.hellokoding.springboot.oauth2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

Run and Test

You can run the application by typing the following command on the terminal console at the project root directory

$ mvn clean spring-boot:run

Access to http://localhost:8081 on your web browser to explore the app

Troubleshooting

Spring Security OAuth2 can help you to ease the integration process with providers that compatible with the OAuth2 specification. However, when they don’t, as of Spring Security 5.2, it’s not easy to provide the customization like other libraries, ScribeJava for example

Source code

https://github.com/hellokoding/hellokoding-courses/tree/master/springboot-examples/spring-security-oauth2

Follow HelloKoding