OAuth Auth Fails in Custom UI Jira Forge App

I have the following manifest file for my app::

providers:
  auth:
    - key: slack
      name: Slack
      scopes:
        - incoming-webhook
        - users.profile:read
      type: oauth2
      clientId: 'clientId'
      remotes:
        - slack-apis
      bearerMethod: authorization-header
      actions:
        authorization:
          remote: slack-apis
          path: /oauth/v2/authorize
        exchange:
          remote: slack-apis
          path: /api/oauth.v2.exchange
        revokeToken:
          remote: slack-apis
          path: /api/auth.revoke
        retrieveProfile:
          remote: slack-apis
          path: /api/users.profile.get
          resolvers:
            id: avatar_hash
            displayName: display_name
            avatarUrl: picture

remotes:
  - key: remote-backend
    baseUrl: 'https://${BACKEND_DOMAIN}'
    auth:
      appUserToken:
        enabled: true
      appSystemToken:
        enabled: true
    operations:
      - compute
      - fetch
  - key: slack-apis
    baseUrl: 'https://slack.com'

permissions:
  content:
    styles:
      - unsafe-inline
  external:
    fetch:
      backend:
        - 'https://slack.com'
    images:
      - '*.atlassian.net'
      - '*.wp.com'

modules:
  jira:projectPage:
    - key: webim-project-page
      resource: main
      resolver:
        function: resolver
      # render: native
      layout: blank
      title: Leiga Assistant

  function:
    - key: resolver
      handler: index.handler
      providers:
        auth:
          - slack
    - key: handle-request-backend
      handler: index.handleRequestBackend

resolvers code

resolver.define('getSlackToken', async () => {
  const slack = asUser().withProvider('slack', 'slack-apis');
  const isAuthenticated = await slack.hasCredentials();
  logger.debug(`slack: ${isAuthenticated}`);
  if (!isAuthenticated) {
    logger.debug('before requestCredentials');
    try {
      await slack.requestCredentials();
    } catch (e) {
      console.error(e);
      logger.error('Failed to request credentials');
      throw e;
    }
    logger.debug('after requestCredentials');
  }
  const res = await slack.fetch('/api/openid.connect.token');
  if (res.ok) {
    return res.json();
  }
  return {
    code: 401,
    message: 'Failed to get slack token',
  };
});

In the UI, there is a button that, when clicked, invokes the ‘getSlackToken’ function. The console logs are as follows:

DEBUG   17:06:24.307  3b71cb84-affc-4dab-a5dc-56947082bf77  resolvers - {"message":"slack: false"}
DEBUG   17:06:24.307  3b71cb84-affc-4dab-a5dc-56947082bf77  resolvers - {"message":"before requestCredentials"}
ERROR   17:06:24.307  3b71cb84-affc-4dab-a5dc-56947082bf77  [NEEDS_AUTHENTICATION_ERR: Authentication Required] {
  status: 401,
  serviceKey: 'slack',
  options: { scopes: undefined, isExpectedError: true }
}
ERROR   17:06:24.307  3b71cb84-affc-4dab-a5dc-56947082bf77  resolvers - {"message":"Failed to request credentials"}

After clicking the access button, Slack authorization is processed, and it redirects to a new window, which then closes automatically. However, my Forge app page still shows the authorization information. Even after refreshing, the app remains unauthorized.

I need help resolving this issue. Thank you!

Hi @alonezhou , are you able to provide me with your appId?

ari:cloud:ecosystem::app/3335813a-6ae1-46ca-9fd3-cbc08613ba33

@BoZhang hello

Sorry @alonezhou , I had a look just now, it seems like the profile retrieval mapping doesn’t line up. It seems like the Slack API to retrieve profile info nests the response like so

{
    "ok": true,
    "profile": {
        "title": "Head of Coffee Production",
        "display_name": "",
        "avatar_hash": "",
         ....
    }
}

can you try update your profile retrieval configuration to take this into account, i.e.:

          resolvers:
            id: profile.avatar_hash
            displayName: profile.display_name
            avatarUrl: profile.picture

also note that I didn’t find the picture field mentioned in the docs, so I don’t know if it will be available or not.

1 Like

@BoZhang Oh, okay. I’ll change it. Is the attribute mapping of this personal profile necessary? I filled it in randomly before.

Hi @alonezhou, yes this is necessary, we use this information to display connections on the Connected apps page. I would recommend that you populate it accurately so that the customers of your app can understand what exactly your app has authorization for.

1 Like

@BoZhang I have reconfigured it, but it still doesn’t work. Can you help me take a look again?

resolvers:
            id: profile.avatar_hash
            displayName: profile.display_name
            avatarUrl: profile.image_48

The logs suggests that the id field still doesn’t exist. For privacy reasons, I can’t view the exact response that you get back from this end point, are you able to call this API somehow and manually confirm that the fields that you are mapping to exists in the response (my guess at this point is that avatar_hash doesn’t exist in the response).
Alternatively, I have seen some developers use this profile endpoint for Slack.

Edit: Another option; you can look at using a dynamic profile retriever. Forge will call a resolver that you implement for profile retrieval instead of a Slack API.

1 Like

@BoZhang I used a dynamic retriever. In the log, the Slack server returned {“ok”:false,“error”:“invalid_auth”}. Is it that Jira didn’t pass the token?

Interesting, which endpoint are you calling for profile retrieving?
For now, an interesting test that I would do would be to return stub values from the profile retrieval to get it working, for testing purposes and then try call the same endpoint like

const res = await slack.fetch('/api/users.identity');

and see what you get.

actually, I think I see the problem, Slack seems to expect the scopes being requested to be passed in as a comma delimited query parameter, normally providers (Google, Microsoft, Atlassian etc
) expect this as a space delimited query parameter.
I think what’s happening is that we are sending malformed scopes from Slack’s perspective.
Also the exchange endpoint that you are using seems to be a legacy endpoint.
Can you please update your manifest to:

        authorization:
          remote: slack-apis
          path: /oauth/v2/authorize
          queryParameters:
            user_scope: incoming-webhook,users.profile:read
        exchange:
          remote: slack-apis
          path: /api/oauth.v2.access
          resolvers:
            accessToken: authed_user.access_token
1 Like

@BoZhang Thank you very much. Authorization can be done normally now. It is just a problem of incorrect configuration of the exchange API.

1 Like

Just as an FYI, this issue exists specifically for this use case.

What you need to be wary of is that scope changes for user_scope will not follow the standard scope change workflow of Forge because it is defined in a query parameter.

1 Like