Sharepoint REST API not authorized with Azure - Stack Overflow

I want to use REST API with Sharepoint, i'm not here talking about using graph API.I created an A

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:

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

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/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 = "/{{ sharepointTenantId }}"; And my SCOPE_MS_GRAPH_DEFAULT = "https://{{ tenantName }}.sharepoint/.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 : ";,

{
  "aud": "00000003-0000-0ff1-ce00-000000000000",
  "iss": "/{{ tenantId }}/",
  "iat": 1731857052,
  "nbf": 1731857052,
  "exp": 1731860952,
  "aio": "k2BgYCgvOXulrW5bcsKiANamCU47NKdfuaHRkb3ilVrLSZE73MYA",
  "app_displayname": "sharepointRest ",
  "appid": "3a0aba82-2805-4dd7-b9f9-41c854199262",
  "appidacr": "1",
  "idp": "/{{ 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: .0/sites/{{ tenantName }}.sharepoint:/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 tokenn 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  = ";;
    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("/%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 absolutelly 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

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:

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

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/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/{{ sharepointTenantId }}"; And my SCOPE_MS_GRAPH_DEFAULT = "https://{{ tenantName }}.sharepoint/.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",

{
  "aud": "00000003-0000-0ff1-ce00-000000000000",
  "iss": "https://sts.windows/{{ tenantId }}/",
  "iat": 1731857052,
  "nbf": 1731857052,
  "exp": 1731860952,
  "aio": "k2BgYCgvOXulrW5bcsKiANamCU47NKdfuaHRkb3ilVrLSZE73MYA",
  "app_displayname": "sharepointRest ",
  "appid": "3a0aba82-2805-4dd7-b9f9-41c854199262",
  "appidacr": "1",
  "idp": "https://sts.windows/{{ 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/v1.0/sites/{{ tenantName }}.sharepoint:/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 tokenn 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";
    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/%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 absolutelly 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

Share Improve this question edited Nov 17, 2024 at 21:11 davidvera asked Nov 17, 2024 at 16:25 davidveradavidvera 1,5092 gold badges31 silver badges70 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

Client Secrets are not supported for app-only authentication scenario (they are considered not secure enough), you need to use certificate authentication instead. I.e. just use a certificate instead of client secret.

But do you need app-only token? The alternative is a "delegated" token, when users of your app are supposed to authenticate themselves against SharePoint. For that scenario, client secret should be fine.

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/watch?v=kpDs4Xgb_z8

Here is the tutorial : https://learn.microsoft/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/" + 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);
    }
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745631836a4637156.html

相关推荐

  • Sharepoint REST API not authorized with Azure - Stack Overflow

    I want to use REST API with Sharepoint, i'm not here talking about using graph API.I created an A

    4小时前
    20

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信