Sitecore Azure AD Authentication and Docker

Getting your customizations into your Identity Server Docker container

May 7, 2024

By Ryan Allan

The changes to set up Azure Active Directory haven’t changed very much in the past few years, but getting them into Sitecore has. These days everyone has their own good deployment processes and files for the content management and the content deliver servers, or got them from Sitecore themselves. Customizing the Identity server is rare though, and not everyone has good code for that, or in-depth knowledge to set it up themselves.

Configuring Sitecore for Azure AD

In case you are here for the Sitecore setup, my colleague David has written two excellent articles on configuring Sitecore and Azure AD itself. See this article for the bulk of the setup, and this article for surprising behavior from the default user builder. These changes are still current for Sitecore as of the time of this article, and likely will continue to be good for a long time yet.

Building the Bridge Into Docker

You’ve got your setup now, and managed to deploy it to your CM server. Remember to budget lots of time for testing, Azure AD authentication requires every step to be just right. Now, it’s likely you haven’t had to make any changes to the docker setup in a long time, or possibly never! Where do you put your shiny new XML configuration file for the identity server? I found it simplest to add an Identity.csproj file to my solution, and wire it into the Docker build steps.

The project only contains one file:

sitecore/Sitecore.Plugin.IdentityProvider.AzureAd/Config/Sitecore.Plugin.IdentityProvider.AzureAd.xml

That might sound like overkill, but it’s much simpler for the next part of the process.

Here’s an example Dockerfile for the solution build:

# escape=`

ARG BASE_IMAGE ARG BUILD_IMAGE

FROM ${BUILD_IMAGE} AS prep SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# Gather only artifacts necessary for NuGet restore, retaining directory structure COPY *.sln nuget.config Directory.Build.targets src\Project.Website\packages.config \nuget
COPY src\ \temp
RUN Invoke-Expression 'robocopy C:\temp C:\nuget\src /s /ndl /njh /njs *.csproj *.scproj packages.config'
FROM ${BUILD_IMAGE} AS builder

ARG BUILD_CONFIGURATION

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# Create an empty working directory WORKDIR C:\build # Copy prepped NuGet artifacts, and restore as distinct layer to take better advantage of caching COPY --from=prep .\nuget .
RUN nuget restore
# Copy remaining source code COPY src\ .\src\

# Copy transforms, retaining directory structure RUN Invoke-Expression 'robocopy C:\build\src C:\out\transforms /s /ndl /njh /njs *.xdt' # Build XConnect with file publish RUN msbuild .\src\Project.XConnect\XConnect.csproj /p:Configuration=$env:BUILD_CONFIGURATION /p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:PublishUrl=C:\out\xconnect # Build website with file publish RUN msbuild .\src\Project\Website.csproj /p:Configuration=$env:BUILD_CONFIGURATION /p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:PublishUrl=C:\out\website # Build identity with file publish RUN msbuild .\src\Project.Identity\Identity.csproj /p:Configuration=$env:BUILD_CONFIGURATION /p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:PublishUrl=C:\out\identity FROM ${BASE_IMAGE}

WORKDIR C:\artifacts # Copy final build artifacts COPY --from=builder C:\out\website .\website
COPY --from=builder C:\out\transforms .\transforms
COPY --from=builder C:\out\xconnect .\xconnect
COPY --from=builder C:\out\identity .\identity</span>

I’ve only had to add two lines here, the msbuild line with Identity.csproj and the COPY line to move the artifacts, which is just the one file.

Then there is are a few changes to the Dockerfile for the Identity server, likely called “id” in your codebase. This is the original version, it’s very bare bones.

# escape=`

ARG BASE_IMAGE

FROM ${BASE_IMAGE}

Our updated version, which grabs the files from the COPY command up above:

# escape=`

ARG BASE_IMAGE ARG SOLUTION_IMAGE

FROM ${SOLUTION_IMAGE} as solution FROM ${BASE_IMAGE}

SHELL ["Powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

WORKDIR C:\Identity COPY --from=solution \artifacts\identity\ .
ENTRYPOINT ["dotnet", "Sitecore.IdentityServer.Host.dll"]

The COPY command here overwrites the original XML file with our new file, and the entrypoint is actually unchanged, it just needs to be explicitly called out now. You can check in the container to make sure the file was deployed, but that’s the whole thing!

You Do Not Have Access to the System

Here’s a surprise that might happen to you. It’s not related to Docker but it’s still good to know.

Login screen on Sitecore showing a warning about restricted access with fields for user name and password

Azure AD might return hasGroups:true instead of the group id. That’s why you see this error message. This happens when the user belongs to more than five groups. You can test against https://jwt.ms to see if that’s the case.

Microsoft has a couple options to fix the problem. https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims#configure-groups-optional-claims "Groups assigned to the application" is one option, and the other is a group filter here: https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-group-claims#group-filtering

Safe on the Other Side

That covers the whole process. Create your XML file, link it into the solution Dockerfile, pull from the solution into the Identity Server image, and you’re done. Different solutions may have different details, but you can generalize from this. For example, your solution build might start with the entire sln file, and you wouldn’t need the msbuild command just for the Identity.csproj file.



Ryan

Ryan Allan

Senior Developer

Ryan is a seasoned Senior Developer at Fishtank and is Sitecore Developer Certified. In a nutshell, he describes his role as "building extremely fancy websites". Ryan has a degree in Mechanical Engineering and previously worked on power plants and compressors, and now builds websites for the same company as a client! Outside of work, his interests are science + physics and spending time with his kids.