By
Shah Chandon. September 26, 2024.

In .Net 8, the on-premise WS-Fed authentication has changed slightly. As a result the .Net 6 version of the WS-Fed authentication is no longer working.

This is based of off: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2406#issuecomment-1814246256

All credits are due to the user “Hakon” in the above thread.

.Net 6 version of authentication

In the program.cs/startup.cs we have the following code that enables the ws-fed authentication:

    		// Add AD Authentication
			builder.Services.AddAuthentication(
			sharedOptions =>
			{
				sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
				sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
			})
			.AddWsFederation(options =>
			{
				options.Wtrealm = builder.Configuration["wsfed:realm"];
				options.MetadataAddress = builder.Configuration["wsfed:metadata"];                
            })
			.AddCookie();

In the appsettings.json we have the following config for ws-fed:

  "wsfed": {
    "realm": "https://localhost:44342",
    "metadata": "https://adfstest.uwaterloo.ca/federationmetadata/2007-06/federationmetadata.xml"
  }

Error with .Net 6 WS-Fed

Below is the sample of the error that was received when trying to access the site with the .Net 6 version of the code.

    An unhandled exception occurred while processing the request. 
    XmIReadException: IDX30011: Unable to read XML. Expecting XmIReader to be at ns.element: '[PII of 
    type 'System.StringI is hidden. For more details, see https://aka.ms/IdentityModel/PIl.].[PlI of type 
    'System.String' is hidden. For more details, see https://aka.ms/IdentityModeI/Pll.]', found: '[PII of type 
    'System.String' is hidden. For more details, see https://aka.ms/IdentityModeI/Pll.].[PlI of type 
    'System.String' is hidden. For more details, see https://aka.ms/IdentityModeI/Pll.]'. 
    Microsoft.ldentityModel.Xml.XmlUtil.CheckReaderOnEntry(XmlReader reader, string element, string namespace) 
    AggregateException: One or more errors occurred. (IDX30011: Unable to read XML. Expecting 
    XmIReader to be at ns.element: '[PII of type 'System.String' is hidden. For more details, see 
    https://aka.ms/IdentityModel/PIl.].[PlI of type 'System.String' is hidden. For more details, see 
    https://aka.ms/IdentityModel/PIl.]I, found: '[PII of type 'System.StringI is hidden. For more details, see 
    https://aka.ms/IdentityModel/PIl.].[PlI of type 'System.String' is hidden. For more details, see 
    https://aka.ms/IdentityModel/PIl.]I.) (IDX10204: Unable to validate issuer. 
    validationParameters.Validlssuer is null or whitespace AND validationParameters.VaIidIssuers is null or 
    empty.) (IDX14122: JWT is not a well formed JWE, there are more than four dots (.) a JWE can have at 
    most 4 dots. 
    The token needs to be in JWS or JWE Compact Serialization Format. (JWS): 
    'EncodedHeader.EncodedPayIoad.EncodedSignature'. (JWE): 
    'EncodedProtectedHeader.EncodedEncryptedKey.EncodedlnitiaIizationVector.EncodedCiphertext.Enco 
    Microsoft.ldentityModel.Xml.XmlUtil.CheckReaderOnEntry(XmlReader reader, string element, string namespace) 
    SecurityTokenException: No token validator or token handler was found for the given token. 
    Microsoft.As NetCore.Authentication.WsFederation.WsFederationHandler.HandleRemoteAuthenticateAs nc 

Cause

It seems some of the validationParameters are received from the security server but not populated as validationParameters. The values are kept inside validation configuration!

Add a new security token handler

We need to add a new security token handler that will read the validation configuration and then populate the missing vallidation parameter properties.

using Microsoft.IdentityModel.Tokens.Saml;
using Microsoft.IdentityModel.Tokens;

namespace myAccessLostNFound.CustomSecurityHandlers
{
    /// <summary>
    /// This class helps with the validation of the WSFed tokens. Some of the validation params are empty in .net 8. 
    ///     However, those values exists in the configurationManager of the validationParam itself at runtime.
    ///     This method, reads those values from the runtime config manager and adds them directly under the validationParameters object.
    ///     This is based of off: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2406#issuecomment-1814246256
    /// </summary>
    public class CustomSamlSecurityTokenHandler : SamlSecurityTokenHandler
    {
        public override async Task<TokenValidationResult> ValidateTokenAsync(string token, TokenValidationParameters validationParameters)
        {

            var configuration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
            var issuers = new[] { configuration.Issuer };
            validationParameters.ValidIssuers = (validationParameters.ValidIssuers == null ? issuers : validationParameters.ValidIssuers.Concat(issuers));
            validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys == null ? configuration.SigningKeys : validationParameters.IssuerSigningKeys.Concat(configuration.SigningKeys));

            var baseValidationRes = await base.ValidateTokenAsync(token, validationParameters);
            return baseValidationRes;
        }
    }
}

Register the token handler in the program.cs/startup.cs

Clear out any previous token handlers and then add the new one. The new handler calls the base token validation method after populating the validation params.

                // Add AD Authentication
                builder.Services.AddAuthentication(
                sharedOptions =>
                {
                    sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
                })
                .AddWsFederation(options =>
                {
                    options.Wtrealm = builder.Configuration["wsfed:realm"];
                    options.MetadataAddress = builder.Configuration["wsfed:metadata"];
                    options.TokenHandlers.Clear();
                    options.TokenHandlers.Add(new CustomSamlSecurityTokenHandler());
                })
                .AddCookie();

Comments/thoughts

Please feel free to let me know.

Leave a Reply

Your email address will not be published. Required fields are marked *


*