Spring Social
  1. Spring Social
  2. SOCIAL-263

Refresh token not supported for OAuth2

    Details

    • Type: New Feature New Feature
    • Status: Open
    • Priority: Blocker Blocker
    • Resolution: Unresolved
    • Affects Version/s: 1.0.0.RELEASE
    • Fix Version/s: 2.0.0 Backlog
    • Component/s: API Binding
    • Labels:
      None

      Description

      OAuth2 has two tokens - an access token, used for every request, and a refresh token, used to obtain a new access token once the original expires.

      Google+ is an OAuth2 provider that, unlike Facebook, supports and requires a refresh token. However, I don't see such a facility in spring-social. There is OAuth2Template.refreshAccess(..), but it can't be invoked properly. In theory, a refresh token should be used when the server responds with 401 to an access token (meaning the token has expired or is otherwise invalid). However, neither AbstractOAuth2ApiBinding, nor AbstractOAuth2ServiceProvider, nor RestTemplate offer any facility to do that.

      • AbstractOAuth2ServiceProvider's getApi(..) methods requires only an access token (what happens if it is invalid? Where should the refresh token come from?)
      • AbstractOAuth2ApiBinding again has only an access token, and even if there was a facility to accommodate the refresh-on-failure, the refresh token isn't stored anywhere
      • RestTemplate offers a ResponseErrorHandler and a list of request interceptors. Neither of them can be used to make a new request in order to refresh the access token (let alone return that token to the caller, to be persisted). a ResponseErrorHandler can send a refreshAccess request, and can persist the result, but cannot change the result from the restTemplate invocation. a request interceptor can do nothing about the response (logically)

      Currently I have worked this around by using the refresh token on every request (passing it instead of accesstoken to getApi()), and using the thus-obtained access token to make authenticated calls. This relies on the fact that google+ in particular does not change the refresh token, so it remains a valid way to make requests forever. However, by spec, the provider can choose to renew the refresh token as well. Even in that case the workaround can be used (just persist the new refresh token), but either way it goes against the idea of OAuth. The refresh token should be used only if the original token has expired. I don't know if google won't at some point "frown upon" frequent refreshes and impose some limit.

      (I choose an issue type of "bug", because it is bug per the OAuth2 (latest draft) spec, but feel free to change it to "improvement". I prioritize it as "blocker", because I am publishing a google+ API based on spring-social, and it is not a good idea to spread software that relies on workarounds)

      My suggestion would be to let the error handler change the result of the resttemplate invocation, and also provide an error handler that checks for 401 and tries to refresh the token. That handler should have some callback that informs the application about the change, so that the new token(s) can be persisted.

        Activity

        Hide
        Craig Walls added a comment - - edited

        Note, I'll also need to find an OAuth 2 provider that uses refresh tokens to try this out with. Both Facebook and LinkedIn force a redirect-based renewal of tokens instead of using refresh tokens. Google+ is a likely candidate for this.

        Show
        Craig Walls added a comment - - edited Note, I'll also need to find an OAuth 2 provider that uses refresh tokens to try this out with. Both Facebook and LinkedIn force a redirect-based renewal of tokens instead of using refresh tokens. Google+ is a likely candidate for this.
        Hide
        Craig Walls added a comment -

        One option that I'm considering here (and that makes a lot of sense) is to handle this in the API binding's error handler, or better in a common base class for all API binding error handlers.

        This would have some minimal impact to API binding implementations, but very minimal. And handling authorization exceptions in an error handler makes sense. An added plus is that some common non-authorization errors could be handled in the base class, too...alleviating the burden on the API binding implementation to handle those themselves.

        Show
        Craig Walls added a comment - One option that I'm considering here (and that makes a lot of sense) is to handle this in the API binding's error handler, or better in a common base class for all API binding error handlers. This would have some minimal impact to API binding implementations, but very minimal. And handling authorization exceptions in an error handler makes sense. An added plus is that some common non-authorization errors could be handled in the base class, too...alleviating the burden on the API binding implementation to handle those themselves.
        Hide
        marc schipperheyn added a comment -

        If this is a feature that is hard to implement in a cross vendor way, why not create some logical hooks for the various implementations to develop against and provide a simple fallback that supports removing the connection and recreating it

        Show
        marc schipperheyn added a comment - If this is a feature that is hard to implement in a cross vendor way, why not create some logical hooks for the various implementations to develop against and provide a simple fallback that supports removing the connection and recreating it
        Hide
        Craig Walls added a comment -

        Changing issue to New Feature, as opposed to a bug. Also moving to 2.0.0 Backlog. At the moment the main Spring Social provider modules that rely on OAuth 2 are Facebook and LinkedIn, neither of which support refresh tokens. In those cases, a reauthorization flow (as provided by ReconnectFilter) is the only way to renew a connection.

        This issue will be reconsidered in Spring Social 2.0.0 for providers who support refresh tokens.

        Show
        Craig Walls added a comment - Changing issue to New Feature, as opposed to a bug. Also moving to 2.0.0 Backlog. At the moment the main Spring Social provider modules that rely on OAuth 2 are Facebook and LinkedIn, neither of which support refresh tokens. In those cases, a reauthorization flow (as provided by ReconnectFilter) is the only way to renew a connection. This issue will be reconsidered in Spring Social 2.0.0 for providers who support refresh tokens.
        Hide
        Joseph Barefoot added a comment - - edited

        I have been watching this issue and SOCIAL-328 for some time now, and I'd like to share some thoughts. The implemented solution for SOCIAL-328 seems fine for a web app, except for perhaps minor collision issues with other configured filters/exception resolvers. However it obviously doesn't work at all for offline access (the solution is a servlet filter after all).

        The solution suggested above for the Google+ client, to do this at a lower level in/near the request factory, might be ok however I see two problems with it: 1) It seems like it could be difficult to configure/inject behavior and 2) Really you want the refresh to be transparent to calling code if possible, i.e. the operation is automatically retried post-refresh. That seems hard without a proxy for method intercepts, and given the creative nature of the api client implementations I've seen (i.e. delegate classes for different operation groups), automatically creating proxies for different api clients at a low level in the framework seems really, really hard. You would have to change the SPI for implementing clients entirely methinks.

        So...if you have a system that uses an OAuth API for both web interactions and async/offline scheduled tasks on a user's behalf (e.g., email alerts when some status changes), and you want consistent automatic refresh behavior, then the only satisfactory solution I've come up with is a service interface + proxy factory pattern.

        The idea here is that you define a base service interface/abstract class (in my case, I just used an abstract class) that has utility methods for checking expiration and refreshing the OAuth token (also storing the new token). To do this with Spring Social, you have to have a handle to the Connection for the api in question, and the ConnectionRepository. Your service interface wraps all the calls you want to make to the api client as top-level methods, no matter which delegate it uses under the covers. Then you obtain handles to your services via a factory that actually returns proxies, with method intercepts to do the right thing.

        Here is some simplified example code...the full code involves handling some custom exceptions from other client APIs as well as locating the authentication principal, so it would have to be refactored significantly to be useful as generic Spring components.

        OAuth2ServiceFactory.java

            public GoogleService googleService(Connection<Google> connection){
                return (GoogleService) Proxy.newProxyInstance(GoogleService.class.getClassLoader(), new Class<?>[]{GoogleService.class},
                        new OAuth2Service.ServiceInvocationHandler(new GoogleServiceImpl(connection, connectionManager)));
            }
        

        OAuth2Service.ServiceInvocationHandler.java

               public ServiceInvocationHandler(OAuth2Service service) {
                    this.service = service;
                }
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    service.checkExpiration();
                    try {
                        return method.invoke(service, args);
                    } catch (InvocationTargetException ite) {
                        if(ite.getTargetException() instanceof NotAuthorizedException){
                            logger.catching(ite.getTargetException());
                            boolean refreshed;
                            try {
                                refreshed = service.refreshConnection();
                            } catch (Exception e) {
                                NotAuthorizedException nae = new NotAuthorizedException("Unable to refresh connection after catching exception for api invocation, cause contains caught exception for failed refresh");
                                nae.initCause(e);
                                throw nae;
                            }
         
                            if (refreshed) {
                                return method.invoke(service, args);
                            }
                        }
                        throw ite.getTargetException();
                    }
                }
        

        Anyways, this has worked well for me in both online and offline contexts. If there was enough interest in this solution I could probably clean it up and refactor it so that exception handling for refresh is configurable/injectable. However anyone using it would still need to define their own service wrappers so that there are methods to intercept, and a service factory impl to return them.

        Show
        Joseph Barefoot added a comment - - edited I have been watching this issue and SOCIAL-328 for some time now, and I'd like to share some thoughts. The implemented solution for SOCIAL-328 seems fine for a web app, except for perhaps minor collision issues with other configured filters/exception resolvers. However it obviously doesn't work at all for offline access (the solution is a servlet filter after all). The solution suggested above for the Google+ client, to do this at a lower level in/near the request factory, might be ok however I see two problems with it: 1) It seems like it could be difficult to configure/inject behavior and 2) Really you want the refresh to be transparent to calling code if possible, i.e. the operation is automatically retried post-refresh. That seems hard without a proxy for method intercepts, and given the creative nature of the api client implementations I've seen (i.e. delegate classes for different operation groups), automatically creating proxies for different api clients at a low level in the framework seems really, really hard. You would have to change the SPI for implementing clients entirely methinks. So...if you have a system that uses an OAuth API for both web interactions and async/offline scheduled tasks on a user's behalf (e.g., email alerts when some status changes), and you want consistent automatic refresh behavior, then the only satisfactory solution I've come up with is a service interface + proxy factory pattern. The idea here is that you define a base service interface/abstract class (in my case, I just used an abstract class) that has utility methods for checking expiration and refreshing the OAuth token (also storing the new token). To do this with Spring Social, you have to have a handle to the Connection for the api in question, and the ConnectionRepository. Your service interface wraps all the calls you want to make to the api client as top-level methods, no matter which delegate it uses under the covers. Then you obtain handles to your services via a factory that actually returns proxies, with method intercepts to do the right thing. Here is some simplified example code...the full code involves handling some custom exceptions from other client APIs as well as locating the authentication principal, so it would have to be refactored significantly to be useful as generic Spring components. OAuth2ServiceFactory.java public GoogleService googleService(Connection<Google> connection){ return (GoogleService) Proxy.newProxyInstance(GoogleService.class.getClassLoader(), new Class<?>[]{GoogleService.class}, new OAuth2Service.ServiceInvocationHandler(new GoogleServiceImpl(connection, connectionManager))); } OAuth2Service.ServiceInvocationHandler.java public ServiceInvocationHandler(OAuth2Service service) { this.service = service; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { service.checkExpiration(); try { return method.invoke(service, args); } catch (InvocationTargetException ite) { if(ite.getTargetException() instanceof NotAuthorizedException){ logger.catching(ite.getTargetException()); boolean refreshed; try { refreshed = service.refreshConnection(); } catch (Exception e) { NotAuthorizedException nae = new NotAuthorizedException("Unable to refresh connection after catching exception for api invocation, cause contains caught exception for failed refresh"); nae.initCause(e); throw nae; }   if (refreshed) { return method.invoke(service, args); } } throw ite.getTargetException(); } } Anyways, this has worked well for me in both online and offline contexts. If there was enough interest in this solution I could probably clean it up and refactor it so that exception handling for refresh is configurable/injectable. However anyone using it would still need to define their own service wrappers so that there are methods to intercept, and a service factory impl to return them.

          People

          • Assignee:
            Craig Walls
            Reporter:
            Bozhidar Bozhanov
          • Votes:
            12 Vote for this issue
            Watchers:
            12 Start watching this issue

            Dates

            • Created:
              Updated: