External Provider as Salesforce does not refresh the token when it expires

Hello everyone, I have been dealing with this issue for more than a week now…
I have this manifest and I am trying to have Salesforce as external provider. The access token is working fine but it does not refresh. How do you usually debug this?

Here are the Salesforce Oauth2 endpoints: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_endpoints.htm&type=5

And here is my latest manifest file after I tried everything :frowning: :

permissions:
  external:
    fetch:
      backend:
        - https://login.salesforce.com
        - https://something.loca.lt
  scopes: []
remotes:
  - key: sf-oauth
    baseUrl: https://login.salesforce.com
  - key: my-api
    baseUrl: https://something.loca.lt
providers:
  auth:
    - key: salesforce
      name: Salesforce
      scopes:
        - api
        - refresh_token
        - offline_access
      type: oauth2
      bearerMethod:
        type: authorization-header
      clientId: xxxxxxxxxxxxxxxx
      remotes:
        - sf-oauth
        - my-api
      actions:
        authorization:
          remote: sf-oauth
          path: /services/oauth2/authorize
          queryParameters:
            access_type: offline
            prompt: consent
            grant_type: refresh_token
        exchange:
          remote: sf-oauth
          path: /services/oauth2/token
          resolvers:
            accessToken: access_token
            accessTokenExpires: expires_in
            refreshToken: refresh_token
        revokeToken:
          remote: sf-oauth
          path: /services/oauth2/revoke
        retrieveProfile:
          remote: sf-oauth
          path: /services/oauth2/userinfo
          resolvers:
            id: user_id
            displayName: name
            avatarUrl: picture

Hi @TziortzisKyprianou1, can you provide your app ID and the forge environment you are testing this app in? I’ll check the logs and see what could be causing the issue.

Hello @Ash , yes my dev environment is prodly-jira.atlassian.net and my app id is ari:cloud:ecosystem::app/c3af9e22-6ec4-428d-98f9-f0405c97759b

Thanks

Thanks, @TziortzisKyprianou. I’ve looked at the app’s config, and they seem to be correct. Can you let me know how you are testing to reproduce this issue? What error do you see? I’m also trying to reproduce the error on my end.

HI Ash,

As soon as the access token is expired, it never reauthenticates. And obviously, I can’t fetch my data anymore. My app is very basic, having just one view file, see the snippet attached.

I need to understand how forge is handling the refresh flow. In Salesforce, the expires_in property does not exist in the response. When does Forge trigger the refresh if it doesn’t know when it will expire? Is it based on that property? Is it based on the response if its status code is 401 for example? Is it possible to get on a call at some point?

Hey @TziortzisKyprianou, we did some more digging around the logs, we found one user account has provided consent to your app and that request has been successful on our end and we have received the refresh token from Salesforce.
But 30 mins later we see that an API call is made to revoke the user access which deletes all the tokens from our end (including the refresh token). As part of the same action, we also try to revoke the access from Salesforce which returned 404 error.

So next time the app makes an API call, we have to display the consent screen again since we don’t have any tokens on our end.

Have you revoked access for this user from “Connected Apps” in the user’s profile? Is the salesforce app still active?

Yes, I did that while I was testing after my access token was already expired. The Salesforce access token is configured to live only for 15 minutes now. I had to revoke it manually from the connected apps because the calls after the 15 minutes were failing with 401, and Forge app didn’t ask me to login again into salesforce, and the refresh flow was never updating the access token. Would it be useful to reauthenticate again and let it expire on its own to read the logs?

Yes please, can you reauthenticate? I will check the logs tomorrow for any issue

Hey Ash, sorry I had to create a new app with id: ari:cloud:ecosystem::app/f0f4b6eb-e97a-4b88-9048-859e26b76081

As you can see, it’s working now until the access_token expires… I will leave it like this waiting for your logs. Thanks again for the help.

Screenshot 2022-11-29 at 3.54.45 PM

Thanks, @TziortzisKyprianou, the access token is retrieved as a pull mechanism, not as a push. So when the user invokes the forge app again, we check if the existing token is expired, if so we try and contact the provider to get the new token using the refresh token. Till now the logs look ok, the first invocation of the app has retrieved the access token and refresh token.

Hey Ash, the issue is that the access token is expired though, and no new one is issued…

hey @TziortzisKyprianou, yep I wanted to check the logs for when that issue happens.
I have created an app with salesforce as a provider, I’ll try and get that set up and see if I can reproduce it on my end. I’ll keep you posted.

Hey Ash, I did something different yesterday to check. Instead of redirecting back to Jira when the salesforce provider is called, I redirected locally to my Node app to “debug” the flow and then back to Jira. What I noticed is that indeed when the expires_in doesn’t exist, Jira never calls the refresh flow again. When I redirected back to my node and then back to Jira, I set the expires_in manually to 60. Indeed after I added that, every 60 seconds when I invoke the app, a refresh flow happens.

My assumption is that Forge App is checking this expires_in property every time you invoke the app, and then if time passes, it’s calling the refresh flow. If that’s true, then it is a problem because expires_in is RECOMMENDED in the RFC 6749 but not REQUIRED. https://www.rfc-editor.org/rfc/rfc6749#section-4.1

  RECOMMENDED.  The lifetime in seconds of the access token.  For
         example, the value "3600" denotes that the access token will
         expire in one hour from the time the response was generated.
         If omitted, the authorization server SHOULD provide the
         expiration time via other means or document the default value.

Salesforce indeed provides a different endpoint to get this information, https://hostname/services/oauth2/introspect documented here: https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_endpoints.htm&type=5

If the missing expires_in in the response is really the issue, then there should be a workaround for Providers like Salesforce that might not provide this key. Either by calling a different endpoint to get this info or by setting a default time in the manifest file??? I would also love to see a function that allows the developer to force a refresh flow, since the current implementation does not support access tokens revoked manually by the provider… In case of getting a 401 error and refresh token still works there is no reason to ask the user to login again…

Can this issue be considered an enhancement request or a known issue? If so, can you open it somewhere, and is there a way for us to formally track it?

hey @TziortzisKyprianou, thanks for your patience.
Yes, you’re right, from our investigation, we found that we rely on the expires_in attribute from the provider to decide if the token needs a refresh. We have an internal enhancement ticket created. Will keep you posted if we create an external facing ticket so you can track it.

Thanks, Ash, do we have a rough estimation of how much time it will take or what type of enhancement will be?