When building console applications, Azure Functions, or desktop tools that interact with SharePoint Online, authentication is one of the most important challenges.

Older approaches relied on credentials or app-only secrets, but today Microsoft recommends using Microsoft Authentication Library (MSAL) for secure and modern OAuth 2.0 flows.


🔐 Modern Authentication in SharePoint Online with MSAL and CSOM (C#)

Overview

When building console applications, Azure Functions, or desktop tools that interact with SharePoint Online, authentication is one of the most important challenges.

Older approaches relied on credentials or app-only secrets, but today Microsoft recommends using Microsoft Authentication Library (MSAL) for secure and modern OAuth 2.0 flows.

This article shows how to implement a simple but robust authentication helper — AuthService — that:

  • Acquires and caches a delegated access token using MSAL.
  • Silently renews tokens when possible (avoiding repeated login prompts).
  • Injects the token into CSOM (Client-Side Object Model) requests to SharePoint.

🧠 The Concept

The idea is to authenticate once interactively (through the system browser), then reuse and refresh tokens silently as long as they’re valid.

This works using the public client flow, suitable for desktop or console applications that cannot store a client secret.


⚙️ Full Implementation

Below is the complete implementation of the AuthService class in C#:

using Microsoft.Identity.Client;
using Microsoft.SharePoint.Client;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Contoso_SP_Clone_Site
{
    public class AuthService
    {
        private static IPublicClientApplication _app;
        private static AuthenticationResult _authResult;

        // 👇 Define your SharePoint scope (site collection or tenant-wide)
        private static readonly string[] scopes = new[] { "https://contoso.sharepoint.com/AllSites.FullControl" };

        // 👇 Replace with your registered app details in Entra ID (Azure AD)
        private static readonly string tenantId = "YOUR_TENANT_ID";
        private static readonly string clientId = "YOUR_CLIENT_ID";

        /// <summary>
        /// Retrieves an access token, using cache or interactive login if required.
        /// </summary>
        public static async Task<string> GetAccessTokenAsync()
        {
            // Build the MSAL app instance (once)
            if (_app == null)
            {
                _app = PublicClientApplicationBuilder.Create(clientId)
                    .WithTenantId(tenantId)
                    .WithRedirectUri("http://localhost") // Browser redirect
                    .Build();
            }

            // ✅ Reuse existing valid token (no new request)
            if (_authResult != null && _authResult.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5))
            {
                return _authResult.AccessToken;
            }

            try
            {
                // ✅ Try silent renewal (refresh token, no UI)
                var accounts = await _app.GetAccountsAsync();
                _authResult = await _app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
                                        .ExecuteAsync();
            }
            catch (MsalUiRequiredException)
            {
                // ⚠️ Fallback to interactive (opens system browser once)
                _authResult = await _app.AcquireTokenInteractive(scopes)
                                        .WithPrompt(Prompt.SelectAccount)
                                        .ExecuteAsync();
            }

            return _authResult.AccessToken;
        }

        /// <summary>
        /// Creates a ClientContext with the valid access token automatically injected.
        /// </summary>
        public static ClientContext GetContext(string siteUrl)
        {
            var ctx = new ClientContext(siteUrl);
            ctx.ExecutingWebRequest += async (sender, e) =>
            {
                string token = await GetAccessTokenAsync();
                e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + token;
            };
            return ctx;
        }
    }
}


🧩 How It Works — Step by Step

StepDescription
1️⃣The app registers an Azure AD App (public client) with redirect URI http://localhost.
2️⃣The first call to GetAccessTokenAsync() builds a PublicClientApplication.
3️⃣If a token is already cached and valid, it is reused.
4️⃣If expired, MSAL attempts a silent token renewal using refresh tokens.
5️⃣If silent renewal fails (e.g., first run or expired refresh token), MSAL triggers interactive login through the system browser.
6️⃣Once authenticated, the access token is injected into the ClientContext authorization header.
7️⃣Any CSOM call (e.g., ExecuteQueryAsync()) is now authenticated to SharePoint Online.

🧱 Integration Example

Here’s how you use the AuthService class in your main program:

static async Task Main(string[] args)
{
    string siteUrl = "https://contoso.sharepoint.com/sites/Engineering";
    using (var ctx = AuthService.GetContext(siteUrl))
    {
        ctx.Load(ctx.Web, w => w.Title);
        await ctx.ExecuteQueryAsync();
        Console.WriteLine($"Connected to site: {ctx.Web.Title}");
    }
}

✅ The first run will open a browser asking you to sign in.
✅ Subsequent runs will use cached tokens silently.


🛡️ Why This Approach Is Secure

FeatureBenefit
MSAL Token CacheTokens are automatically stored securely in the OS user profile.
No passwords in codeOnly user login through Microsoft’s secure OAuth flow.
Refresh Token HandlingAutomatic silent renewal avoids unnecessary prompts.
Scope GranularityYou can limit app access (e.g., only specific site collections).

⚠️ Common Pitfalls

IssueDescription / Fix
401 UnauthorizedEnsure your app is granted the right delegated permission (e.g., AllSites.FullControl).
Stuck Login LoopClear MSAL token cache by calling _app.RemoveAsync(account) and retry.
No Browser Window OpensMake sure .WithRedirectUri("http://localhost") is set — MSAL uses it for interactive flows.
Token Expired During Long TasksAdd logic to refresh token mid-process using GetAccessTokenAsync() again before each CSOM call.

🧠 Key Takeaways

TopicSummary
MSALHandles all OAuth 2.0 authentication securely.
Public Client FlowIdeal for desktop and console apps without secrets.
Silent RenewalKeeps user experience smooth after first sign-in.
CSOM IntegrationSimple ExecutingWebRequest event injects tokens seamlessly.

📘 Further Reading


Conclusion

The AuthService class provides a clean, secure, and modern way to authenticate to SharePoint Online using MSAL.
It eliminates hardcoded credentials, simplifies token renewal, and integrates easily with existing CSOM or PnP Framework operations — making it an essential building block for any SharePoint automation or site provisioning tool.

Edvaldo Guimrães Filho Avatar

Published by