Hi Community,
I’m developing a Forge app for Jira Cloud that aims to display the groups a specific user belongs to. I’m using the /rest/api/3/user/{accountId}/groups
endpoint for this purpose, making the API call using api.asUser()
.
I’m encountering a persistent 401 Unauthorized
error when calling this specific endpoint, and I’m running out of ideas on how to solve it.
Here are the relevant parts of my setup and the error logs:
App Goal: Display groups for a given accountId
. Initially hardcoded, but now dynamically fetched using /rest/api/3/myself
.
API Endpoint Causing Issue: GET /rest/api/3/user/{accountId}/groups
API Call Method: api.asUser().requestJira(...)
Manifest Scopes: My manifest.yml
includes the necessary scopes as per the API documentation:
permissions:
scopes:
- read:jira-user
- read:group:jira
# Other scopes are present for other features but these are the relevant ones
# - manage:jira-configuration
# - read:jira-work
# - read:application-role:jira
# - read:user:jira
# - read:avatar:jira
(Note: I have also tried with only read:jira-user
or only read:group:jira
with no difference).
Backend Resolver (getUserGroups
): This is the resolver making the failing call:
resolver.define('getUserGroups', async (req) => {
const { accountId } = req.payload;
console.log(`[getUserGroups] Resolver called for accountId: ${accountId}`);
if (!accountId) {
console.error('[getUserGroups] accountId is required but missing in payload.');
throw new Error('accountId is required');
}
try {
let startAt = 0;
const maxResults = 50;
let isLast = false;
const userGroups = [];
console.log(`[getUserGroups] Starting pagination to fetch groups for ${accountId}`);
while (!isLast) {
console.log(`[getUserGroups] Fetching page with startAt=${startAt} and maxResults=${maxResults}`);
const groupsResponse = await api.asUser().requestJira( // <-- api.asUser() call
route`/rest/api/3/user/${accountId}/groups?startAt=${startAt}&maxResults=${maxResults}`,
{
headers: {
'Accept': 'application/json'
}
}
);
console.log(`[getUserGroups] API response status: ${groupsResponse.status} ${groupsResponse.statusText}`); // <-- Logs 401 Unauthorized
if (!groupsResponse.ok) {
// Error handling based on status...
throw new Error(`Failed to fetch user's groups: ${groupsResponse.statusText}. Status: ${groupsResponse.status}`);
}
// ... processing data (never reached due to 401)
isLast = groupsData.isLast;
startAt += maxResults;
}
console.log(`[getUserGroups] Pagination finished. Found ${userGroups.length} groups for user ${accountId}`);
return { groups: userGroups };
} catch (error) {
console.error(`[getUserGroups] An error occurred during group fetch for ${accountId}:`, error);
throw error;
}
});
Key Observation: /rest/api/3/myself Works!
I also have a resolver (getCurrentUserAccountId) that calls /rest/api/3/myself using api.asUser(). This call succeeds with a 200 OK and returns the current user’s accountId and displayName.
// ... in the same index.js file
resolver.define('getCurrentUserAccountId', async () => {
console.log('[getCurrentUserAccountId] Resolver called.');
try {
const response = await api.asUser().requestJira(route`/rest/api/3/myself`, { // <-- api.asUser() call
headers: {
'Accept': 'application/json'
}
});
console.log(`[getCurrentUserAccountId] API response status: ${response.status} ${response.statusText}`); // <-- Logs 200 OK
if (!response.ok) {
throw new Error(`Failed to fetch current user information: ${response.statusText}`);
}
const userData = await response.json();
console.log('[getCurrentUserAccountId] Fetched user data:', { accountId: userData.accountId, displayName: userData.displayName });
return { accountId: userData.accountId, displayName: userData.displayName };
} catch (error) {
console.error('[getCurrentUserAccountId] An error occurred:', error);
throw error;
}
});
The frontend successfully calls getCurrentUserAccountId
, gets the user’s ID (which is 712020:ef130e54-ba84-43ab-b516-31a58a31693a
in my tests), and then calls getUserGroups
with this ID. The getUserGroups
resolver then gets the 401.
Error Logs: Here are the logs showing the successful /myself
call followed by the failed /user/groups
call:
invocation: [ID_1 - getCurrentUserAccountId invocation ID] index.handler
INFO [TIMESTAMP_1] [getCurrentUserAccountId] Resolver called.
INFO [TIMESTAMP_1] [getCurrentUserAccountId] API response status: 200 OK
INFO [TIMESTAMP_1] [getCurrentUserAccountId] Fetched user data: {
accountId: '712020:ef130e54-ba84-43ab-b516-31a58a31693a',
displayName: 'Romera, Jordi (ES)'
}
INFO [TIMESTAMP_1] [getCurrentUserAccountId] Returning accountId: 712020:ef130e54-ba84-43ab-b516-31a58a31693a
invocation: [ID_2 - getUserGroups invocation ID] index.handler
INFO [TIMESTAMP_2] [getUserGroups] Resolver called for accountId: 712020:ef130e54-ba84-43ab-b516-31a58a31693a
INFO [TIMESTAMP_2] [getUserGroups] Starting pagination to fetch groups for 712020:ef130e54-ba84-43ab-b516-31a58a31693a
INFO [TIMESTAMP_2] [getUserGroups] Fetching page with startAt=0 and maxResults=50
INFO [TIMESTAMP_2] [getUserGroups] API response status: 401 Unauthorized // <-- Problem here
ERROR [TIMESTAMP_2] [getUserGroups] Error fetching groups page: 401 Unauthorized
ERROR [TIMESTAMP_2] [getUserGroups] An error occurred during group fetch for 712020:ef130e54-ba84-43ab-b516-31a58a31693a: Error: Failed to fetch user's groups: Unauthorized. Status: 401
... (stack trace)
(Note: Timestamps and invocation IDs will vary)
Troubleshooting Steps Taken:
- Confirmed necessary scopes (
read:jira-user
,read:group:jira
) are inmanifest.yml
. - Deployed and reinstalled the app multiple times.
- Verified that the
accountId
being passed togetUserGroups
is correct (it’s the same ID returned by/myself
). - Checked that the user running the app has the “Browse users and groups” global permission in Jira.
- Checked the Atlassian Status page (no relevant incidents reported at the time).
- Tried testing with a different user (a Jira Admin), and the issue persists.
The fact that api.asUser()
works for /myself
but fails with a 401 for /user/{accountId}/groups
on the same site, for the same user, within moments of each other, is very puzzling. It doesn’t seem to be a general authentication issue with api.asUser()
or a simple missing scope/permission.
Has anyone encountered a similar issue with this specific endpoint or a pattern where api.asUser()
works for some endpoints but not others with a 401? Are there any known site configurations or edge cases that could cause this?
Any help or suggestions would be greatly appreciated!
Thanks,
Jordi