Share via

Unable to Authenticate .NET 4.0 App with SharePoint Online Using Azure App-Only Token (401 Unauthorized)

Selvakumar K 25 Reputation points
2026-04-02T12:57:55.5433333+00:00

Hi,

We have a .NET 4.0 application used to upload file in sharepoint. It previously authenticated using username and password. Due to this method deprecated, we registered a new Azure AD application and updated our .NET code to use client credentials (app-only authentication).

However, after these changes, we are encountering the following error when trying to access SharePoint:

The remote server returned an error: (401) Unauthorized.

Below is a simplified version of our current authentication code:
public static void ApplyCredentials(Guid UserID, ClientContext ctx) {     System.Net.ServicePointManager.SecurityProtocol = (System.Net.SecurityProtocolType)3072;     string accessToken = GetAccessToken();     ctx.Credentials = null;     ctx.ExecutingWebRequest += (sender, e) =>     {         e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accessToken;         if (e.WebRequestExecutor.WebRequest is System.Net.HttpWebRequest webRequest)         {             webRequest.UserAgent = "NON-BROWSER-APP";             webRequest.PreAuthenticate = false;         }     }; }

public string GetAccessToken() {     string tenantId = "YOUR_TENANT_ID";     string clientId = "YOUR_CLIENT_ID";     string clientSecret = "YOUR_CLIENT_SECRET"; // From Azure Portal > Certificates & Secrets     string scope = "https://[Moderator note: personal info removed] .sharepoint.com/.default";     string url = string.Format("https://login.microsoftonline.com/{0}/oauth2/v2.0/token", tenantId);

 

    using (var client = new WebClient())     {         var values = new NameValueCollection();         values["client_id"] = clientId;         values["scope"] = scope;         values["client_secret"] = clientSecret;         values["grant_type"] = "client_credentials";

 

        try         {             byte[] responseBytes = client.UploadValues(url, "POST", values);             string responseString = Encoding.UTF8.GetString(responseBytes);             string tokenKey = ""access_token":"";             int start = responseString.IndexOf(tokenKey) + tokenKey.Length;             int end = responseString.IndexOf(""", start);             return responseString.Substring(start, end - start);         }         catch (WebException ex)         {             using (var reader = new StreamReader(ex.Response.GetResponseStream()))             {                 throw new Exception("Azure Auth Failed: " + reader.ReadToEnd());             }         }     } }

private static string GetAccessToken()         {             string tenantId = ConfigurationManager.AppSettings["AzureTenantId"];             string clientId = ConfigurationManager.AppSettings["AzureClientId"];             string clientSecret = ConfigurationManager.AppSettings["AzureClientSecret"];             string resource = ConfigurationManager.AppSettings["SharePointResource"];

 

            string url = "https://login.microsoftonline.com/" + tenantId + "/oauth2/token";

 

            var request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);             request.Method = "POST";             request.ContentType = "application/x-www-form-urlencoded";

 

            string postData =                 "grant_type=client_credentials" +                 "&client_id=" + HttpUtility.UrlEncode(clientId) +                 "&client_secret=" + HttpUtility.UrlEncode(clientSecret) +                 "&resource=" + HttpUtility.UrlEncode(resource);

 

            using (var streamWriter = new System.IO.StreamWriter(request.GetRequestStream()))             {                 streamWriter.Write(postData);             }

 

            var response = (System.Net.HttpWebResponse)request.GetResponse();

 

            using (var reader = new System.IO.StreamReader(response.GetResponseStream()))             {                 string result = reader.ReadToEnd();                 var token = Newtonsoft.Json.Linq.JObject.Parse(result);                 return token["access_token"].ToString();             }         }

We have tried both v1 and v2 endpoints, registered the app, and generated a new client secret. Despite this, the request continues to fail with 401 Unauthorized.

Questions:

  1. Are we missing any specific permissions or configuration in SharePoint Online for app-only authentication?
  2. Are there changes required in the code for .NET 4.0 to work with modern Azure AD app-only authentication?
  3. Could the issue be related to using v1 vs. v2 endpoint, or scope vs. resource parameter?

Any guidance or sample code for a working .NET 4.0 app-only authentication to SharePoint Online using Azure AD would be highly appreciated.

Microsoft 365 and Office | SharePoint | Development
0 comments No comments

2 answers

Sort by: Most helpful
  1. Hin-V 14,080 Reputation points Microsoft External Staff Moderator
    2026-04-02T14:42:50.17+00:00

    Please note that our forum is a public platform, and we will modify your question to hide your personal information in the description. Kindly ensure that you hide any personal or organizational information the next time you post an error or other details to protect personal data.  

    Hi @Selvakumar K

    First, I’d like to clarify that this is a user‑to‑user support forum. Moderators participating here do not have access to backend systems, nor can we directly intervene in Microsoft product functionality. Our role is limited to providing technical guidance and sharing best‑practice recommendations based on reported issues, requests, and scenarios.     

    Regarding your concerns:

    Any misconfiguration in SharePoint Online for app-only authentication

    As far as I know, the 401 Unauthorized error is most likely related to the Azure AD application not being granted the required Application permissions for SharePoint Online.

    At this time, your application may be able to successfully obtain an access token. However, SharePoint rejects the token because the application permissions have not been approved by an administrator.

    Please ensure the following configuration:

    In Azure AD App Registration > API Permissions.

    Add SharePoint > Application permissions (for example, Sites.ReadWrite.All).

    Note: Do not use Delegated permissions for app-only authentication and Global Administrator must click “Grant admin consent”.

    Without admin consent for Application permissions, SharePoint Online will always return 401 Unauthorized, even if the access token is successfully issued.

    For changes required in the code for .NET 4.0 to work with modern Azure AD app-only authentication:

    Based on my research, modern Azure AD app-only authentication no longer supports client secrets for SharePoint Online in many scenarios. Instead, Microsoft requires certificate-based authentication.

    You could try to replace the client secret with an X.509 certificate (.pfx)

    The application must generate and sign a JWT client assertion using that certificate

    Because .NET Framework 4.0 is very old, it is not supported by modern Microsoft authentication libraries, such as:

    MSAL.NET (Microsoft.Identity.Client)

    PnP.Framework

    These libraries handle certificate-based authentication automatically, but they require .NET Framework 4.6.2 or later.

    With .NET 4.0, you would need to implement custom cryptographic logic to manually generate and sign JWT assertions, which is complex and error-prone.

    You may consider upgrading the application to .NET Framework 4.6.2 or higher so that MSAL.NET can be used, allowing certificate-based app-only authentication to be implemented with minimal code.

    Reference: Avoiding Access Errors with SharePoint App-Only Access

    Regarding to v1 vs. v2 endpoint

    Its might not relevant. Looks like your code successfully retrieves an access token means the endpoints (v1 or v2) and the parameters (scope or resource) are working perfectly fine at the Azure AD level. The 401 Unauthorized error is thrown by SharePoint, not Azure AD. SharePoint decodes your valid token, looks at how it was generated, sees that it originated from a Client Secret, and intentionally blocks the connection. Changing between v1 and v2 will not bypass SharePoint's restriction on Client Secrets. 

    Additionally, I recommend decoding the access token (JWT) to verify why SharePoint returns 401. 

    To clearly identify the root cause, we recommend decoding the access token (JWT) using https://jwt.ms. 

    Note: This link may reference Microsoft-related content but is not hosted on an official Microsoft domain (such as .microsoft.com). Please note that Microsoft is not responsible for the accuracy, security, or advertising on this site.

    Please verify the following claims:  

    aud must be: https://{tenant}.sharepoint.com  

    roles must include SharePoint Application permissions, for example: Sites.ReadWrite.All  

    If the token contains scp (scopes) but does not contain roles, this indicates that the application only has Delegated permissions, which are ignored in app‑only authentication. In this case, SharePoint will always return 401 Unauthorized. 

    Please note that this summary is based on my own findings and may not fully address your concerns. To help you reach your goal more effectively, I recommend engaging with  [GitHub Community Forum] for a deeper technical dive or to connect with individuals who have relevant experience and expertise. Some approaches may behave differently or be restricted depending on your specific environment and configuration. These forums include many experienced developers and Microsoft specialists who can assist with troubleshooting and guidance.   

    Apologies for redirecting you to the related development team support. As moderators in this community, we do not have access to your specific tenant configuration, and my testing environment is limited. Therefore, my guidance is based on available Microsoft documentation and resources. That said, I’ll do my best to provide additional insight where possible.   

    I hope this helps.   

    If you have any additional concerns, feel free to comment below. I would be more than happy to assist.  


    Note: Please follow the steps in [our documentation] to enable e-mail notifications if you want to receive the related email notification for this thread. 


  2. Q&A Assist
    2026-04-02T12:58:18.8333333+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    For SharePoint Online app-only with an Entra ID (Azure AD) app, several changes are required compared to username/password and ACS-style tokens.

    Key points from the context:

    • For SharePoint Online, the preferred app-only method is an Entra ID application, not ACS. ACS app-only is retired and will stop working from April 2nd 2026.
    • App-only policy uses OAuth; SharePoint checks only the app principal’s permissions. Authorization succeeds only if the app has sufficient SharePoint permissions.
    • For SharePoint REST/CSOM with Entra ID app-only, the token must be issued for SharePoint (resource = SharePoint Online) and the app must have the correct SharePoint delegated/app permissions.

    Answers to the questions:

    1. Missing permissions/configuration in SharePoint Online Yes, app-only requires:
      • An Entra ID application with SharePoint application permissions (for example, Sites.FullControl.All or other appropriate SharePoint Online app-only permissions), not just Microsoft Graph permissions.
      • Admin consent granted for those SharePoint permissions in Entra ID.
      • The app-only policy in SharePoint: SharePoint must accept app-only tokens and authorize them based on the app principal’s permissions. In the add-in model, this is expressed via AllowAppOnlyPolicy and app permissions; with Entra ID app-only, the equivalent is granting SharePoint Online application permissions to the Entra app.
      If the token is issued for Microsoft Graph (wrong resource/scope) or the app has only Graph permissions, SharePoint will return 401 or “unsupported app only token”. The context explicitly notes a common mistake: using Graph Sites.FullControl.All instead of SharePoint Sites.FullControl.All.
    2. Code changes required for .NET 4.0 The .NET 4.0 code must:
      • Request a token for SharePoint Online as the resource (v1 endpoint) or as the scope (v2 endpoint) that maps to SharePoint, not Graph.
      • Use TLS 1.2 when calling Microsoft 365 endpoints. The context shows that if .NET is not configured for TLS 1.2, token requests or SharePoint calls can fail with 401 or connection errors.
      From the context:
      • Configure .NET Framework to enable TLS 1.2+; otherwise, authentication to SharePoint Online can fail with 401 and transport errors.
      • When using CSOM, set the Authorization: Bearer <access_token> header on ClientContext.ExecutingWebRequest as already done, but ensure the token is valid for SharePoint.
      The pattern of attaching the bearer token via ExecutingWebRequest is correct; the critical part is obtaining a valid SharePoint app-only token and having the right SharePoint permissions.
    3. v1 vs v2 endpoint, scope vs resource The issue is typically not “v1 vs v2” by itself, but:
      • For v1 (/oauth2/token): use resource = https://<tenant>.sharepoint.com (or the SharePoint principal) and grant_type=client_credentials.
      • For v2 (/oauth2/v2.0/token): use scope = https://<tenant>.sharepoint.com/.default and grant_type=client_credentials.
      The context shows that:
      • There are two app-only approaches: Entra ID app-only (preferred) and SharePoint ACS app-only (retired).
      • For Entra ID app-only, the app must have SharePoint Online application permissions and the token must be for SharePoint, not Graph.
      • Using a client secret vs certificate can matter: one example in the context shows that SharePoint REST rejected a client-secret-based token as “unsupported app only token” until the app was switched to certificate-based auth. For robust SharePoint REST app-only, using a certificate is recommended.
      So, 401 Unauthorized is most likely caused by:
      • Token issued for the wrong resource (Graph instead of SharePoint), or
      • Missing SharePoint application permissions (e.g., not granting Sites.FullControl.All for SharePoint), or
      • Using an unsupported app-only token format (client secret vs certificate) for SharePoint REST.
      Once the Entra ID app is configured with SharePoint application permissions and a token is requested for SharePoint Online (correct resource/scope) over TLS 1.2, the existing .NET 4.0 pattern of attaching the bearer token to ClientContext should work for app-only access.

    References:

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.