We are building a Worker
service that we want to host in a Docker container. This container interacts with the REST APIs for our on-premise SharePoint server. Authentication in SharePoint REST APIs uses Windows authentication.
When I run the code below from Visual Studio Code (using dotnet run
on my Windows development machine), the code works without the exception thrown.
var opts = new RestClientOptions("https://sharepoint.domain.local");
opts.Credentials = new NetworkCredential("username", "password", "domain.local");
var spclient = new RestClient(opts);
var req = new RestRequest("_api/lists/getbytitle('MY_LIST')/items", Method.Get);
var resp = spclient.Execute(req);
_logger.LogDebug("Status code {x} returned from API call {y}", resp.StatusCode, resp.ResponseUri);
_logger.LogTrace("Raw response from {url}. Content: {raw}", resp.ResponseUri, resp.Content);
if(!resp.IsSuccessful)
{
throw new Exception();
}
Now, when I run the same code in the Docker container, the API call is failing and I am getting an Unauthorized
status code from the SharePoint REST API, with the following JSON response:
{
"odata.error": {
"code": "-2147024891, System.UnauthorizedAccessException",
"message": {
"lang": "en-US",
"value": "Access denied. You do not have permission to perform this action or access this resource."
}
}
}
For testing purposes, I am using the same username/password as I use on my DEV machine. I am certain the username and password is provided and not empty.
I've also tried specifying NTLM
using the following code. No change in behaviour:
CredentialCache cc = new CredentialCache();
cc.Add("https://sharepoint.domain.local", "NTLM", "username", "password", "domain.local");
var opts = new RestClientOptions("https://sharepoint.domain.local");
opts.Credentials = cc;
var spclient = new RestClient(opts);
var req = new RestRequest("_api/lists/getbytitle('MY_LIST')/items", Method.Get);
var resp = spclient.Execute(req);
_logger.LogDebug("Status code {x} returned from API call {y}", resp.StatusCode, resp.ResponseUri);
_logger.LogTrace("Raw response from {url}. Content: {raw}", resp.ResponseUri, resp.Content);
if(!resp.IsSuccessful)
{
throw new Exception();
}
Any ideas on how to get this working, please?
If anyone else runs into this problem, there are some packages you need to install into your container image for this to work.
Let's say you have this code (note: domain is optional. Username username@domain.local
is acceptable):
var opts = new RestClientOptions("https://sharepoint.domain.local");
opts.Credentials = new NetworkCredential("username", "password", "domain.local");
var spclient = new RestClient(opts);
var req = new RestRequest("_api/lists/getbytitle('MY_LIST')/items", Method.Get);
var resp = spclient.Execute(req);
_logger.LogDebug("Status code {x} returned from API call {y}", resp.StatusCode, resp.ResponseUri);
_logger.LogTrace("Raw response from {url}. Content: {raw}", resp.ResponseUri, resp.Content);
if(!resp.IsSuccessful)
{
throw new Exception();
}
Next, build a Dockerfile
with similar contents:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /source
# copy csproj and restore as distinct layers
COPY ./src .
RUN dotnet restore
# publish app and libraries
RUN dotnet publish -c release -o /app
# final stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# Install necessary packages for NTLM/Kerberos
RUN apt-get update && apt-get install -y libkrb5-3 libgssapi-krb5-2 gss-ntlmssp && rm -rf /var/lib/apt/lists/*
# Create the ./logs directory
RUN mkdir -p ./logs
COPY --from=build /app .
ENTRYPOINT [ "dotnet", "my.application.dll" ]