Spring Security 5 OAuth 2.0 Login and Sign Up in Stateless REST Web Services
Providing users a easy and secure way to log in can do wonders for the security of your web service. Read on to see how to implement SSO.
Join the DZone community and get the full member experience.
Join For FreeAdding social login to a Spring application is now easy with Spring Security 5. It’s well explained in the official documentation, as well as in some blogs like this one, which cover the basic requirements. In real-world applications, though, you’ll have some additional requirements, like registering new users or having a stateless backend. So, in this post, we’ll broadly discuss how to address the following requirements:
Users should be able to authenticate using their social accounts.
New users should get registered automatically.
Our backend should remain stateless (we’ll assume our application to be a stateless REST API, with separate front-ends).
For code examples, we’ll refer to Spring Lemon. If you haven’t heard of Spring Lemon, you should give it a look. It’s a library encapsulating the sophisticated non-functional code and configuration that’s needed when developing real-world RESTful web services using the Spring framework and Spring Boot.
Before proceeding, if you wish to look at how exactly our social sign-on is going to work, here is a video that includes it.
So, let’s begin with the first step - a user pressing a social sign-on button. We’ll assume that your backend is running at https://api.example.com and the front-end is running at https://www.example.com.
Step 1 - User Presses a Social Sign On Button
In your front-end application, you’ll have multiple buttons -- one for each provider -- like “Sign on using Google” and “Sign on using Facebook.” When a user clicks on a button, you’ll open a modal browser window with the corresponding backend-url. For example, when the user clicks on “Sign on using Google,” you’ll open https://api.example.com/oauth2/authorization/google in the browser.
Step 2 - Spring Security Intercepts the Above Request
So, what happens when the browser opens https://api.example.com/oauth2/authorization/google?
Spring Security has a OAuth2AuthorizationRequestRedirectFilter, which intercepts requests of the pattern /oauth2/authorization/*, and does the following:
Creates an OAuth2AuthorizationRequest object.
Stores that using a configured AuthorizationRequestRepository.
Redirects the browser to the provider’s authorization-uri page (e.g. https://accounts.google.com/o/oauth2/v2/auth), with a few parameters. Among the parameters would be a callback URL (e.g. https://api.example.com/login/oauth2/code/google).
For storing the OAuth2AuthorizationRequest in Step 2 above, Spring provides a session-based implementation of AuthorizationRequestRepository. That’ll not work if your backend is stateless, because there’ll be no session. So, you’ll need to code an alternative implementation. For a cookies based implementation, refer to Spring Lemon’s HttpCookieOAuth2AuthorizationRequestRepository.
Step 3 - User Logs in to the Provider and Approves Our Application
After Spring redirects the user to the authorization-uri page of the provider (e.g. Google), the provider takes over and:
Asks the user to login if they wouldn’t have.
Seeks permissions from the user to allow our application to access their data (only for first time use).
Redirects the browser to the callback URL (e.g. https://api.example.com/login/oauth2/code/google), along with some parameters, e.g. accessToken and state.
Step 4 - Spring Security Intercepts the Above Request
In the backend, Spring’s OAuth2LoginAuthenticationFilter intercepts the above request (e.g. https://api.example.com/login/oauth2/code/google), and:
Receives the parameters, e.g. accessToken and state.
Retrieves the OAuth2AuthorizationRequest saved earlier, using the configured AuthorizationRequestRepository discussed earlier, and does things like matching the state.
Calls a configured OAuth2UserService implementation to retrieve the user information (email etc.) by calling the user-info-uri of the provider (e.g. https://www.googleapis.com/oauth2/v3/userinfo).
Authenticates the user.
Clears the stored OAuth2AuthorizationRequest.
Redirects the user to a configured success-url. You may be thinking that you could configure the success-url to be the homepage of your front-end, but that won’t work with a stateless backend. More on this later.
So, how do you register a new user?
In number 3 above, we saw that an OAuth2UserService implementation is used for retrieving the user information (email, etc.) from the provider. Spring comes with a couple of implementations, DefaultOAuth2UserService and OidcUserService, which are used for OAuth 2.0 (Facebook) and OpenID Connect (Google) providers respectively.
These implementations don’t save the user to our database. But, you can easily extend these and provide your custom implementations, which could check if the user record, having the given email, is present in your database, and if not, save it! See Spring Lemon’s LemonOAuth2UserService and LemonOidcUserService for examples. If you want to preserve the access token (either as is or after exchanging it for a long-term token), these services are the right place to do that as well.
Let’s now get back to number 6, where we saw how the user would be ultimately redirected back to the front-end. The front-end then could call your backend to get the currently logged-in user, if your backend is stateful. But that won’t work when the backend is stateless.
A way to fix this would be to embed a nonce or short-lived JWT in the success-url in number 6 above, which the front-end could exchange for a longer-term authorization token. For example, refer to Spring Lemon’s OAuth2AuthenticationSuccessHandler. That builds a success-url like https://www.example.com/social-login-success?token=a-short-lived-jwe-auth-token.
Next, how to clear any cookies created by our custom components?
We can configure authentication success and failure handlers to do that. See Spring Lemon’s OAuth2AuthenticationSuccessHandler and OAuth2AuthenticationFailureHandler for examples.
Lastly, to tell Spring Security about all the customizations you made, a WebSecurityConfigurerAdapter can be configured:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login()
.authorizationEndpoint()
.authorizationRequestRepository(your-custom-repo).and()
.successHandler(your-custom-success-handler)
.failureHandler(your-custom-failure-handler)
.userInfoEndpoint()
.oidcUserService(your-custom-oidc-user-service)
.userService(your-custom-oauth2-user-service);
}
}
For an exact example, see Spring Lemon’s LemonSecurityConfig (and also LemonAutoConfiguration, where many of the required beans are created).
Step 5 - Front-End Uses the Short-Lived Token for Fetching User Information and a Long-Lived Token
After getting a nonce or short-lived token in the above step, the front-end then:
Calls your backend to fetch the user information and a long-lived token.
Updates the user information in the front-end models.
Stores the long-lived token in the local storage.
Closes the modal browser window.
For an example front-end, see this sample AngularJS application. Particularly, look at the $scope.facebookLogin, $scope.googleLogin, and $window.socialLoginSuccess functions in login.js.
Please post your questions, comments, and suggestions!
NOTE: This article is derived from the live book Spring Framework Recipes For Real World Application Development.
Published at DZone with permission of Sanjay Patel. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments