Google Yolo and Spring Boot 2.0 Authentication

Back in 2016, Google announced the “Open Yolo” project: You Only Login Once. It originally seemed to be an Android library but during Google’s last Dev Summit in October 2017, Google released “One-tap Sign-ups On Websites and API Integrations” which brings Google Yolo to your website via JavaScript goodness. There’s a very easy guide that will have you up and running in no time here:

https://developers.google.com/identity/one-tap/web/overview

We used React to quickly setup a prototype of how the flow works, and you should hopefully end up with something that looks like this after following the guide above:

yolo_1

And, once you login, you’ll end up being issued a token, which would look something like the following:

eyJhbGciOiJSUz [ … ] 52BVk4nS9ReGWOe8A

The Google guide then proceeds to use the above token in a function they call

useGoogleIdTokenForAuth(credential.idToken);

In this article we’ll explore what happens (or at least one way of what happens) once you are given that token. We’re looking at it from the point of view of the back-end, and we assume that as a backend engineer you have no control or say over how this Google Yolo token is generated, you just receive this token in your REST API requests and need to figure out how to integrate the Yolo token so that only those clients who present a valid Google Yolo token are allowed access to the REST API. To make it a bit more interesting, let’s define the following rules:

  • Any user with a valid Google Yolo token will be allowed to query the REST API

– however –

  • Any some authorized users with a valid Google Yolo token will be allowed to access sensitive parts of the API

In this case, the backend is written using the just released Spring Boot 2.0 framework – specifically using the MVC framework. For the sake of brevity we assume the REST API endpoints, controllers, and so on are already up and running and now we need to integrate the Google Yolo authorization scheme outlined above.

Our scenario is slightly different from a full blown OAUTH2 implementation, so it’s best to outline the workflow:

Our conceptual Google YOLO + Spring Boot authentication workflow

We already outlined how the frontend would achieve steps 1 & 2, so we turn our attention to the remaining steps. In step 3, the frontend needs to request some resources from our API. Without a valid token, the REST API will return an HTTP 403 FORBIDDEN.

(Sidenote: I realize there are lots of other ways to do this – like using a database, or a service like Firebase… it all depends on your architectural needs)

In this case, the backend API requires its own token (which is shared with the rest of the infrastructure) – so we somehow need to translate the Google YOLO token to a token which our infrastructure will understand. So, in step 3 the frontend the frontend sends a POST request to the url /googleToken. The job of this endpoint is to validate the Google YOLO token (step 4 – which is outlined in Google’s guide we linked to above). and if valid, create and return it’s own valid token. We’ve decided to go with JSON Web Tokens [JWTs] in this project. There’s plenty of good resources out there outlining JWT awesomeness (in particular https://jwt.io/ by Auth0), but in a nutshell what drew us to JWT is:

  • We don’t ever need to know a user’s password – we just need to know a client ID
  • JWT is JSON based – so lots of support
  • JWTs can be cryptographically signed and verified as an anti-tamper measure
  • JWTs have in built expiry
  • JWT have built in “claims”, a.k.a. user permissions / roles

Since JWTs basically encode user identity, permissions, and expiry into one JSON blob, JWT makes it incredibly easy for your application to be stateless.

Step 4 is all about being able to issue API JWTs to users who present a valid Google YOLO token. Below is how /googleToken could be coded as a Spring @RestController, named SecurityController:


@RestController
public class SecurityController {
@Autowired
private TokenCreator tokenCreator;
@Value("${google.client.id}")
private String GOOGLE_CLIENT_ID;
@Autowired
SecurityController() { }
@RequestMapping(method = RequestMethod.POST, path="/googleToken", produces={"application/json"}, consumes={"application/json"})
ResponseEntity<?> googleToken( @RequestBody String idTokenString ) {
ApacheHttpTransport transport = new ApacheHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Collections.singletonList(GOOGLE_CLIENT_ID))
.build();
// (Receive idTokenString by HTTPS POST)
GoogleIdToken idToken;
try {
idToken = verifier.verify(idTokenString);
if (idToken != null) {
Payload payload = idToken.getPayload();
/*
* UserInfo is a HashMap which will store the User Information,
* later to be sent back to the frontend as JSON
*/
HashMap<String, String> UserInfo = new HashMap<String, String>();
String userId = payload.getSubject();
UserInfo.put("id", userId);
// Get profile information from payload
String email = payload.getEmail();
UserInfo.put("email", email);
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
UserInfo.put("emailVerified", String.valueOf(emailVerified));
String name = (String) payload.get("name");
UserInfo.put("name", name);
String family_name = (String) payload.get("family_name");
UserInfo.put("family_name", name);
String pictureUrl = (String) payload.get("picture");
UserInfo.put("pictureUrl", pictureUrl);
String[] claims;
/*
* This code is where we define the "authorization" part…
* Which users will we allow to access the sensitive parts of our API?
* Typically this will be coded into some database repository, but for
* illustrative purposes we define this in our code, right here
*
* In this case, only the user who has the Google YOLO token containing
* davevassallo@gmail.com as their address is given an extra Authority …. "ACTUATOR"
*
*/
if (email.equals("davevassallo@gmail.com")){
claims = new String[]{"API_ALLOWED", "ACTUATOR"};
} else {
claims = new String[]{"API_ALLOWED"};
}
/*
* The Authorities we granted to the users above are coded into the JWT as "claims"
*/
String token = tokenCreator.issueToken(claims, userId, null);
UserInfo.put("api_token", token);
/*
* We turn the HashMap into a JSON string and send it back to the user
*/
return new ResponseEntity<String>(new ObjectMapper().writeValueAsString(UserInfo), HttpStatus.OK);
} else {
System.out.println("Invalid ID token.");
return new ResponseEntity<Error>(HttpStatus.UNAUTHORIZED);
}
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new ResponseEntity<Error>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}

Most of the code is fairly standard Spring code – however pay special note to lines 70-74 which as the comments explain, are a trivial implementation of the authorization part of our code. If the user’s email (extracted from the secure Google YOLO token) matches an authorized user, we add an extra “claim” to JWT (called “ACTUATOR” in our case). As we’ll see later, claims are analogous to Spring Security Authorities.

In the above code snippet, we see reference to “TokenCreator”, which handles the creation of our API JWT token – step 5 in our workflow. Below is an example of the implementation of TokenCreator, which is a Spring Service responsible for building, signing and issuing the API JWTs. We use auth0 JWT library [com.auth0.jwt.JWT] to actually do the heavy lifting of signing and creating the JWT


@Service
public class TokenCreator {
@Value("${jwtSecret}")
private String jwtSecret;
private Algorithm algorithm = null;
public TokenCreator() { }
public String issueToken(String[] claims, String subject, Date expiryDate){
try {
this.algorithm = Algorithm.HMAC256(jwtSecret);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/*
* If no expiry date given, default to a 24hour expiry time
*/
if (expiryDate == null){
Calendar c = Calendar.getInstance();
c.setTime(new Date()); // Use today's date.
c.add(Calendar.HOUR_OF_DAY, 24); // Adds 24 hours
expiryDate = c.getTime();
}
Builder tokenBuilder = JWT.create()
.withIssuer("YOUR ORGANIZATION HERE")
.withSubject(subject)
.withExpiresAt(expiryDate);
/*
* Cycle through the claims and add them to our JWT
*/
for (String claim : claims){
tokenBuilder.withClaim(claim, true);
}
/*
* Sign and issue the JWT
*/
String issuedToken = tokenBuilder.sign(algorithm);
return issuedToken;
}
}

Note how:

  • Line 31: we can specify when a JWT will expire (and so not be valid anymore)
  • Line 36: we specify an “issuer” which we can then check later to validate the JWT
  • lines 44-46: we add all the claims the user will have into the token.

At this point we can issue JWT to our frontend, but now we need to use Spring Security so that we can authenticate and authorize those users with valid API JWTs. First, we configure Spring Security

Below is an example of a Spring Boot Security configuration to accomplish this.


@EnableWebSecurity
public class SecurityConfig {
@Value("${security.enabled:false}")
private Boolean security_enabled;
@Configuration
@Order(1)
public class BasicSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/googleToken").authorizeRequests().anyRequest().permitAll();
http.csrf().disable();
}
}
@Configuration
@Order(2)
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${jwtSecret}")
private String jwtSecret;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new JWTFilter(jwtSecret), BasicAuthenticationFilter.class);
http.authorizeRequests()
.antMatchers("/actuator/*").hasAuthority("ACTUATOR")
.antMatchers("/**").permitAll();
}
}
}

Note the following:

  • We use multiple Spring Security configurations, ordered using the @Order annotation.
  • Line 14: The first WebSecurityConfigurerAdapter allows all requests to /googleToken since this is the endpoint clients will use to get their tokens issued in the first place
  • Lines 35-36: The second WebSecurityConfigurerAdapter ensures that all requests to the sensitive part of our API (urls beginning with /actuator) have the ACTUATOR authority while the other URLs are permitted to all users which have a valid API JWT

In order to verify that a user has a valid API JWT, in the above snippet (line 32) we added a new Spring Security filter called “JWTFilter” which is in charge of extracting the JWT from the HTTP Authenticate header, validating the JWT signature, and translating JWT claims to Spring Security Authorities.

( TIP: this was a bit counter-intuitive to me, but even if you are not using Basic Authentication in your app, you have to include your new filter (JWTFilter in our case) after BasicAuthenticationFilter.class as shown in line 32 above.

Also, even though line 36 says “permitAll”, the requests still need to pass through the filter, which can return an error if the JWT token is invalid as we’ll see below)

The implementation of the Spring Security Filter which we called JWTFilter follows. Again we use the auth0 JWT library to do the heavy lifting of validating our token and extracting the claims:


public class JWTFilter implements Filter{
private String jwtSecret;
public JWTFilter(String jwtSecret){
this.jwtSecret = jwtSecret;
}
@Override
public void destroy() {
// Do nothing
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
String jwtSecret = this.jwtSecret;
System.out.println("Checking JWT…");
String token = null;
Boolean verified = false;
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response =(HttpServletResponse) res;
token = request.getHeader("authorization");
try {
Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("YOUR ORGANIZATION HERE")
.build();
DecodedJWT jwt = verifier.verify(token);
verified = jwt.getClaim("API_ALLOWED").asBoolean();
Map<String, Claim> roles = jwt.getClaims();
String subject = jwt.getSubject();
ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
for (String role: roles.keySet()){
authorities.add(new SimpleGrantedAuthority(role));
}
UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken( subject,
"",
authorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JWTVerificationException exception){
//Invalid signature/claims
System.out.println("Invalid JWT verification");
exception.printStackTrace();
}
if (verified) {
chain.doFilter(req, res);
} else {
res.reset();
System.out.println("Could not verify JWT…");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}

view raw

JWTFilter.java

hosted with ❤ by GitHub

Note the following:

  • Lines 34-37: we build a JWT verifier using the same issuer we used before, to verify and decode all incoming JWT
  • Line 38: we extract the “API_ALLOWED” claim and if false, we send an HTTP 403 FORBIDDEN (lines 70-72)
  • Line 41: we extract the “subject” – a.k.a. the user ID (which originally came from Google YOLO)
  • Lines 43-46: we extract the claims from the JWT and convert them into Spring SimpleGrantedAuthority
  • Lines 48-50: these Authorities are then passed into a UsernamePasswordAuthenticationToken which is subsequently used to update the Spring Security context via
    SecurityContextHolder.Context().setAuthentication()

    in line 52

  • Lines 67-68: only if the JWT is verified does the filter allow the request to complete

That pretty much handles our entire workflow, while allowing us to explore:

  • how to map one token (Google Yolo) to another (API JWT)
  • the relationship between JWT “claims” and Spring Security “Authorities”
  • Using Spring Security to validate and authorize users based on JWT
Advertisement
Privacy Settings