Giter Site home page Giter Site logo

java-spring-google-oauth's Introduction

Adding an Error Page for Unauthenticated Users

In this section we modify the logout app we built earlier, switching to Github authentication, and also giving some feedback to users that cannot authenticate. At the same time we take the opportunity to extend the authentication logic to include a rule that only allows users if they belong to a specific Github organization. The "organization" is a Github domain-specific concept, but similar rules could be devised for other providers, e.g. with Google you might want to only authenticate users from a specific domain.

Switching to Github

The logout sample use Facebook as an OAuth2 provider. We can easily switch to Github by changing the local configuration:

application.yml
security:
  oauth2:
    client:
      clientId: bd1c0a783ccdd1c9b9e4
      clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
      accessTokenUri: https://github.com/login/oauth/access_token
      userAuthorizationUri: https://github.com/login/oauth/authorize
      clientAuthenticationScheme: form
    resource:
      userInfoUri: https://api.github.com/user

Detecting an Authentication Failure in the Client

On the client we need to be able to provide some feedback for a user that could not authenticate. To facilitate this we add a div with an informative message:

index.html
<div class="container text-danger error" style="display:none">
There was an error (bad credentials).
</div>

This text will only be shown when the "error" element is shown, so we need some code to do that:

index.html
$.ajax({
  url : "/user",
  success : function(data) {
    $(".unauthenticated").hide();
    $("#user").html(data.userAuthentication.details.name);
    $(".authenticated").show();
  },
  error : function(data) {
    $("#user").html('');
    $(".unauthenticated").show();
    $(".authenticated").hide();
    if (location.href.indexOf("error=true")>=0) {
      $(".error").show();
    }
  }
});

The authentication function checks the browser location when it loads and if it finds a URL with "error=true" in it, the flag is set.

Adding an Error Page

To support the flag setting in the client we need to be able to capture an authentication error and redirect to the home page with that flag set in query parameters. Hence we need an endpoint, in a regular @Controller like this:

SocialApplication.java
@RequestMapping("/unauthenticated")
public String unauthenticated() {
  return "redirect:/?error=true";
}

In the sample app we put this in the main application class, which is now a @Controller (not a @RestController) so it can handle the redirect. The last thing we need is a mapping from an unauthenticated response (HTTP 401, a.k.a. UNAUTHORIZED) to the "/unauthenticated" endpoint we just added:

ServletCustomizer.java
@Configuration
public class ServletCustomizer {
  @Bean
  public EmbeddedServletContainerCustomizer customizer() {
    return container -> {
      container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"));
    };
  }
}

(In the sample, this is added as a nested class inside the main application, just for conciseness.)

Generating a 401 in the Server

A 401 response will already be coming from Spring Security if the user cannot or does not want to login with Github, so the app is already working if you fail to authenticate (e.g. by rejecting the token grant).

To spice things up a bit we will extend the authentication rule to reject users that are not in the right organization. It is easy to use the Github API to find out more about the user, so we just need to plug that into the right part of the authentication process. Fortunately, for such a simple use case, Spring Boot has provided an easy extension point: if we declare a @Bean of type AuthoritiesExtractor it will be used to construct the authorities (typically "roles") of an authenticated user. We can use that hook to assert the the user is in the correct orignization, and throw an exception if not:

SocialApplication.java
@Bean
public AuthoritiesExtractor authoritiesExtractor(OAuth2RestOperations template) {
  return map -> {
    String url = (String) map.get("organizations_url");
    @SuppressWarnings("unchecked")
    List<Map<String, Object>> orgs = template.getForObject(url, List.class);
    if (orgs.stream()
        .anyMatch(org -> "spring-projects".equals(org.get("login")))) {
      return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
    }
    throw new BadCredentialsException("Not in Spring Projects origanization");
  };
}

Note that we have autowired a OAuth2RestOperations into this method, so we can use that to access the Github API on behalf of the authenticated user. We do that, and loop over the organizations, looking for one that matches "spring-projects" (this is the organization that is used to store Spring open source projects). You can substitute your own value there if you want to be able to authenticate successfully and you are not in the Spring Engineering team. If there is no match, we throw BadCredentialsException and this is picked up by Spring Security and turned in to a 401 response.

The OAuth2RestOperations has to be created as a bean as well (as of Spring Boot 1.4), but that’s trivial because its ingredients are all autowirable by virtue of having used @EnableOAuth2Sso:

@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
	return new OAuth2RestTemplate(resource, context);
}
Tip
Obviously the code above can be generalized to other authentication rules, some applicable to Github and some to other OAuth2 providers. All you need is the OAuth2RestOperations and some knowledge of the provider’s API.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.