0

I want to use REST API with SharePoint, I'm not here talking about using graph API.

I created an Azure Application and selected SharePoint API to set authorizations: enter image description here

I created secret for OAuth 2.0. Here are the authorizations I set for an application. it should not involve any user account ... enter image description here

Now in a java application, for authentication, I'm using msal4j dependency:

<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>msal4j</artifactId>
    <version>1.17.2</version>
</dependency>

I also have an AuthProviderRequest object:

@Test
@Order(2)
void restAuthenticate() throws Exception {
    // HttpConnectionProvider
    IAuthenticationResult result = sharepointManager.authenticate(restGraphProviderRequest);
    System.out.println(result.accessToken());
    String endpoint = "https://{{tenantName}}.sharepoint.com/sites/{{siteName}}/_api/web/lists/GetByTitle('{{libName}}')/items";
    HttpRequest request = HttpRequest.newBuilder().uri(URI.create(endpoint))
            .header(RequestConstants.HTTP_HEADER_AUTHORIZATION,
                    RequestConstants.HTTP_HEADER_AUTHORIZATION_BEARER_PREFIX + result.accessToken()) //
            .build();
    HttpClient client = HttpClient.newBuilder().build();
    HttpResponse<String> response = client.send(request,
            HttpResponse.BodyHandlers.ofString());
    System.out.println(response);
}

Here my TENANT_AUTHORITY = "https://login.microsoftonline.com/{{ sharepointTenantId }}"; And my SCOPE_MS_GRAPH_DEFAULT = "https://{{ tenantName }}.sharepoint.com/.default";

if i run my unit test i do retrieve a JWT token... I decoded the token to check if it looked correct.. for a SharePoint graph application aud should be : "https://graph.microsoft.com",

{
  "aud": "00000003-0000-0ff1-ce00-000000000000",
  "iss": "https://sts.windows.net/{{ tenantId }}/",
  "iat": 1731857052,
  "nbf": 1731857052,
  "exp": 1731860952,
  "aio": "k2BgYCgvOXulrW5bcsKiANamCU47NKdfuaHRkb3ilVrLSZE73MYA",
  "app_displayname": "sharepointRest ",
  "appid": "3a0aba82-2805-4dd7-b9f9-41c854199262",
  "appidacr": "1",
  "idp": "https://sts.windows.net/{{ tenantId }}/",
  "idtyp": "app",
  "oid": "...",
  "rh": "...",
  "roles": [
    "Sites.Manage.All",
    "Sites.Read.All",
    "Sites.ReadWrite.All",
    "Sites.FullControl.All"
  ],
  "sid": "...",
  "sub": "...",
  "tid": "...",
  "uti": "...",
  "ver": "1.0",
  "xms_idrel": "7 24",
  "xms_pftexp": 1731947352
}

it seems here that i can do have a valid token i could use with rest API in SharePoint.

Here is the response:

  • When running unit test i have a 401 response.
  • When testing Url with tokenn i have: Unsupported app only token.

I tried to add authorization to application in Graph Explorer with a POST request to the following endpoint: https://graph.microsoft.com/v1.0/sites/{{ tenantName }}.sharepoint.com:/sites/{{ siteName }}

with following payload:

{
    "roles": [
        "write",
        "read",
        "delete",
        "owner"
    ],
    "grantee": {
        "@odata.type": "microsoft.graph.aadApplication",
        "id": "3a0aba82-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    }
}

After update:

  • When running unit test i have a 401 response.
  • When testing Url with token i have: Unsupported app only token.

I also tried to use OKHttp to authenticate:

public class RestAuth {
    // Azure AD and SharePoint Configurations
    private static final String CLIENT_ID  = "myClientId";
    private static final String CLIENT_SECRET  = "myClientSecret";
    private static final String TENANT_ID  = "myTenantId";
    private static final String RESOURCE  = "https://myTenantName.sharepoint.com";
    private static final String SITE_NAME = "mySite";
    // OkHttp client
    private static final OkHttpClient client = new OkHttpClient();


    public static void main(String[] args) {
        try {
            String accessToken = getAccessToken();
            System.out.println(accessToken);
            if (accessToken != null) {
                getSiteLists(accessToken);
            } else {
                System.out.println("Failed to fetch the access token.");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 1. Retrieve an Access Token
    private static String getAccessToken() throws IOException {
        String tokenEndpoint = String.format("https://login.microsoftonline.com/%s/oauth2/token", TENANT_ID);
        RequestBody formBody = new FormBody.Builder()
                .add("grant_type", "client_credentials")
                .add("client_id", CLIENT_ID)
                .add("client_secret", CLIENT_SECRET)
                .add("resource", RESOURCE)
                .build();

        Request request = new Request.Builder()
                .url(tokenEndpoint)
                .post(formBody)
                .header("Content-Type", "application/x-www-form-urlencoded")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                String responseBody = response.body().string();
                JSONObject json = new JSONObject(responseBody);
                return json.getString("access_token"); // Extract the access token
            } else {
                System.err.println("Failed to get access token: " + response.code() + " - " + response.message());
                System.err.println(response.body().string());
                return null;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 2. Get the Lists from the SharePoint Site
    private static void getSiteLists(String accessToken) {
        String siteUrl = String.format("%s/sites/%s/_api/web/lists", RESOURCE, SITE_NAME);

        Request request = new Request.Builder()
                .url(siteUrl)
                .get()
                .header("Authorization", "Bearer " + accessToken)
                .header("Accept", "application/json;odata=verbose")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                String responseBody = response.body().string();
                System.out.println("Site Lists: " + responseBody);
            } else {
                System.err.println("Failed to fetch site lists: " + response.code() + " - " + response.message());
                System.err.println(response.body().string());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

I obtain: Failed to fetch site lists: 401 - Unsupported app only token.

the decoded token is absolutely similar to previous one: same aud, iss, roles...

I checked access control in admin SharePoint center :

  • for unmanaged devices, i set authorizations
  • i activated access for legacy authentication

I really don't get what is wrong here. thanks

1 Answer 1

1

I simply throw all code i implemented, remove client secret and create a self sign certificate.

I found this nice video (not 100% related to my issue but as she talked about 'unsupported app token') : https://www.youtube.com/watch?v=kpDs4Xgb_z8

Here is the tutorial : https://learn.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread

My whole implementation changed :

public static void main(String[] args) {
    try {
        // Authenticate and get the access token
        IAuthenticationResult authResult = authenticate();
        String accessToken = authResult.accessToken();

        // Make the API call
        callSharePointApi(accessToken);

    } catch (MsalException e) {
        System.out.println("Error during authentication: " + e.getMessage());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static IAuthenticationResult authenticate() throws Exception {
    File certificateFile = new File(RestV10Auth.class.getClassLoader().getResource(CERTIFICATE_PATH).getFile());

    PrivateKey privateKey = CertificateUtils.getPrivateKeyFromPfx(certificateFile, CERTIFICATE_PASSWORD);
    X509Certificate certificate = CertificateUtils.getX509CertificateFromPfx(certificateFile, CERTIFICATE_PASSWORD);

    ConfidentialClientApplication cca = ConfidentialClientApplication
            .builder(CLIENT_ID, ClientCredentialFactory.createFromCertificate(privateKey, certificate))
            .authority("https://login.microsoftonline.com/" + TENANT_ID)
            .build();

    ClientCredentialParameters clientCredentialParam = ClientCredentialParameters
            .builder(Collections.singleton(SHAREPOINT_RESOURCE + "/.default"))
            .build();

    IAuthenticationResult result = cca.acquireToken(clientCredentialParam).join();
    return result;
}
public static void callSharePointApi(String accessToken) throws Exception {
    OkHttpClient client = new OkHttpClient();

    String url = SHAREPOINT_RESOURCE + "/_api/web/lists";

    Request request = new Request.Builder()
            .url(url)
            .header("Authorization", "Bearer " + accessToken)
            .header("Accept", "application/json")
            .build();

    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        System.out.println("Response: " + response.body().string());
    } else {
        System.out.println("Error: " + response.code() + " " + response.message());
    }
}

here is also the certificateUtils class:

public class CertificateUtils {
    public static PrivateKey getPrivateKeyFromPfx(File pfxFile, String password) throws Exception {
        KeyStore keystore = KeyStore.getInstance("PKCS12");
        try (FileInputStream fis = new FileInputStream(pfxFile)) {
            keystore.load(fis, password.toCharArray());
        }

        String alias = keystore.aliases().nextElement();
        return (PrivateKey) keystore.getKey(alias, password.toCharArray());
    }

    public static X509Certificate getX509CertificateFromPfx(File pfxFile, String password) throws Exception {
        KeyStore keystore = KeyStore.getInstance("PKCS12");
        try (FileInputStream fis = new FileInputStream(pfxFile)) {
            keystore.load(fis, password.toCharArray());
        }

        String alias = keystore.aliases().nextElement();
        return (X509Certificate) keystore.getCertificate(alias);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.