Spring Boot and Oauth2 with Thymeleaf

Spring has a good tutorial explaining how to authenticate with your application using one or more external authentication providers, like GitHub or Google. This tutorial uses a single page application with a Rest endpoint. For a personal project I didn’t want a single page application, I wanted to use Thymeleaf. During implementation I discovered a few things that I’d like to share. This post continues where the tutorial stopped, so you might want to read the tutorial first.

Configure custom OAuth2UserService

When the user has authenticated using an external service, you probably want to do something with that information. Most commonly you’d want to find the user in your own database. You need to have a hook where you can get access to the authenticated user details. To do this, you can create your own OAuth2UserService bean which will be executed when the user has been authenticated. The bean itself can be quite basic, the following example only returns the authenticated user:

 	@Bean
	public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
		DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
		return request -> {
			OAuth2User user = delegate.loadUser(request);

            // custom code

			return user;
		};
	}

Now we need to tell our application when to call this bean. In the configure(HttpSecurity http) method, we’re going to add the following fragment:

			.oauth2Login(o -> o.failureHandler((request, response, exception) -> {
						request.getSession().setAttribute("error.message", exception.getMessage());
						handler.onAuthenticationFailure(request, response, exception);
					})
				.userInfoEndpoint()
				.userService(oauth2UserService())
			);

Now we’ve specifically told Spring Security to use our own OAuth2UserService.

Authenticating with Google

Using our own custom OAuth2UserService, I discovered that authenticating with Google didn’t work. Or rather, the custom OAuth2UserService wasn’t executed while it was executed when using GitHub or Facebook. It turned out that when you don’t specify which scope you’re interested in, Google returned all scopes. Included in the list was “openid”, which is specifically filtered out by Spring Security. So, if you want to use your own OAuth2UserService with Google, you need to configure it with the scopes you need. Like this:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            clientId: google-client-ID
            clientSecret: google-client-secret
            scope:
              - email
              - profile

@CurrentSecurityContext

To obtain the authenticated user principal, the tutorial uses the annotation @AuthenticationPrincipal, like this:

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

Usually you’re only interested in the user. However, if you need more information, you can use @SecurityContext. This article provides more information.
Here are two examples of how to use @CurrentSecurityContext with Thymeleaf:

    @RequestMapping(value = {"/","/index"})
    public String index(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
        if (authentication.isAuthenticated()) {
            return "redirect:/welcome";
        }
        return "redirect:/login";
    }
    @RequestMapping(value = "/welcome")
    public String welcome(@CurrentSecurityContext(expression = "authentication.principal") OAuth2User user, Model model) {
            model.addAttribute("name", user.getAttribute("name"));
            return "welcome";
    }

Custom Access Denied Page

The Spring Boot tutorial throws an unauthorized exception when the user tries to access a resource that he’s not allowed to. I wanted the website to redirect to the login page, assuming the user wasn’t authenticated. Or, if he was, then he should be redirected to the welcome page. We can configure this in the configure(HttpSecurity http) method:

			.exceptionHandling(e -> e
					.accessDeniedPage("/")
			)

This redirection ends up in the index() method of the previous section. Let’s look at that method again:

    @RequestMapping(value = {"/","/index"})
    public String index(@CurrentSecurityContext(expression = "authentication") Authentication authentication) {
        if (authentication.isAuthenticated()) {
            return "redirect:/welcome";
        }
        return "redirect:/login";
    }

When the user is authenticated, the user is redirected to the welcome page. Otherwise, the user is redirected to the login page.

Conclusion

While the Spring tutorial is quite good, there’s a lot more to OAuth2 authentication than it covers. This article covers some subjects that were beyond the scope of Spring’s tutorial. We’ve seen how to add your own processing of the authenticated user. Next we discussed some quirks when authenticating with Google. Then we’ve seen an alternative and more flexible way to get access to the user details. And last we redirected the access denied page to either the login page or the welcome page, using Thymeleaf.