OAuth integration issue with Confluence API and space visibility

I am working on integrating Confluence with my application using the OAuth2 authentication method. I have successfully implemented the OAuth flow, obtained the access token, and can hit the Confluence API endpoints. However, I am encountering an issue where I am unable to list the spaces unless I manually enable anonymous access in the space settings and the general site settings.

Despite having the necessary scopes and using the access token, the API does not return any spaces unless anonymous access is enabled. Here is an overview of my OAuth integration process:

  1. Initiate OAuth flow to get authorization code.
  2. Exchange authorization code for access token.
  3. Use the access token to hit the https://api.atlassian.com/oauth/token/accessible-resources endpoint to retrieve the user’s accessible resources.
  4. Use the accessible resource URL to hit the /wiki/rest/api/space endpoint to list spaces.

Unless I manually set anonymous access, the /wiki/rest/api/space endpoint returns empty results.

Is there any additional configuration or permissions required to access the spaces using the OAuth access token without enabling anonymous access? Any guidance or assistance would be greatly appreciated.

Welcome to the Atlassian developer community @JohnLangenderfer,

Quick warning that /wiki/rest/api/space is deprecated in favor of /wiki/api/v2/spaces.

I’ve tried with both endpoints and the results work correctly for me. Namely, I nave neither anonymous nor guest access on any spaces. I can still see all the spaces.

From experience, the most error-prone part of this flow is your 3. The result from accessible-resources is an array, and your client can’t really know which site was chosen most recently.

Under the following circumstances, I would expect the behavior you describe:

  • The user picks site A
  • Your client uses the Cloud ID for unauthorized site B

Confluence won’t send an error code, but an empty list. “The user” is simply not authorized on any of the spaces, so they can’t seem any. If the accessible-resources array is handled wrong, the above is possible. Can you post the accessible-resources result here (nothing in that payload is considered secret)?

Here’s the accessible-resources result from my console output:

[
    {
        "id": "665ac162-737c-41c0-9037-a040ded9ad03",
        "url": "https://johnlangen.atlassian.net",
        "name": "johnlangen",
        "scopes": [
            "read:user.property:confluence",
            "read:comment:confluence",
            "read:page:confluence",
            "read:confluence-props",
            "write:confluence-content",
            "read:blogpost:confluence",
            "read:space-details:confluence",
            "read:space.setting:confluence",
            "read:space:confluence",
            "read:confluence-groups",
            "read:analytics.content:confluence",
            "write:space.setting:confluence",
            "read:template:confluence",
            "read:content.property:confluence",
            "read:configuration:confluence",
            "read:confluence-content.all",
            "read:user:confluence",
            "read:attachment:confluence",
            "read:content:confluence",
            "write:confluence-groups",
            "read:label:confluence",
            "readonly:content.attachment:confluence",
            "read:content-details:confluence",
            "write:confluence-space",
            "read:confluence-content.permission",
            "manage:confluence-configuration",
            "read:custom-content:confluence",
            "write:configuration:confluence",
            "read:confluence-user",
            "search:confluence",
            "write:confluence-props",
            "read:space.property:confluence",
            "read:content.restriction:confluence",
            "write:confluence-file"
        ],
        "avatarUrl": "https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/star.png"
    }
]

Based on your suggestion, it seems I might be using the incorrect Cloud ID. I’ve double-checked, and I believe I’m using the correct one (665ac162-737c-41c0-9037-a040ded9ad03). However, I’m still seeing an empty list of spaces unless anonymous access is enabled. Could there be another permission or configuration I’m missing?

Thank you for your assistance!

Additionally, when testing the /confluence/list-spaces endpoint with the user ID 665ac162-737c-41c0-9037-a040ded9ad03, I get the following results:

When anonymous users are not allowed to view spaces:

{
    "message": "Confluence spaces retrieved successfully",
    "spaces": [  ]
}

When I allow anonymous users to view the ‘second space’:

{
    "message": "Confluence spaces retrieved successfully",
    "spaces": [
        {
            "name": "second space",
            "id": 196836,
            "key": "SS"
        }
    ]
}
/confluence/list-spaces

@router.get("/confluence/list-spaces/")
def list_spaces(user_id: str):
    logging.debug(f"Listing spaces for user ID: {user_id}")
    user_data = user_data_store.get(user_id)
    if not user_data:
        logging.error("User data not found")
        return JSONResponse(content={"message": "User data not found"}, status_code=404)

    access_token = user_data['access_token']
    base_url = user_data['base_url']
    logging.debug(f"Making request to list spaces with access token: {access_token} and base URL: {base_url}")
    
    response = requests.get(f"{base_url}/wiki/rest/api/space", headers={
        'Authorization': f'Bearer {access_token}'
    })
    
    logging.debug(f"List spaces response status: {response.status_code}")
    logging.debug(f"List spaces response: {response.text}")

    if response.status_code != 200:
        logging.error(f"Failed to list spaces: {response.text}")
        return JSONResponse(content={"message": "Failed to list spaces"}, status_code=500)
    
    spaces = response.json().get("results", [])
    user_data['spaces'] = spaces  # Store spaces in user data
    filtered_data = [
        {"name": space["name"], "id": space["id"], "key": space["key"]}
        for space in spaces
    ]

    return JSONResponse(
        content={
            "message": "Confluence spaces retrieved successfully",
            "spaces": filtered_data,
        },
        status_code=200,

*** UPDATE ***

I am able to get the correct response when directly calling the endpoint.
curl -X GET “https://api.atlassian.com/ex/confluence/665ac162-737c-41c0-9037-a040ded9ad03/wiki/api/v2/spaces

The problem is in my implementation of the FastAPI endpoint.

Got it, thanks for your help though [ibuchanan]!

curl -X GET “http://127.0.0.1:8000/confluence/list-spaces/?user_id=665ac162-737c-41c0-9037-a040ded9ad03

{“message”:“Confluence spaces retrieved successfully”,“spaces”:[{“name”:“John Langenderfer”,“id”:“131074”,“key”:“~712020adecd3a1a7f3494cb92697ab89014730”},{“name”:“My first space”,“id”:“196612”,“key”:“MFS”},{“name”:“second space”,“id”:“196836”,“key”:“SS”}]}%

@router.get("/confluence/list-spaces/")
def list_spaces(user_id: str):
    logging.debug(f"Listing spaces for user ID: {user_id}")
    user_data = user_data_store.get(user_id)
    if not user_data:
        logging.error("User data not found")
        return JSONResponse(content={"message": "User data not found"}, status_code=404)

    access_token = user_data['access_token']
    base_url = user_data['base_url']
    
    logging.debug(f"Making request to list spaces with access token: {access_token} and base URL: {base_url}")
    
    # Correctly construct the API endpoint URL
    endpoint_url = f"https://api.atlassian.com/ex/confluence/{user_id}/wiki/api/v2/spaces"
    
    response = requests.get(endpoint_url, headers={
        'Authorization': f'Bearer {access_token}'
    })
    
    logging.debug(f"List spaces response status: {response.status_code}")
    logging.debug(f"List spaces response: {response.text}")

    if response.status_code != 200:
        logging.error(f"Failed to list spaces: {response.text}")
        return JSONResponse(content={"message": "Failed to list spaces"}, status_code=500)
    
    spaces = response.json().get("results", [])
    user_data['spaces'] = spaces  # Store spaces in user data
    filtered_data = [
        {"name": space["name"], "id": space["id"], "key": space["key"]}
        for space in spaces
    ]

    return JSONResponse(
        content={
            "message": "Confluence spaces retrieved successfully",
            "spaces": filtered_data,
        },
        status_code=200,
    )