-
Hi I am trying to build an Web API to serve Microsoft 365 Copilot teams toolkit based extensions. I have it all working with Web API published to Azure Web App, but auth is temperamental after a period I get a 403 error. I have used a standard .net web api app, have a app registration that supports delegated permissions to SharePoint (Sites.Selected). Used PnP Core SDK + a custom auth provider to use the token from Web API in calls to SharePoint via the Graph. Bit like here: https://pnp.github.io/pnpcore/using-the-sdk/custom-authentication-provider.html It works, I can read/write data to SharePoint but after a small period (think 30 mins) it starts getting 403 errors in Azure. The web app has identity configured, if the token for that is invalid it generates 401s. Custom API Provider public class WebAPICustomPnPProvider : IAuthenticationProvider
{
private readonly ITokenAcquisition _tokenAcquisition;
private readonly ILogger<WebAPICustomPnPProvider> _logger;
public WebAPICustomPnPProvider(ITokenAcquisition tokenAcquisition, ILogger<WebAPICustomPnPProvider> logger)
{
_tokenAcquisition = tokenAcquisition;
_logger = logger;
}
/// <summary>
/// Authenticates the request.
/// </summary>
/// <param name="resource"></param>
/// <param name="request"></param>
/// <returns></returns>
public async Task AuthenticateRequestAsync(Uri resource, HttpRequestMessage request)
{
if (request == null)
{
_logger.LogError("Request is null.");
throw new ArgumentNullException(nameof(request));
}
if (resource == null)
{
_logger.LogError("Resource is null.");
throw new ArgumentNullException(nameof(resource));
}
try
{
var accessToken = await GetAccessTokenAsync(resource).ConfigureAwait(false);
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
_logger.LogInformation("Request authenticated successfully for resource {Resource}.", resource);
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while authenticating the request for resource {Resource}.", resource);
throw;
}
}
/// <summary>
/// Gets the access token.
/// </summary>
/// <param name="resource"></param>
/// <param name="scopes"></param>
/// <returns></returns>
public async Task<string> GetAccessTokenAsync(Uri resource, string[] scopes)
{
if (resource == null)
{
_logger.LogError("Resource is null.");
throw new ArgumentNullException(nameof(resource));
}
if (scopes == null)
{
_logger.LogError("Scopes are null.");
throw new ArgumentNullException(nameof(scopes));
}
try
{
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes).ConfigureAwait(false);
_logger.LogInformation("Access token acquired successfully for resource {Resource}.", resource);
return accessToken;
}
catch (MsalUiRequiredException ex)
{
_logger.LogError(ex, "Token expired or user interaction required while acquiring access token for resource {Resource}.", resource);
throw new UnauthorizedAccessException("Token has expired or user re-authentication required.", ex);
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while acquiring the access token for resource {Resource}.", resource);
throw;
}
}
/// <summary>
/// Gets the access token.
/// </summary>
/// <param name="resource"></param>
/// <returns></returns>
public async Task<string> GetAccessTokenAsync(Uri resource)
{
try
{
var scopes = GetRelevantScopes(resource);
var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes).ConfigureAwait(false);
return accessToken;
}
catch (MsalUiRequiredException ex)
{
_logger.LogError(ex, "Token expired or user interaction required while acquiring access token for resource {Resource}.", resource);
throw new UnauthorizedAccessException("Token has expired or user re-authentication required.", ex);
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while acquiring the access token for resource {Resource}.", resource);
throw;
}
}
// Minimum permissions
private const string MicrosoftGraphScope = "Sites.Selected";
private const string SharePointOnlineScope = "AllSites.Write";
private string[] GetRelevantScopes(Uri resourceUri)
{
if (resourceUri.ToString() == "https://graph.microsoft.com")
{
return new[] { $"{resourceUri}/{MicrosoftGraphScope}" };
}
else
{
string resource = $"{resourceUri.Scheme}://{resourceUri.DnsSafeHost}";
return new[] { $"{resource}/{SharePointOnlineScope}" };
}
}
} Web API Configuration builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDistributedTokenCaches();
//... skipped for berevity
// Add PnP Core SDK services to process messages
builder.Services.AddScoped<IMessageService, PnPSharePointMessageService>();
builder.Services
.AddScoped<IAuthenticationProvider, WebAPICustomPnPProvider>(sp =>
{
var tokenAcquisition = sp.GetRequiredService<ITokenAcquisition>();
var logger = sp.GetRequiredService<ILogger<WebAPICustomPnPProvider>>();
return new WebAPICustomPnPProvider(tokenAcquisition, logger);
})
.AddSingleton<IConfiguration>(builder.Configuration)
.AddScoped<IM365PnPContextFactory, M365PnPContextFactory>();
// Add the PnP Core SDK library services
builder.Services.AddPnPCore(options =>
{
options.PnPContext.GraphFirst = true;
}); Any ideas? CC: @PaoloPia - do you think Im doing any craziness here, or do you know a way to streamline. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
So it turns out, if you turn OFF the Authentication settings in Azure, which I had enabled, this resolves the issue. This problem is not specific to PnP Core SDK as, after creating a mocking service exhibited the same behaviour. |
Beta Was this translation helpful? Give feedback.
So it turns out, if you turn OFF the Authentication settings in Azure, which I had enabled, this resolves the issue. This problem is not specific to PnP Core SDK as, after creating a mocking service exhibited the same behaviour.