caribbean
caribbean

Reputation: 317

Spring exchange google access token for user info

I am trying to implement google authentication for my website, it is React front end and Spring Boot REST back end.

For my React app I've found a library that provides a Google Login button, in which I can specify 'client-id' of my app, registered in Google developer's console and the rest (redirection of the user to google authentication page, retrieval of access token/id token) is done by this library.

Now I want to send this access token to my back-end, use it to make a request to google, to verify that this front-end user is a real google user, who correctly authenticated and get this user's data by showing this access token to google (i only want email/name/sub, which is users unique Google id). Am I looking for some library to exchange this google access token for user data ?

I'm also confused by the fact that all the oauth tutorials say that when user authenticates on google page, my app would receive an Authorization Code but my front end receives, as I said, acces token and id token. Is it because of the library I'm using ? https://www.npmjs.com/package/react-google-login this is the library

This is the data that comes to my client app when user autheneticates on google page console.log(response) in browser

Thanks

Upvotes: 1

Views: 2408

Answers (2)

Yevhen Oliinyk
Yevhen Oliinyk

Reputation: 121

Maybe I am late, but let me leave it here:

  1. The react library you are using intentionally retrieves access token because it assumes you will use it for a frontend, so with this token, you can make any further request to your resource server. In this case, the resource server and auth server are the same. For example, you want to use Google as an OAuth provider and show Google`s contacts in your frontend app. So Google here is the auth server(react`s lib makes Google`s API call for an access token) and resource server(with an access token, your react app makes a request for Google contacts).
  2. If the backend enters the game, you need to get an authorization code - a temporary code that the client(not a frontend; it is a client in terms of OAuth spec) will exchange for an access token. React requests an auth code to the auth server(Google/Facebook/Okta) to send this code to the backend. The backend sends this code along with client_id, secret, grant_type... and other parameters defined in the OAuth spec to the same OAuth server. The Auth server compares all parameters and returns the token if all is correct. The backend receives the token so we can declare that we exchanged the token for the code. The next step is to send the token back to the frontend, store it on the frontend, and attach it to each further request to the backend. Of course, we need to handle refresh token as well.

This could be a possible controller method to exchange the code for a token.

@PostMapping("/check/code/google")
    public ResponseEntity<Striing> handleGoogleAuthCode(@RequestBody Map<String, String> codeMap) {
        String code = codeMap.get("code");

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        Map<String, String> params = new HashMap<>();
        params.put("code", code);
        params.put("client_id", clientId);
        params.put("client_secret", clientSecret);
        String redirectUri = "https://bpnckmnjnpoohfnodnhjpehocneckmmc.chromiumapp.org/";
        params.put("redirect_uri", redirectUri);
        params.put("grant_type", "authorization_code");

        HttpEntity<Map<String, String>> request = new HttpEntity<>(params, headers);

        ResponseEntity<Map> response = restTemplate.postForEntity("https://oauth2.googleapis.com/token", request, Map.class);
        Map<String, Object> responseBody = response.getBody();

        String accessToken = (String) responseBody.get("access_token");

       String email = profile.getEmailAddresses().get(0).getValue();
    String name = profile.getNames().get(0).getGivenName();
    try {
        // Create an Authentication Object
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER"); // "ROLE_" is a convention
        Authentication auth = new UsernamePasswordAuthenticationToken(email, null, Collections.singletonList(authority));

        // Set the Authentication Object in Security Context
        SecurityContextHolder.getContext().setAuthentication(auth);
    } catch (Exception e) {
        // Token is invalid
        SecurityContextHolder.clearContext();
    }
    JwtUtil bean = applicationContext.getBean(JwtUtil.class);
    String jwt = bean.generateSessionToken(email);
    return jwt;
    } 

Upvotes: 1

I had the same problem as you have. I solved it by using google's GoogleIdTokenVerifier. It was very simple to set up.

This is my code:

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

@RestController
@RequestMapping(value = "/api/authenticate")
public final class AuthenticationController {

    @GetMapping
    public String exchange(@Autowired NetHttpTransport transport, @Autowired GsonFactory factory, HttpServletRequest request) throws GeneralSecurityException, IOException, IllegalAccessException {
        // get id_token from Authorization Bearer
        String token = this.getTokenFromRequest(request);

        // Create verifier
        GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, factory)
                .setAudience(Collections.singletonList(<CLIENT_ID_HERE>))
                .build();

        // Verify it
        GoogleIdToken idToken = verifier.verify(token);
        if (idToken == null) {
            throw new IllegalAccessException("Invalid id_token");
        }
        // Access payload 
        System.out.println("Email: " + idToken.getPayload().getEmail());
    }

    public String getTokenFromRequest(HttpServletRequest request) throws IllegalAccessException {
        String token = request.getHeader("Authorization");
        String[] parts = token.split(" ");
        if (parts.length != 2 || !parts[0].contains("Bearer")) {
            throw new IllegalAccessException("Authorization Bearer format invalid. <Bearer {token}>");
        }
        return parts[1];
    }
}

Maven dependencies:

<dependency>
    <groupId>com.google.api-client</groupId>
    <artifactId>google-api-client</artifactId>
    <version>1.30.4</version>
</dependency>

<dependency>
    <groupId>com.google.http-client</groupId>
    <artifactId>google-http-client-gson</artifactId>
    <version>1.21.0</version>
</dependency>

Upvotes: 3

Related Questions