Spring Boot For my side project Powerline, I’m building a REST API and I needed some authentication. I thought: users of this app will probably not want to be bothered with creating an account, so why not allow them to sign in with their Facebook account.

To accomplish this, I set up a OAuth 2.0 authorization server that will allow the user to log in and provide us with a token to make requests to the REST API.

Spring Boot in combination with Spring Security allows us to quickly set this up. You can either use Spring Security OAuth to do a SSO with Facebook, or you can use Spring Social to do it. I chose Spring Social because I also want to store user profiles and need to relate a Facebook account to a user profile. Additionally, users can then have multiple ways of logging in to the same profile.

Facebook login with Spring Social

First we set up Spring Social sign in with Facebook:

@SpringBootApplication
@EnableSocial
class SpringBootExampleApplication

In this example I’m just using the in-memory store of Spring Social. In the actual implementation I’m storing the user connections in MongoDB.

To make it work we need to register our application with Facebook and configure the details in the application.yml:

spring:
  social:
    facebook:
      app-id: changeit
      app-secret: changeit

Finally we need to add a SignInAdapter for Spring Social that sets the authentication in the session:

@Component
class MySignInAdapter : SignInAdapter {

    override fun signIn(userId: String, connection: Connection<*>, request: NativeWebRequest): String? {
        val authentication = PreAuthenticatedAuthenticationToken(userId, connection, setOf(SimpleGrantedAuthority("ROLE_USER")))

        SecurityContextHolder.getContext().authentication = authentication as Authentication?

        return null
    }

}

Then we can try it out by starting the application and making a POST /signin/facebook request. It will redirect us to Facebook and then, after logging in, back to our application.

We know have a session that is authenticated.

Authorization Server

To configure the OAuth authorization server, we add:

@Configuration
    @EnableAuthorizationServer
    class OAuth2ServerConfiguration : WebSecurityConfigurerAdapter() {

        override fun configure(http: HttpSecurity) {
            http
                    .csrf().disable()
                    .antMatcher("/").authorizeRequests().anyRequest().permitAll()
                    .and()
                    .antMatcher("/signin/**").authorizeRequests().anyRequest().permitAll()
        }
    }

And configure the client details in the application.yml:

security:
  oauth2:
    client:
      client-id: acme
      client-secret: acmesecret
      scope: basic
      auto-approve-scopes: basic
      authorized-grant-types: authorization_code
      access-token-validity-seconds: 600
      refresh-token-validity-seconds: -1

Here we limit the grant types to “authorization code” which is preferred over “implicit flow”.

We can then make sure we’re logged (see above). After we have an authenticated session, the app can make POST /oauth/authorize?client_id=acme&response_type=code&redirect=http://localhost:8080 request which will provide an authorization code. With that authorization code we can then retrieve the token (bearer or JWT) from the token endpoint /oauth/token. This is now a working OAuth 2.0 authorization server.

Protecting resources

Because the authorization server is also the REST API server, we need to make sure the API endpoints are protected:

@Configuration
@EnableResourceServer
@EnableWebSecurity
class OAuth2ResourceConfiguration : ResourceServerConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
        // Because we are both resource and authorization server,
        // we must always define a specific matcher on which authorizeRequests() is called
        // else the resource server filter will also protect any authorization server endpoints
        http.antMatcher("/api/**")
                .authorizeRequests()
                .anyRequest().authenticated()
    }

}

Any REST controllers are now protected.

Adding password login

To also allow users to login with a username and password instead of Facebook, we can add password grant type to the OAuth 2.0 authorization server. For this example we will use a in-memory user store:

class OAuth2ServerConfiguration : WebSecurityConfigurerAdapter() {

    // ...

    override fun configure(auth: AuthenticationManagerBuilder) {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("secret")
                .roles("ADMIN", "USER")
                .withUser("johndoe")
                .password("secret")
                .roles("USER")
    }
}

Then add the password grant type to the application.yml:

security:
  oauth2:
    client:
      authorized-grant-types: authorization_code, password

Now the app can present a login screen and do a POST /oauth/token?user=admin&password=secret with basic authentication for acme:acmesecret and it will get back a token. Of course, make sure to run this on HTTPS.

You can find the full source code on GitHub.