0

I have simple configured app

spring.security.oauth2.client.registration.google.clientId=xyz
spring.security.oauth2.client.registration.google.clientSecret=xyz
spring.security.oauth2.client.registration.google.scope=email,profile,openid,https://www.googleapis.com/auth/calendar,https://www.googleapis.com/auth/spreadsheets

I login with http://localhost:8080/oauth2/authorization/google and save to google calendar normally.

Getting access token looks like:

private String getAccessToken() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String accessToken = null;
    if (authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {
      OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
      String clientRegistrationId = oauthToken.getAuthorizedClientRegistrationId();
      OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(
        clientRegistrationId, oauthToken.getName());
      accessToken = client.getAccessToken().getTokenValue();
    }
    return accessToken;
  }

OAuth2AuthorizedClient contains refresh token.

But after 1 hour access token expires and I don't know how to refresh it. Without relogin.

https://developers.google.com/calendar/v3/errors#401_invalid_credentials says

Get a new access token using the long-lived refresh token.

And I found this one Spring Google OAuth2 With Refresh Token but don't works for me.

A have also relogin message after 401 but it's not user-friendly.

Could you help me? Thanks in advance.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
wojek
  • 21
  • 5
  • 1
    I cant help to much with Java but if you check this example https://developers.google.com/analytics/devguides/reporting/core/v4/quickstart/installed-java see how it uses FileDataStoreFactory the if you request off line access you will get a refresh token and the GoogleAuthorizationCodeFlow can use that to load a new access toen when it needs – Linda Lawton - DaImTo Apr 09 '21 at 12:05
  • Have you tried following the Calendar API [quickstart](https://developers.google.com/calendar/quickstart/java)? – Iamblichus Apr 12 '21 at 07:52
  • 1
    Iamblichus, yes I did, I solved my problem as below. To authenticate with OAuth2 Google I use Spring built-in mechanism. It's recivies Access Token and Refresh Token then I use them to connect to Google Calendar via Google lib. – wojek Apr 12 '21 at 10:46

1 Answers1

1

Thank you DaImTo for the hint.

I changed my credential usage from

    Credential credential = new GoogleCredential().setAccessToken(tokenUtils.getAccessToken());

to

 private Calendar getClient() throws GeneralSecurityException, IOException {
    HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();

    Credential credential = new GoogleCredential.Builder()
      .setJsonFactory(JSON_FACTORY)
      .setTransport(httpTransport)
      .setClientSecrets(clientId, clientSecret)
      .build()
      .setAccessToken(tokenUtils.getAccessToken())
      .setRefreshToken(tokenUtils.getRefreshToken());

    return new Calendar.Builder(httpTransport, JSON_FACTORY, credential)
      .setApplicationName("appname").build();
  }

and it WORKS!

But the app receives Refresh Token only if there is set prompt with value select_account or consent. With value none or without prompt Refresh Token is always null (access_type=offline).

It's also not user friendly, because application user has to choose google account every time...

Do you know some workaround?

EDIT:

public class TokenUtils {

  private final OAuth2AuthorizedClientService authorizedClientService;

  public TokenUtils(OAuth2AuthorizedClientService authorizedClientService) {
    this.authorizedClientService = authorizedClientService;
  }

  public String getAccessToken() {
    OAuth2AuthorizedClient client = getClient();
    return client.getAccessToken().getTokenValue();
  }

  public String getRefreshToken() {
    OAuth2AuthorizedClient client = getClient();
    return client.getRefreshToken().getTokenValue();
  }

  public OAuth2AuthorizedClient getClient() {
    OAuth2AuthenticationToken oauthToken = getOAuthToken();
    return authorizedClientService.loadAuthorizedClient(
      oauthToken.getAuthorizedClientRegistrationId(), oauthToken.getName());
  }

  private OAuth2AuthenticationToken getOAuthToken() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication.getClass().isAssignableFrom(OAuth2AuthenticationToken.class)) {
      return (OAuth2AuthenticationToken) authentication;
    }
    throw new SecurityException("Authentication is not OAuth2AuthenticationToken");
  }
}

EDIT 2:

http
      .oauth2Login()
      .successHandler(new RedirectToPrevAuthSuccessHandler())
      .userInfoEndpoint()
      .oidcUserService(addRolesOidcUserService);
      .oidcUserService(addRolesOidcUserService)
      .and()
      .authorizationEndpoint()
      .authorizationRequestResolver(new AddParamsRequestResolver(
        this.clientRegistrationRepository))
    ;

and

public class AddParamsRequestResolver implements OAuth2AuthorizationRequestResolver {

  private final OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver;

  public AddParamsRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
    this.defaultAuthorizationRequestResolver =
      new DefaultOAuth2AuthorizationRequestResolver(
        clientRegistrationRepository,
        OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
  }

  @Override
  public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
    OAuth2AuthorizationRequest authorizationRequest =
      this.defaultAuthorizationRequestResolver.resolve(request);

    if (authorizationRequest != null) {
      return addParams(authorizationRequest);
    }
    return null;
  }

  @Override
  public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
    OAuth2AuthorizationRequest authorizationRequest =
      this.defaultAuthorizationRequestResolver.resolve(
        request, clientRegistrationId);

    if (authorizationRequest != null) {
      return addParams(authorizationRequest);
    }
    return null;
  }

  private OAuth2AuthorizationRequest addParams(
    OAuth2AuthorizationRequest authorizationRequest) {

    Map<String, Object> additionalParameters =
      new LinkedHashMap<>(authorizationRequest.getAdditionalParameters());
    additionalParameters.put("access_type", "offline");
    additionalParameters.put("prompt", "consent");
//    additionalParameters.put("prompt", "select_account");
//    additionalParameters.put("prompt", "login");

    return OAuth2AuthorizationRequest.from(authorizationRequest)
      .additionalParameters(additionalParameters)
      .build();
  }
wojek
  • 21
  • 5